A lot of articles have been written about the advantages of the transition from version 1.2. However, according to statistics, more than 45% of sites still use version 1.2, only 31% switched to a newer 1.3 and only 5% use 1.4.
And this is when
space ships plow the expanses of the universe, version 1.2.0 was released almost two years ago, version 1.3.0 - a year ago, version 1.4.0 - this spring, and 1.5.0 is already in beta.

')
As a rule, large projects from transition restrain the opacity of this process and the scarcity of materials on this subject.
In the official guide you can find only a small piece of all possible problems, and blogs, as a rule, only retell it.
In this article we will talk about what you can encounter when migrating to new versions, and analyze the most problematic places.
Unfortunately, the post came out too big, so in this part I will focus on breaking changes of this transition, and we will talk about features and merits in the next part. But I note that the main advantage of the transition is a significant increase in the speed of work, as well as a solid list of fixed bugs (after all, the latest fixes for 1.2 were a year ago at 1.2.28).
I divided this post into two parts. One of them is my personal experience of translating two projects into new versions (I hid it under the spoiler), the second is a list of problems found, read and translated with explanations and examples.
The history of our transitionThe first relatively large project that required a transition (from 1.2 to 1.3) was the E-Learning platform, where, despite more than 12,000 lines of code (mostly directives), no problems arose.
The second project required migration from version 1.2.16 to 1.4.4 and was significantly larger (about 75,000 lines of pure angular), and product specifics (accounting) implied complex relationships, many forms and inevitable problems, but previous experience inspired.
The first result of the migration was expected to get a non-working application with a bunch of errors in the console, but the application started, and there were no problems. It was extremely strange, so I began my journey through the official guide, where, however, none of the problems described were reproduced. Having decided that the job was done, I gave the task to the QA and automatic tests.
And the next day I received 9 bugs, then another 9, and more. The insidiousness of all the errors found was not in the fact that they break the work of the application, but in the fact that it imperceptibly changes its behavior.
Here,
Change Log and discussions inside the commits found there became a friend and companion in search of reasons.
First of all, asynchronous checks on the server side, which did not work with statuses, but with responses in the form of an “OK” text primitive, fell off. Not a single break change was notified about this, but there was a corresponding
bug fix .
Tip: Check your work with XHR requests. If the server does not send you an object, but a primitive with the “application / json” headers, you will have problems.
The next came the spinners embedded in the pop-ups, ceasing to correctly determine the width of the parent. The problem was two things. First, the spinners lay in
ng-show , which means they were initialized before the parent container could be displayed. Secondly, the spinner showed / hid, following the attribute through
$ observe . For some reason, in the old version, the attribute changed later than the parent became
: visible , and in the new one, vice versa.
Tip:- Do not store dynamic directives for which the moment of initialization is important in ng-show / ng-hide , use ng-if for this. This advice is also relevant for version 1.2.
- Do not use $ observe to track attributes if you need to monitor external changes to other data (DOM or $ scope). Use $ watch (function () {}, function () {}) for this . Moreover, both options add their votcher to a dirty check, the only difference is in the conditions of calling a callback.
The same problem affected the conditions in the expressions inside
$ eval : some blocks began to disappear. But the problem turned out to be another
bug-fix and was already
covered in Habré .
Tip: Do not use the undocumented features of the framework, examine the code for use when checking the following values ​​in
$ scope :
'f' ,
'0' ,
'false' ,
'no' ,
'n' ,
'[]' .
The main problem that has arisen is validation. Starting with version
1.3 , new methods for checking forms have appeared in the anguly, and with them new pitfalls. But there are problems that will affect you, even if you do not plan to use the new validation. These are the
maxlength / minlength directives and the new
$ setValidity logic that threatens to make your form permanently invalid (this is what happened to us).
It was found that we kept in
ngModelCtrl. $ Error the special property
showError (for conveniently displaying errors one by one), which led to a permanent invalidation of the form, because in the new version one of the validity conditions is the empty hash
ngModelCtrl. $ Error .
Tip: Do not put in properties that start with
$ nothing that is not listed in the official API.
Another problem arose with inputs that use a mask (for phone, contract numbers, etc.) in tandem with the
ng-maxlength / ng-minlength directives .
Both of these directives now check
ngModelCtrl. $ ViewValue instead of
ngModelCtrl. $ ModelValue , which means that the maximum length for the value “xx-xx” is no longer 4, but 5 (taking into account the “-” character). I would have had to rewrite hundreds of verification rules throughout the application, so it was decided to replace all
ng-maxlength with a custom
model-maxlength with autocorrect , which would only check the model again. And this decision was terrible! Angulyar reserved himself the maxlength attribute limiter, which means we could no longer limit the number of characters entered. As a result, it was decided to change all the rules for new ones, taking into account the mask symbols. However, the custom
model-minlength directive found its use in directives where there are predefined characters (for example, "+7" for the phone), allowing you to check only the model without the prefixes set in
ngModelCtrl. $ ViewValue .
Tip: If you use a mask or otherwise manipulate the value of
ngModelCtrl. $ ViewValue , change the validation taking into account mask characters. Use the
placeholder attribute for the predefined values ​​in the input with the
ng-minlength check or replace the check with the custom one. The working code of such a directive is in the break changes list.
The second wave of problems went after the transition to our new validation (goodbye,
$ formatters and
$ parsers ). We are faced with the fact that a number of forms again became permanently invalid.
With synchronous validations (
$ validators ), the problem came to light quickly. It consisted in the fact that the fields contained hidden fields via
ng-show with their validation checks. Due to the peculiarities of working in the old version (
$ formatters and
$ parsers ), these fields were not included in the form verification, however, in the new version (
$ validators ) this results in a hidden
non-valid field.
Tip: If you have forms with hidden dynamic fields, then hide these fields or show them through
ng-if , do not use
ng-show / ng-hide .
Remark: How asynchronous validators workAsynchronous validators are stored in the
ngModelCtrl. $ AsyncValidators collection and are a function that returns a
Promise . Promises return various services (for example, $ timeout and $ http), and are also generated by the special service
$ q . The validity of the field depends on the resolved or rejected return. Asynchronous validation starts only after all synchronous validators become valid. At the time of the validator call, its validity (
$ setValidity ) will be equal to
undefined , and the name of the expected validator will appear in
ngModelCtrl. $ Pending . As soon as the resolve of the promise resolves, its validity will be set to
null .
If you call the validation function several times, the last promise will wipe the previous ones. This means that if the field was validated twice, and the first promise was resolved and the second rejected, the field would be invalid.
An example from the API documentation:ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { var value = modelValue || viewValue;
With asynchronous validation, it turned out to be more difficult: the form was invalid, but did not contain any errors.
Here, due to the
nature of the mask (via
$ formatters and
$ parsers ), asynchronous validation was called before the completion of synchronous validations, and it was called several times in 1 changed symbol. This caused a multiple creation bug of promises, which led to the fact that the last promise was not resolved or rejected. Accordingly, the input received infinite pending, and the form was invalid without any errors.
An example of building a validator for such cases var pendingPromise; ngModelCtrl.$asyncValidators.checkPhoneUnique = function (modelValue) { if (pendingPromise) { return pendingPromise; } var deferred = $q.defer(); if (modelValue) { pendingPromise = deferred.promise; $http.post('/', {value: modelValue}) .success(function (response) { if (response.Result === ' ') { deferred.resolve(); } else { deferred.reject(); } }).error(function () { deferred.reject(); }).finally(function () { pendingPromise = null; }); } else { deferred.resolve(); } return deferred.promise; };
Tip: Test the behavior of asynchronous validators: make sure the return promise will always be resolved or rejected.
The strangest problem of validation was the custom directive for entering the amount (it looked like two input lines for rubles and kopecks, giving to ngModel the ready result of concatenating these numbers). It was not only invalid, but it also reported an error to the console from the depths of the framework.
Time was spent on the problem fairly, the problem was again in ng-maxlength, and the solution was simple: add directives in $ formatters to convert the value of
$ viewValue to a string.
But why the problem arose at all? Let's figure it out!
It's all about
$ compile : the behavior of the main directives is described inside. For example, for
input and
textarea , the
inputDirective control is
called , which takes
attr.type and, based on it, calls one of the functions in the
inputType collection. For text types, this is
textInputType . He, in turn, passes our control into the function
stringBasedInputType , which adds in
$ formatters the code that converts our value to a string.
Thus, when
ngModel is not tied to some existing base element such as
input , but, for example, hangs on a simple diva or custom directive (in our case it is a directive to input the amount), the data is thrown into
$ viewModel "as is", which causes an error for filter directives like maxlength, which for their work use the property
.length , which is absent in numbers.
Working example on Plunker .
Tip: All custom directives that work with numbers must have the appropriate formatter to convert numbers to strings.
Total:
As a result, about 6 iterations of tests were driven off and 54 problems were found, but most of them had a similar nature. Part of the problem could not be at all, do not use some parts of the code for their work bugs and undocumented features. A total of 56 commits and 3 weeks of working time per person were spent on migration, taking into account refactoring and the transition to a new validation.
Part One: Breaking Changes
Immediately, I note that from now on we are losing support for IE8. However, it can be returned by connecting the necessary polyfills.
Job $ parse
.bind, .call and .apply
You can no longer call
.bind ,
.call, and
.apply inside an expression (for example,
{{}} ).
This allows you to be sure that the behavior of existing functions cannot be changed.
__proto__
Since version
1.3, the deprecated (
deprecated ) property
__proto__ has
been removed.
Previously, it could be used to access global prototypes.
Object
It is forbidden to use
Object inside expressions.
This is due to the ability to execute arbitrary code in expressions.
For example: ''.sub.call.call( ({})["constructor"].getOwnPropertyDescriptor(''.sub.__proto__, "constructor").value, null, "alert('evil')" )()
If someone needs
Object.key or some other method, pass it through
scope .
{define, lookup} {Getter, Setter}
Since version
1.3 , the
{define, lookup} {Getter, Setter} properties, which allowed to execute arbitrary code inside an expression, are prohibited.
If you need these properties, wrap them in the controller and push them into the
scope with your hands.
$ parseProvider
[commit]Removed obsolete
$ parseProvider.unwrapPromises and
$ parseProvider.logPromiseWarnings methods .
$ interpolate
The functions returned by
$ interpolate no longer contain an array of
.parts . Instead, they contain:
- .expressions , which contains all the expressions in the text.
- .separators , in which there are delimiters between expressions in the text. It is always 1 element longer than .expressions , for the convenience of combining.
toBoolean
In an angular, the true implementation of
toBoolean () is used to test for truth, which equates to
false some non-standard values ​​in the form of the following lines:
'f' ,
'0' ,
'false' ,
'no' ,
'n' ,
'[]'Starting with version
1.3 , only the same values ​​as in the normal JS are equal to false:
false ,
null ,
undefined ,
NaN ,
0 ,
""About it wrote on HabréFor example: $scope.isEnabled = 'no';
<1.3: {{ isEnabled ? ' ' : '' }}
1.3+: {{ isEnabled ? '' : ' ' }}
Helpers
.copy ()
Previously, when working with
copy objects, I copied all the properties of the object, including those in the prototype, which led to the loss of the prototype chain (except for Date, RegExp and Array).
Starting with version
1.3 , it copies only its own properties (something like
iteration with
hasOwnProperty ), and then refers to the prototype of the original.
For example: var Foo = function() {}; Foo.prototype.bar = 1; var foo = new Foo(); var fooCopy = angular.copy(foo); foo.bar = 3;
<1.3: console.log(foo instanceof Foo);
1.3+: console.log(foo instanceof Foo);
IE8: Object.create and
Object.getPrototypeOf polyfills are required .forEach ()
Previously, if the array increased in the process of iteration, then the cycle went through the newly appeared elements too.
Starting with version
1.3 , it caches the number of elements in the array and passes only through them, here it is closer to the native
Array.forEach .
For example: var foo = [1, 2];
<1.3: angular.forEach(foo, function (value, key) { foo.push(null);
1.3+: angular.forEach(foo, function(value, key) { foo.push(null);
.toJson ()
The salt of this helper is primarily in the fact that it does not serialize all the data, but only those that do not begin with the special character $.
Starting with version
1.3 , it does not serialize only properties whose names begin with $$.
For example: var foo = {bar: 1, baz: 2, $qux: 3};
<1.3: angular.toJson(value);
1.3+: angular.toJson(value);
jqLite
Briefly about the main thing:
- You can no longer set data nodes for text and comments This is due to memory leakage and cleansing hemorrhoids;
- Calling element.detach () now does not trigger the $ destroy event;
Select
Controller
SelectController is now one abstraction for the
Select directive and for the
ngOptions directive.
This means that now
ngOptions can be removed from
Select , without fear that it could somehow affect it.
Different variations of the
Select directive have their own
SelectController.writeValue and
SelectController.readValue methods , which are responsible for working with the
$ viewValue of the <select> tag and its children
<option> .
value for ngOptions
Previously, in
ngOptions, the surrogate key used the index or the item key in the passed collection.
Starting with version
1.4 , this is done using the
hashKey call for the item in the collection.
Accordingly, if you read the
value directly from the
DOM , then problems may arise.
For example: <select ng-model="model" ng-option="i in items"></select>
<1.4: <option value="1">a</option> <option value="2">b</option> <option value="3">c</option> <option value="4">d</option>
1.4+: <option value="string:a">a</option> <option value="string:b">b</option> <option value="string:c">c</option> <option value="string:d">d</option>
Comparing ngModel with option value
Starting with version
1.4 , the
select directive begins to compare the value of
option and
ngModel , using strict comparison.
This means that the value
1 is not equivalent to
"1" as it is not equivalent to the value
false or
true .
If you put the value
1 in the model, you get
unknown option .
To avoid this, you need to put a string in the model, for example,
scope.model = "1" .
If the model needs exactly a number, it is proposed to use conversion via formatters and parsers.
Example: ngModelCtrl.$parsers.push(function(value) { return parseInt(value, 10);
Sorting
As in the case of
ngRepeat , sorting in alphabetical order no longer works, but corresponds in sequence to the call to
Object.keys (obj) .
ngRepeat
Sorting
[commit] [issue] [holy war]Previously, ngRepeat, sorting an object, sorted it alphabetically by keys. Starting with version
1.4 , it returns it in an order dependent on the browser, as if you were sorting it out
for key in obj .
This is due to the fact that browsers usually return object keys in the order in which they were declared,
unless the keys were deleted or reinstalled.
To iterate over an object, it is proposed to use
custom filters that convert the object into an array.
$ compile
controllerAs, bindToController
In version
1.3 bindToController was introduced. Starting with version
1.4 , an object can be passed into it to specify an isolated scope.
In this regard, now the object returned from the controller constructor overwrites the scope.
The views that used the
controllerAs syntax no longer receive a reference to the function itself, but to the object it returns.
If the directive uses
bindToController , then all previous bindings are reinstalled into the new controller, all installed watches are deleted (
unwatch ).
Expression '&' in isolated scope
Previously, a function with an
& was always created, even if the attribute along with an expression was missing (in this case, a function was created that returns
undefined ).
Starting at
1.4 , the behavior
& approached
@ . Now, if the expression is missing, then the corresponding method in $ scope is also missing. When you access it, you get
undefined instead of a function that returns
undefined .
Replace directive property
Starting from
1.3 , it becomes
deprecated and should be removed in the next major release.
This is explained by the fact that there are some problems with merge attributes.
More details:If you combine
<div ng-class="{hasHeader: true}"></div>
WITH
<div ng-class="{active: true}"></div>
That will get
<div ng-class="{active: true}{hasHeader: true}"></div>
With the corresponding error that the expression is not valid.
And an insufficient level of encapsulation of such directives and in general.
Holivar on this topic is available
here .
$ observer
Starting from version
1.3 , we finally got a convenient way to remove the attribute observatory: the destructor function returns when you call attr.observe (as watch does). Previously, he returned a link to the function of the Observer.
Now, in order to have a link to the function of the Observer, you must save it somewhere beforehand.
For example:<1.3: directive('directiveName', function() { return { link: function(scope, elm, attr) { var observer = attr.$observe('someAttr', function(value) { console.log(value); }); } }; });
As it is now: directive('directiveName', function() { return { link: function(scope, elm, attr) { var observer = function(value) { console.log(value); }; var destructor = attr.$observe('someAttr', observer); destructor();
Access to isolated scope from outside
It is no longer possible to get the isolated scope property through the attribute of the element where the isolated directive is defined.
For example:The following directive is given:
app.controller('testController', function($scope) { $scope.controllerScope = true; }); app.directive('testDirective', function() { return { template:'<span ng-if="directiveScope">world!</span>', scope: {directiveScope: '='}, controller: function($scope) {}, replace: true, restrict: 'E' } });
<1.3: Hello <test-directive directive-scope="controllerScope"></test-directive> // Hello
1.3+: Hello <test-directive directive-scope="controllerScope"></test-directive> // Hello world!
ngModelController
$ setViewValue ()
The behavior of
$ setViewValue () has changed a bit, now it does not pass changes to $ modelValue immediately, as before.
Now the model is updated depending on the two settings
ngModelOptions , and in particular:
- updateOn : The model will not be updated until one of the events specified in this trigger is called.
- debounce : The model will not update until the time specified in one of the debounce passes.
By default,
updateOn is
default , and
debounce is
0 , so $ modelValue runs as before, instantly.
However, you should take into account the features described above when working with old code.
$ commitViewValue
If you want to update $ modelValue instantly at all costs, ignoring
updateOn and
debounce , use
$ commitViewValue () .
$ commitViewValue () takes no arguments. Previously, he had an undocumented argument,
revalidate , used by
in private api as a hack for the forced launch of revalidation and related processes, even if
$$ lastCommittedViewValuenot updated, but in recent versions it is removed.
$ cancelUpdate ()
Renamed to
$ rollbackViewValue () .
The call allows you to “roll back”
$ viewValue to the state
$$ lastCommittedViewValue , cancel all
debounce processes that are in progress, and redraw the view (for example, input).
For example:<1.3: $scope.resetWithCancel = function (e) { $scope.myForm.myInput.$cancelUpdate(); $scope.myValue = ''; };
1.3+: $scope.resetWithCancel = function (e) { $scope.myForm.myInput.$rollbackViewValue(); $scope.myValue = ''; };
input: date, time, datetime-local, month, week
Since version
1.3, Angulyar normally supports HTML5 inputs associated with numbers.
In the
ng-model of such inputs there must be a strictly
Date object
In older browsers that do not support these inputs, the user will see the text. In such cases, he will have to enter the correct ISO format for the required date.
Validation
$ Error collection
[commit]Previously, arbitrary properties could be stored in
$ error by controlling the validity of the control manually via
$ setValidity .
Starting with version
1.3 , final validation depends on whether the
$ error hash is empty. Throwing a property into
ngModelCtrl. $ Error manually and not removing it from there manually, you will get permanently invalid control, regardless of the value of this property.
result in $ setValidity
[commit]$ setValidity allows
you to set the validity of certain properties of the control, taking two arguments:
name and
result .
Previously,
result was always cast to
true or
false , regardless of what was passed there.
Starting with version
1.3 ,
$ setValidity begins to distinguish between
false ,
undefined and
null passed to
result . It is necessary now to take care of the fact that the
result is exactly the boolean value.
The values
undefined and
null are used, for example, internally for asynchronous validators. So, if not all synchronous validators are valid, then the asynchronous values ​​will be set to
null . If the synchronous validators are ready and asynchronous validation has begun, then as long as the
pending is going on, the value of the validator will be set to
undefined .
$ parsers and undefined.
[commit]Previously, you could prokidykivat
undefined in the chain
$ parsers if, for example, you want to break it.
Starting with version
1.3 , the parsers no longer handle
undefined and make the control invalid, setting the value
{parse: true} to
$ error .
This is done to prevent parsers from running in cases where
$ viewValue (
not yet installed )
ngPattern
[commit]Starting from
1.4.5 , the
ngPattern directive performs validation based on
$ viewValue (previously based on
$ modelValue ), before the
$ parsers chain
works .
This is related to the
problem when
input [date] and
input [number] are not validated due to the fact that the parsers have converted
$ viewValue to
Date and
Number respectively.
If you use
$ viewValue modifiers with this directive and you need to check
$ modelValue as before, then you should use a custom directive.
For example: .directive('patternModelOverwrite', function patternModelOverwriteDirective() { return { restrict: 'A', require: '?ngModel', priority: 1, compile: function() { var regexp, patternExp; return { pre: function(scope, elm, attr, ctrl) { if (!ctrl) return; attr.$observe('pattern', function(regex) { if (isString(regex) && regex.length > 0) { regex = new RegExp('^' + regex + '$'); } if (regex && !regex.test) {
ngMinlength / ngMaxlength
Starting from
1.3 , the
ngMinlength and
ngMaxlength directives perform validation based on
$ viewValue (previously based on
$ modelValue ).
This can lead to incorrect validation when using these directives along with directives that modify the
$ viewValue , for example, the masks for entering the phone.
To avoid problems, there are two solutions:
- Change the number of maximum characters in accordance with $ viewValue (for example, masks of the form “xx-xx”, if the model contains only “xxxx”, should be taken into account as maxlength = “5” , not 4, as it was before)
- Use your own custom directives that check $ modelValue . However, there may be problems with maxlength , because, according to the specification, it limits the number of characters entered, so you have to implement your limit.
I recommend for most cases to use the first option as the least problematic.
The second option may be useful for minLength. In cases when there is an optional mask with input where n characters are pre-entered (for example, phone with the set "+7" installed), this is due to the fact that minLength does not validate the field only as long as it is empty.
Custom maxlength example (function (angular) { 'use strict'; angular .module('mainModule') .directive('maxModelLength', maxlengthDirective); function maxlengthDirective () { return { restrict: 'A', require: '?ngModel', link: function (scope, elm, attr, ctrl) { if (!ctrl) { return; } var maxlength = -1; attr.$observe('maxModelLength', function (value) { var intVal = parseInt(value); maxlength = isNaN(intVal) ? -1 : intVal; ctrl.$validate(); }); ctrl.$validators.maxlength = function (modelValue, viewValue) { return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (String(modelValue).length <= maxlength); }; elm.bind('keydown keypress', function (event) { var stringModel = String(ctrl.$modelValue); if (maxlength > 0 && !ctrl.$isEmpty(ctrl.$modelValue) && stringModel.length >= maxlength) { if ([8, 37, 38, 39, 40, 46].indexOf(event.keyCode) === -1) { event.preventDefault(); } } }); } }; } })(angular);
Custom minlength example (function (angular) { 'use strict'; angular .module('mainModule') .directive('minModelLength', minlengthDirective); function minlengthDirective () { return { restrict: 'A', require: '?ngModel', link: function (scope, elm, attr, ctrl) { if (!ctrl) { return; } var minlength = 0; attr.$observe('minModelLength', function (value) { minlength = parseInt(value) || 0; ctrl.$validate(); }); ctrl.$validators.minlength = function (modelValue, viewValue) { return ctrl.$isEmpty(modelValue) || String(modelValue).length >= minlength; }; } }; } })(angular);
Virtual ngModel issue:If you use ngMinlength / ngMaxlength on an element that is not intended for direct data entry (for example, at the root of a directive that contains several inputs that work with the root
ngModel ), and use numerical data, you will get incorrect data validation (there will always be an error) .
More specifically, a
number will always be stored in
$ viewValue , which the validator cannot verify, since can't get it
.length .
Why it happens?The
$ compile contains the main directives. For example, for
input and
textarea , the
inputDirective control is
called , which takes
attr.type and, based on it, calls one of the functions in the
inputType collection. For text types, this is, respectively,
textInputType , it, in turn, passes our control to the function
stringBasedInputType , which adds to
$ formatters the code for converting our value to a string.
When
ngModel is not tied to some existing base element like
input , but, for example, hangs on a simple diva or custom directive, the data is thrown into the
$ viewModel “as it is”, without additional conversion to the string, which causes an error in the directives filters like
ngMaxlength .
Based on this, all custom directives that work with numbers must have the appropriate formatter for converting numbers to strings.
Working example on Plunker .
Scopes and Digests
$ id
Now integer compounded.
Previously, due to fears that the numbers might not be enough to count the scope 's, they decided to use $ id for the string (and in fact it is an array of the form [' 0 ',' 0 ',' 0 ']), but the concerns about this the bill didn't come true.Instead, we got some extra load (adds a few milliseconds) when creating a large amount of scope 's (for example, when working with large tables). Going to prime numbers solves this problem.For example:<1.3: [ Plunker] console.log($rootScope.$id);
1.3+: [ Plunker] console.log($rootScope.$id);
broadcast and emit
Now set the currentScope to null as soon as the event reaches the end of the distribution chain.This is due to a hard -to- track bug when using event.currentScope incorrectly , when someone tries to access it from an asynchronous function.Previously, event.currentScope in this case was equal to the last $ scope in the chain, imperceptibly leading to incorrect application operation.Now in a similar case when using event.currentScope there will be an error.For asynchronous access to event.currentScope, you now need to use event.targetScope .For example:scope:
001 ($rootScope) â”” 002 ($scope of ParentCtrl) â”” 003 ($scope of ChildCtrl) â”” 004 ($scope of GrandChildCtrl)
customEvent GrandChildCtrl<1.3: [ Plunker] .controller('ParentCtrl', function($scope, $timeout) { $scope.$on('customEvent', function(event) { console.log(event.currentScope);
1.3+: [ Plunker] .controller('ParentCtrl', function($scope, $timeout) { $scope.$on('customEvent', function(event) { console.log(event.currentScope);
http and resource
JSON primitives
[commit]Starting with version 1.3 , responses with Content-Type: application / json containing primitives start parsing as JSON.In general, this is a bug fix, it allows you to avoid some crutches when working with the answer, but in some cases it may break the existing code.For example:«OK» , , .
<1.3: response === 'OK'
1.3+: response === 'OK'
$ http transformRequest
Starting with version 1.4 , the transformRequest function is no longer supported and does not change the request headers. Instead, use the headers property and the getter functions corresponding to the desired header in the request parameters .The function of the first argument pretends to object the config , which allows to determine and establish the headlines dynamically.For example:'X-MY_HEADER'
<1.4: function requestTransform(data, headers) { headers = angular.extend(headers(), { 'X-MY_HEADER': 'test' }); return angular.toJson(data); }
1.4+: $http.get(url, { headers: { 'X-MY_HEADER': function(config) { return 'test'; } } })
$ http interceptor
The responseInterceptors collection in $ httpProvider already had the status of deprecated and had two different APIs (one of which is not completely obvious), which led to various embarrassments.Starting with version 1.3 , this collection [deleted], as well as its functionality.Instead, a new, transparent API is available for registering interceptors.For example:myHttpInterceptor .
< 1.3: [ Plunker] $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { return function(promise) { return promise.then(function(response) {
1.3+: [ Plunker] $provide.factory('myHttpInterceptor', function($q) { return { response: function(response) {
$ httpBackend and JSONP
Now the angular catches errors in the “success” events, an empty response (missing data in the callback) in JSONP does not lead to an error (it previously generated an error and set the status to -2).It is interesting to know (or not) that:onload onerror JSONP script .
jQuery event .
, , .
$.data(«events») , jqLite .
IE8: The onreadystatechanged event is no longer supported .$ resource
If you call toJson () on the $ resource instance , it will contain the $ promise and $ resolved properties that were previously cut during serialization, as well as all properties starting with a single $ .According to the toJson () change described above , properties starting with $ are no longer serialized. Now only those properties that start with $$ are serialized .Based on this, it can be expected that the serialized $ resource will contain these properties, but this is not the case. Specifically, he cuts these two properties during the serialization itself.All other properties, including those added by the user, will be serialized and will be contained in the final json .
$ inject
Modules: .config () and .provider ()
Previously, it was possible to call .config () before .provider () worked.Since version 1.3 , this behavior is impossible, .config () will always be called only after all the .provider () modules have worked.For example: app .provider('$rootProvider1', function() { console.log('Provider 1'); this.$get = function() {}; }) .config(function() { console.log('Config'); }) .provider('$rootProvider2', function() { console.log('Provider 2'); this.$get = function() {}; });
<1.3: [ Plunker]Provider 1, Config, Provider 21.3+: [ Plunker]Provider 1, Provider 2, Config
ngAnimate
All methods
Previously, for all $ animate methods, the last argument was a done callback , which was executed at the end of the animation.Starting with version 1.3 , a set of options styles is passed there , which is applied to the element.Instead of done, all functions now return a promise whose resolve means the completion of the animation.animate.enter () and animate.move ()
These methods have four arguments (element, parent, after, options).Previously, if the after argument was not specified, then a new element was added after the specified element , and if it was specified, after after .The problem is that with a similar API it is impossible to add a new element to the beginning of the parent container .Starting with version 1.3 , if the after argument is not specified, then this element is added to the beginning of the parent container .Accordingly, it is now necessary to always indicate after which particular element you want to insert a new one.For example:$animate.enter<1.3:
1.3+:
Filters
Internal context
[commit]Previously, all filters had an undocumented feature: the internal context of their control this referred to $ scope in which this filter was invoked.Why you can not do this, and how to do it correctly:, ,
$scope . ,
ng-repeat .
Andy Joslin's :
yourModule.filter("as", function($parse) { return function(value, path) { return $parse(path).assign(this, value); }; });
1.3 ,
$scope (
this )
undefined .
$scope , ** **,
$scope 10%,
$scope $digestError: 10 $digest() iterations reached. Aborting! .
:ng-repeat:item in (filterResults = (items | filter:query)) , ,
as :
item in items | filter:query as filterResults) .
filter
Now it works only with arrays.With version 1.4, an attempt to call a filter on an object will result in an error. Previously, it simply "quietly" returned an empty array.To iterate over an object, it is proposed to use custom filters that convert the object into an array.limitTo
,
limitTo (,
undefined ), .
1.4 , , .. .
ngCookies
$cookies
1.4 ,
$cookies , /, API :
- get
- put
- getObject
- putObject
- getAll
- remove
This is due to data synchronization bugs, when there are no longer actual data in the object. Which means that you can no longer use votchery, tracking changes Cookies through an object.Such manipulations were necessary in the past, for example, for communication between browser tabs, but these days there are more convenient tools, such as localStorage .$ cookieStore
Starting from version 1.4 , the $ cookieStore service received the status of deprecated , all useful logic was transferred to the $ cookies service , and the $ cookieStore request now returns the instance of the $ cookies service .
Conclusion:
There will be no problems with the transition unless the project uses an excessive number of crutches of non-trivial solutions based on bugs and undocumented features of the framework.It will be completely painless for those who have not used the means of an angular to animate and validate the application.In the next article I will talk about all the benefits of new versions and how to improve performance with their help.If you have met with any other interesting problems during the transition, please share them in the comments.