@ngdoc overview @name Migrating from Previous Versions @sortOrder 550 @description # Migrating an App to a newer version Minor version releases in AngularJS introduce several breaking changes that may require changes to your application's source code; for instance from 1.0 to 1.2 and from 1.2 to 1.3. Although we try to avoid breaking changes, there are some cases where it is unavoidable: * AngularJS has undergone thorough security reviews to make applications safer by default, which drives many of these changes. * Several new features, especially animations, would not be possible without a few changes. * Finally, some outstanding bugs were best fixed by changing an existing API. ## Migrating from 1.6 to 1.7 AngularJS 1.7 contains bug fixes and features to AngularJS core and its external modules, some of which contain breaking changes. However, most of these address internal behavior and not APIs, and should not affect many applications. Additionally, we have removed some long-deprecated modules and APIs. The most notable changes are: - `$resource` has now support for request and requestError interceptors - Several deprecated features have been removed: - the `$controllerProvider.allowGlobals()` flag - the `$compileProvider.preAssignBindingsEnabled()` flag - the `angular.lowercase` and `angular.uppercase` methods - the `$cookieStore` service from the `ngCookies` module - the `ngClick` override directive and corresponding services from the `ngTouch` module - the complete `ngScenario` module Please note that feature development (without breaking changes) has happened in parallel on the 1.6.x branch, so 1.7 doesn't contain many new features, but you may still benefit from those features that were added (with possible BCs), bugfixes, and a few smaller performance improvements.
Below is the full list of breaking changes:
### Core: _Directives_ #### **form** **Due to [223de5](https://github.com/angular/angular.js/commit/223de59e988dc0cc8b4ec3a045b7c0735eba1c77)**, forms will now set `$submitted` on child forms when they are submitted. For example: ```
``` Submitting this form will set `$submitted` on "parentform" and "childform". Previously, it was only set on "parentform". This change was introduced because mixing `form` and `ngForm` does not create logically separate forms, but rather something like input groups. Therefore, child forms should inherit the submission state from their parent form. #### **input[radio]** and **input[checkbox]** **Due to [656c8f](https://github.com/angular/angular.js/commit/656c8fa8f23b1277cc5c214c4d0237f3393afa1e)**, `input[radio]` and `input[checkbox]` now listen to the "change" event instead of the "click" event. Most apps should not be affected, as "change" is automatically fired by browsers after "click" happens. Two scenarios might need migration: - Custom click events: Before this change, custom click event listeners on radio / checkbox would be called after the input element and `ngModel` had been updated, unless they were specifically registered before the built-in click handlers. After this change, they are called before the input is updated, and can call `event.preventDefault()` to prevent the input from updating. If an app uses a click event listener that expects `ngModel` to be updated when it is called, it now needs to register a change event listener instead. - Triggering click events: Conventional trigger functions: The change event might not be fired when the input element is not attached to the document. This can happen in **tests** that compile input elements and trigger click events on them. Depending on the browser (Chrome and Safari) and the trigger method, the change event will not be fired when the input isn't attached to the document. Before: ```js it('should update the model', inject(function($compile, $rootScope) { var inputElm = $compile('')($rootScope); inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger() expect($rootScope.checkbox).toBe(true); }); ``` With this patch, `$rootScope.checkbox` might not be true, because the click event hasn't triggered the change event. To make the test, work append `inputElm` to the app's `$rootElement`, and the `$rootElement` to the `$document`. After: ```js it('should update the model', inject(function($compile, $rootScope, $rootElement, $document) { var inputElm = $compile('')($rootScope); $rootElement.append(inputElm); $document.append($rootElement); inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger() expect($rootScope.checkbox).toBe(true); }); ``` #### **input\[number\]** **Due to [aa3f95](https://github.com/angular/angular.js/commit/aa3f951330ec7b10b43ea884d9b5754e296770ec)**, `input[type=number]` with `ngModel` now validates the input for the `max`/`min` restriction against the `ngModelController.$viewValue` instead of against the `ngModelController.$modelValue`. This affects apps that use `$parsers` or `$formatters` to transform the input / model value. If you rely on the `$modelValue` validation, you can overwrite the `min`/`max` validator from a custom directive, as seen in the following example directive definition object: ```js { restrict: 'A', require: 'ngModel', link: function(scope, element, attrs, ctrl) { var maxValidator = ctrl.$validators.max; ctrk.$validators.max = function(modelValue, viewValue) { return maxValidator(modelValue, modelValue); }; } } ``` #### **ngModel, input** **Due to [74b04c](https://github.com/angular/angular.js/commit/74b04c9403af4fc7df5b6420f22c9f45a3e84140)**, *Custom* parsers that fail to parse on input types "email", "url", "number", "date", "month", "time", "datetime-local", "week", no longer set `ngModelController.$error[inputType]`, and the `ng-invalid-[inputType]` class. Also, custom parsers on input type "range" no longer set `ngModelController.$error.number` and the `ng-invalid-number` class. Instead, any custom parsers on these inputs set `ngModelController.$error.parse` and `ng-invalid-parse`. This change was made to make distinguishing errors from built-in parsers and custom parsers easier. #### **ngModelOptions** **Due to [55ba44](https://github.com/angular/angular.js/commit/55ba44913e02650b56410aa9ab5eeea5d3492b68)**, the 'default' key in 'debounce' now only debounces the default event, i.e. the event that is added as an update trigger by the different input directives automatically. Previously, it also applied to other update triggers defined in 'updateOn' that did not have a corresponding key in the 'debounce'. This behavior is now supported via a special wildcard / catch-all key: '*'. See the following example: Pre-1.7: 'mouseup' is also debounced by 500 milliseconds because 'default' is applied: ```html ng-model-options="{ updateOn: 'default blur mouseup', debounce: { 'default': 500, 'blur': 0 } }" ``` 1.7: The pre-1.7 behavior can be re-created by setting '*' as a catch-all debounce value: ```html ng-model-options="{ updateOn: 'default blur mouseup', debounce: { '*': 500, 'blur': 0 } }" ``` In contrast, when only 'default' is used, 'blur' and 'mouseup' are not debounced: ```html ng-model-options="{ updateOn: 'default blur mouseup', debounce: { 'default': 500 } } ``` #### **ngStyle** **Due to [15bbd3](https://github.com/angular/angular.js/commit/15bbd3e18cd89b91f7206a06c73d40e54a8a48a0)**, the use of deep-watching in `ngStyle` has changed. Previously, `ngStyle` would trigger styles to be re-applied whenever nested state changed. Now, only changes to direct properties of the watched object will trigger changes. ### Core: _Services_ #### **$compile** **Due to [38f8c9](https://github.com/angular/angular.js/commit/38f8c97af74649ce224b6dd45f433cc665acfbfb)**, directive bindings are no longer available in the constructor. Previously, the `$compileProvider.preAssignBindingsEnabled` flag was supported. The flag controlled whether bindings were available inside the controller constructor or only in the `$onInit` hook. The bindings are now no longer available in the constructor. To migrate your code: 1. If you haven't invoked `$compileProvider.preAssignBindingsEnabled()` you don't have to do anything to migrate. 2. If you specified `$compileProvider.preAssignBindingsEnabled(false)`, you can remove that statement - since AngularJS 1.6.0 this is the default so your app should still work even in AngularJS 1.6 after such removal. Afterwards, migrating to AngularJS 1.7.0 shouldn't require any further action. 3. If you specified `$compileProvider.preAssignBindingsEnabled(true)` you need to first migrate your code so that the flag can be flipped to `false`. The instructions on how to do that are available in the "Migrating from 1.5 to 1.6" guide: https://docs.angularjs.org/guide/migration#migrating-from-1-5-to-1-6 Afterwards, remove the `$compileProvider.preAssignBindingsEnabled(true)` statement.
**Due to [6ccbfa](https://github.com/angular/angular.js/commit/6ccbfa65d60a3dc396d0cf6da21b993ad74653fd)**, the `xlink:href` security context for SVG's `a` and `image` elements has been lowered. In the unlikely case that an app relied on `RESOURCE_URL` whitelisting for the purpose of binding to the `xlink:href` property of SVG's `` or `` elements and if the values do not pass the regular URL sanitization, they will break. To fix this you need to ensure that the values used for binding to the affected `xlink:href` contexts are considered safe URLs, e.g. by whitelisting them in `$compileProvider`'s `aHrefSanitizationWhitelist` (for `` elements) or `imgSrcSanitizationWhitelist` (for `` elements).
**Due to [fd4f01](https://github.com/angular/angular.js/commit/fd4f0111188b62773b99ab6eab38b4d2b5d8d727)**, deep-watching is no longer used in literal one-way bindings. Previously, when a literal value was passed into a directive/component via one-way binding it would be watched with a deep watcher. For example, for ``, a new instance of the array would be passed into the directive/component (and trigger `$onChanges`) not only if `a` changed but also if any sub property of `a` changed such as `a.b` or `a.b.c.d.e` etc. This also means a new but equal value for `a` would NOT trigger such a change. Now, literal values use an input-based watch similar to other directive/component one-way bindings. In this context inputs are the non-constant parts of the literal. In the example above, the input would be `a`. Changes are only triggered, when the inputs to the literal change.
**Due to [1cf728](https://github.com/angular/angular.js/commit/1cf728e209a9e0016068fac2769827e8f747760e)**, `base[href]` was added to the list of `RESOURCE_URL` context attributes. Previously, `` would not require `baseUrl` to be trusted as a `RESOURCE_URL`. Now, `baseUrl` will be sent to `$sce`'s `RESOURCE_URL` checks. By default, it will break unless `baseUrl` is of the same origin as the application document. Refer to the [`$sce` API docs](https://code.angularjs.org/snapshot/docs/api/ng/service/$sce) for more info on how to trust a value in a `RESOURCE_URL` context. Also, concatenation in trusted contexts is not allowed, which means that the following won't work: ``. Either construct complex values in a controller (recommended): ```js this.baseUrl = '/something/' + this.partialPath; ``` ```html ``` Or use string concatenation in the interpolation expression (not recommended except for the simplest of cases): ```html ``` #### **$rootScope** **Due to ([c2b8fa](https://github.com/angular/angular.js/commit/c2b8fab0a480204374d561d6b9b3d47347ac5570))**, the arguments of `$watchGroup` callbacks have changed. Previously, when using `$watchGroup`, the entries in `newValues` and `oldValues` represented the *most recent change of each entry*. Now, the entries in `oldValues` will always equal the `newValues` of the previous call of the listener. This means comparing the entries in `newValues` and `oldValues` can be used to determine which individual expressions changed. For example `$scope.$watchGroup(['a', 'b'], fn)` would previously: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `a=2` | [2, undefined] | [1, undefined] | | `b=3` | [2, 3] | [1, undefined] | Now the `oldValue` will always equal the previous `newValue`: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `a=2` | [2, undefined] | [1, undefined] | | `b=3` | [2, 3] | [2, undefined] | Note the last call now shows `a === 2` in the `oldValues` array. This also makes the `oldValue` of one-time watchers more clear. Previously, the `oldValue` of a one-time watcher would remain `undefined` forever. For example `$scope.$watchGroup(['a', '::b'], fn)` would previously: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `b=2` | [1, 2] | [undefined, undefined] | | `a=b=3` | [3, 2] | [1, undefined] | Where now the `oldValue` will always equal the previous `newValue`: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `b=2` | [1, 2] | [1, undefined] | | `a=b=3` | [3, 2] | [1, 2] | #### **$interval** **Due to [a8bef9](https://github.com/angular/angular.js/commit/a8bef95127775d83d80daa4617c33227c4b443d4)**, `$interval.cancel()` will throw an error if called with a promise that was not generated by `$interval()`. Previously, it would silently do nothing. Before: ```js var promise = $interval(doSomething, 1000, 5).then(doSomethingElse); $interval.cancel(promise); // No error; interval NOT canceled. ``` After: ```js var promise = $interval(doSomething, 1000, 5).then(doSomethingElse); $interval.cancel(promise); // Throws error. ``` Correct usage: ```js var promise = $interval(doSomething, 1000, 5); var newPromise = promise.then(doSomethingElse); $interval.cancel(promise); // Interval canceled. ``` #### **$timeout** **Due to [336525](https://github.com/angular/angular.js/commit/3365256502344970f86355d3ace1cb4251ae9828)**, `$timeout.cancel()` will throw an error if called with a promise that was not generated by `$timeout()`. Previously, it would silently do nothing. Before: ```js var promise = $timeout(doSomething, 1000).then(doSomethingElse); $timeout.cancel(promise); // No error; timeout NOT canceled. ``` After: ```js var promise = $timeout(doSomething, 1000).then(doSomethingElse); $timeout.cancel(promise); // Throws error. ``` Correct usage: ```js var promise = $timeout(doSomething, 1000); var newPromise = promise.then(doSomethingElse); $timeout.cancel(promise); // Timeout canceled. ``` #### **$cookies** **Due to [73c646](https://github.com/angular/angular.js/commit/73c6467f1468353215dc689c019ed83aa4993c77)**, the `$cookieStore`service has been removed. Migrate to the `$cookies` service. Note that for object values you need to use the `putObject` & `getObject` methods, as `get`/`put` will not correctly save/retrieve the object values. Before: ```js $cookieStore.put('name', {key: 'value'}); $cookieStore.get('name'); // {key: 'value'} $cookieStore.remove('name'); ``` #### **$templateRequest** **Due to [c617d6](https://github.com/angular/angular.js/commit/c617d6dceee5b000bfceda44ced22fc16b48b18b)**, the `tpload` error namespace has changed. Previously, the `tpload` error was namespaced to `$compile`. If you have code that matches errors of the form `[$compile:tpload]` it will no longer run. You should change the code to match `[$templateRequest:tpload]`.
**Due to ([fb0099](https://github.com/angular/angular.js/commit/fb00991460cf69ae8bc7f1f826363d09c73c0d5e)**, `$templateRequest()` now returns the result of `$templateCache.put()` when making a server request for a template. Previously, it would return the content of the response directly. This means that if you are decorating `$templateCache.put()` to manipulate the template, you will now get this manipulated result also on the first `$templateRequest()` call rather than only on subsequent calls (when the template is retrieved from the cache). In practice, this should not affect any apps, as it is unlikely that they rely on the template being different in the first and subsequent calls. #### **$animate** **Due to [16b82c](https://github.com/angular/angular.js/commit/16b82c6afe0ab916fef1d6ca78053b00bf5ada83)**, `$animate.cancel(runner)` now rejects the underlying promise and calls the `catch()` handler on the runner returned by `$animate` functions (`enter`, `leave`, `move`, `addClass`, `removeClass`, `setClass`, `animate`). Previously, it would resolve the promise as if the animation had ended successfully. Example: ```js var runner = $animate.addClass('red'); runner.then(function() { console.log('success')}); runner.catch(function() { console.log('cancelled')}); runner.cancel(); ``` Pre-1.7.0, this logs 'success', 1.7.0 and later it logs 'cancelled'. To migrate, add a `catch()` handler to your animation runners. #### **$controller** **Due to [e269c1](https://github.com/angular/angular.js/commit/e269c14425a3209040f65c022658770e00a36f16)**, the option to instantiate controllers from constructors on the global `window` object has been removed. Likewise, the deprecated `$controllerProvider.allowGlobals()` method that could enable this behavior, has been removed. This behavior had been deprecated since AngularJS v1.3.0, because polluting the global scope is considered bad practice. To migrate, remove the call to `$controllerProvider.allowGlobals()` in the config, and register your controller via the Module API or the `$controllerProvider`, e.g.: ```js angular.module('myModule', []).controller('myController', function() {...}); // or angular.module('myModule', []).config(function($controllerProvider) { $controllerProvider.register('myController', function() {...}); }); ``` #### **$sce** **Due to [1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**, if you use `attrs.$set` for URL attributes (`a[href]` and `img[src]`) there will no longer be any automated sanitization of the value. This is in line with other programmatic operations, such as writing to the `innerHTML` of an element. If you are programmatically writing URL values to attributes from untrusted input, then you must sanitize it yourself. You could write your own sanitizer or copy the private `$$sanitizeUri` service. Note that values that have been passed through the `$interpolate` service within the `URL` or `MEDIA_URL` will have already been sanitized, so you would not need to sanitize these values again.
**Due to [1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**, binding {@link ng.$sce#trustAs trustAs()} and the short versions ({@link ng.$sce#trustAsResourceUrl trustAsResourceUrl()} et al.) to {@link ng.ngSrc}, {@link ng.ngSrcset}, and {@link ng.ngHref} will now raise an infinite digest error: ```js $scope.imgThumbFn = function(id) { return $sce.trustAsResourceUrl(someService.someUrl(id)); }; ``` ```html ``` This is because {@link ng.$interpolate} is now responsible for sanitizing the attribute value, and its watcher receives a new object from `trustAs()` on every digest. To migrate, compute the trusted value only when the input value changes: ```js $scope.$watch('imgId', function(id) { $scope.imgThumb = $sce.trustAsResourceUrl(someService.someUrl(id)); }); ``` ```html ```
### Core: _Filters_ #### **orderBy** **Due to [1d8046](https://github.com/angular/angular.js/commit/1d804645f7656d592c90216a0355b4948807f6b8)**, when using `orderBy` to sort arrays containing `null` values, the `null` values will be considered "greater than" all other values, except for `undefined`. Previously, they were sorted as strings. This will result in different (but more intuitive) sorting order. Before: ```js orderByFilter(['a', undefined, 'o', null, 'z']); //--> 'a', null, 'o', 'z', undefined ``` After: ```js orderByFilter(['a', undefined, 'o', null, 'z']); //--> 'a', 'o', 'z', null, undefined ``` ### Core: _Misceallenous_ #### **jqLite** **Due to [b7d396](https://github.com/angular/angular.js/commit/b7d396b8b6e8f27a1f4556d58fc903321e8d532a)**, `removeData()` no longer removes event handlers. Before this commit `removeData()` invoked on an element removed its event handlers as well. If you want to trigger a full cleanup of an element, change: ```js elem.removeData(); ``` to: ```js angular.element.cleanData(elem); ``` In most cases, though, cleaning up after an element is supposed to be done only when it's removed from the DOM as well; in such cases the following: ```js elem.remove(); ``` will remove event handlers as well. #### **Helpers** **Due to [1daa4f](https://github.com/angular/angular.js/commit/1daa4f2231a89ee88345689f001805ffffa9e7de)**, the helper functions `angular.lowercase` and `angular.uppercase` have been removed. These functions have been deprecated since 1.5.0. They are internally used, but should not be exposed as they contain special locale handling (for Turkish) to maintain internal consistency regardless of user-set locale. Developers should generally use the built-in methods `toLowerCase` and `toUpperCase` or `toLocaleLowerCase` and `toLocaleUpperCase` for special cases.
**Due to [e3ece2](https://github.com/angular/angular.js/commit/e3ece2fad9e1e6d47b5f06815ff186d7e6f44948)**, `angular.isArray()` now supports Array subclasses. Previously, `angular.isArray()` was an alias for `Array.isArray()`. Therefore, objects that prototypally inherit from `Array` where not considered arrays. Now such objects are considered arrays too. This change affects several other methods that use `angular.isArray()` under the hood, such as `angular.copy()`, `angular.equals()`, `angular.forEach()`, and `angular.merge()`. This in turn affects how dirty checking treats objects that prototypally inherit from `Array` (e.g. MobX observable arrays). AngularJS will now be able to handle these objects better when copying or watching. ### ngAria **Due to [6d5ef3](https://github.com/angular/angular.js/commit/6d5ef34fc6a974cde73157ba94f9706723dd8f5b)**, `ngAria` no longer sets `aria-*` attributes on `input[type="hidden"]` with `ngModel`. This can affect apps that test for the presence of ARIA attributes on hidden inputs. To migrate, remove these assertions. In actual apps, this should not have a user-facing effect, as the previous behavior was incorrect, and the new behavior is correct for accessibility. ### ngResource #### **$resource** **Due to [ea0585](https://github.com/angular/angular.js/commit/ea0585773bb93fd891576e2271254a17e15f1ddd)**, the behavior of interceptors and success/error callbacks has changed. If you are not using `success` or `error` callbacks with `$resource`, your app should not be affected by this change. If you are using `success` or `error` callbacks (with or without response interceptors), one (subtle) difference is that throwing an error inside the callbacks will not propagate to the returned `$promise`. Therefore, you should try to use the promises whenever possible. E.g.: ```js // Avoid User.query(function onSuccess(users) { throw new Error(); }). $promise. catch(function onError() { /* Will not be called. */ }); // Prefer User.query(). $promise. then(function onSuccess(users) { throw new Error(); }). catch(function onError() { /* Will be called. */ }); ``` Finally, if you are using `success` or `error` callbacks with response interceptors, the callbacks will now always run _after_ the interceptors (and wait for them to resolve in case they return a promise). Previously, the `error` callback was called before the `responseError` interceptor and the `success` callback was synchronously called after the `response` interceptor. E.g.: ```js var User = $resource('/api/users/:id', {id: '@id'}, { get: { method: 'get', interceptor: { response: function(response) { console.log('responseInterceptor-1'); return $timeout(1000).then(function() { console.log('responseInterceptor-2'); return response.resource; }); }, responseError: function(response) { console.log('responseErrorInterceptor-1'); return $timeout(1000).then(function() { console.log('responseErrorInterceptor-2'); return $q.reject('Ooops!'); }); } } } }); var onSuccess = function(value) { console.log('successCallback', value); }; var onError = function(error) { console.log('errorCallback', error); }; // Assuming the following call is successful... User.get({id: 1}, onSuccess, onError); // Old behavior: // responseInterceptor-1 // successCallback, {/* Promise object */} // responseInterceptor-2 // New behavior: // responseInterceptor-1 // responseInterceptor-2 // successCallback, {/* User object */} // Assuming the following call returns an error... User.get({id: 2}, onSuccess, onError); // Old behavior: // errorCallback, {/* Response object */} // responseErrorInterceptor-1 // responseErrorInterceptor-2 // New behavior: // responseErrorInterceptor-1 // responseErrorInterceptor-2 // errorCallback, Ooops! ```
**Due to [240a3d](https://github.com/angular/angular.js/commit/240a3ddbf12a9bb79754031be95dae4b6bd2dded)**, `$http` will be called asynchronously from `$resource` methods (regardless if a `request`/`requestError` interceptor has been defined). Previously, calling a `$resource` method would synchronously call `$http`. This is not expected to affect applications at runtime, since the overall operation is asynchronous already, but may affect assertions in tests. For example, if you want to assert that `$http` has been called with specific arguments as a result of a `$resource` call, you now need to run a `$digest` first, to ensure the (possibly empty) request interceptor promise has been resolved. Before: ```js it('...', function() { $httpBackend.expectGET('/api/things').respond(...); var Things = $resource('/api/things'); Things.query(); expect($http).toHaveBeenCalledWith(...); }); ``` After: ```js it('...', function() { $httpBackend.expectGET('/api/things').respond(...); var Things = $resource('/api/things'); Things.query(); $rootScope.$digest(); expect($http).toHaveBeenCalledWith(...); }); ``` ### ngScenario **Due to[0cd392](https://github.com/angular/angular.js/commit/0cd39217828b0ad53eaf731576af17d66c18ff60)**, the angular scenario runner end-to-end test framework has been removed from the project and will no longer be available on npm or bower starting with 1.7.0. It has been deprecated and removed from the documentation since 2014. Applications that still use it should migrate to [Protractor](http://www.protractortest.org). Technically, it should also be possible to continue using an older version of the scenario runner, as the underlying APIs have not changed. However, we do not guarantee future compatibility. ### ngTouch **Due to [11d9ad](https://github.com/angular/angular.js/commit/11d9ad1eb25eaf5967195e424108207427835d50)**, the `ngClick` directive of the `ngTouch` module has been removed, and with it the corresponding `$touchProvider` and `$touch` service. If you have included `ngTouch` v1.5.0 or higher in your application, and have not changed the value of `$touchProvider.ngClickOverrideEnabled()`, or injected and used the `$touch` service, then there are no migration steps for your code. Otherwise you must remove references to the provider and service. The `ngClick` override directive had been deprecated and by default disabled since v1.5.0, because of buggy behavior in edge cases, and a general trend to avoid special touch based overrides of click events. In modern browsers, it should not be necessary to use a touch override library: - Chrome, Firefox, Edge, and Safari remove the 300ms delay when `` is set. - Internet Explorer 10+, Edge, Safari, and Chrome remove the delay on elements that have the `touch-action` css property is set to `manipulation`. You can find out more in these articles: https://developers.google.com/web/updates/2013/12/300ms-tap-delay-gone-away https://developer.apple.com/library/content/releasenotes/General/WhatsNewInSafari/Articles/Safari_9_1.html#//apple_ref/doc/uid/TP40014305-CH10-SW8 https://blogs.msdn.microsoft.com/ie/2015/02/24/pointer-events-w3c-recommendation-interoperable-touch-and-removing-the-dreaded-300ms-tap-delay/ ## Migrating from 1.5 to 1.6 AngularJS 1.6 fixes numerous bugs and adds new features, both in core and in external modules. In addition, it includes several security and performance improvements in commonly used services, such as `$compile`, `$injector`, `$parse`, `$animate`, and directives, such as `input`, `ngModel` and `select`. The most notable changes are: - Aligning jqLite with the latest version of jQuery (3.x). - Implementing long awaited features, such as support for inputs of type `range` and the ability to bind to any type of values using `ngRepeat` with `select`. - Disabling (by default) the pre-assignment of bindings on controller instances, which helps with support for native ES6 classes. - Changing the default `$location` hash-prefix to `'!'`, as the previous empty string default was unconventional and confusing. - Reporting possibly unhandled promise rejections that would otherwise go unnoticed. Another major change is the removal of the **Expression Sandbox**. This should not require changes to your application (and may give it a small performance boost), but we strongly recommend reading the [Sandbox Removal Blog Post](http://angularjs.blogspot.com/2016/09/angular-16-expression-sandbox-removal.html) to understand the implications behind the removal and whether any action is required on your part.
You may also notice that this release comes with a longer-than-usual list of breaking changes. Don't let this dishearten you though, since most of them are pretty minor - often not expected to affect real applications. These breaking changes were necessary in order to: - Align with breaking changes in jQuery 3. - Fix bugs that we wouldn't be able to fix otherwise. - Introduce new features, performance improvements and security fixes. - Make the behavior of existing features more consistent and predictable.
To give you a heads-up, here is a brief summary of the breaking changes that are expected to have the highest impact. Make sure you look them up in the full list below or check out the corresponding commits for more info. - **$location** now uses `'!'` as the default hash-prefix for hash-bang URLs, instead of the empty string. ([Details](guide/migration#commit-aa077e8)) - **$compile** will (by default) not pre-assign bindings on component/directive controller instances. ([Details](guide/migration#commit-bcd0d4)) - **http** imposes additional restrictions to **JSONP** requests for security reasons (see [details](guide/migration#migrate1.5to1.6-ng-services-$http) below): - The request URL now needs to be trusted as a resource URL. - You can no longer use the `JSON_CALLBACK` placeholder for specifying the query parameter for the callback. - **jqLite** is more aligned to jQuery 3, which required the following changes (see [details](guide/migration#migrate1.5to1.6-ng-misc-jqLite) below): - Keys passed to `.data()` and `.css()` are now camelCased in the same way as the jQuery methods do. - Getting/setting boolean attributes no longer takes the corresponding properties into account. - Setting boolean attributes to empty string no longer removes the attribute. - Calling `.val()` on a multiple select will always return an array, even if no option is selected. - **input[type=radio]** now uses strict comparison (`===`) to determine its "checked" status. ([Details](guide/migration#commit-5ac7da)) - The improved support for **input[type=range]** means that the behaviour of range inputs (when bound to `ngModel`) has changed. ([Details](guide/migration#commit-913016)) - **ngTransclude** now treats whitespace-only transclusion content as empty and uses the fallback content instead. ([Details](guide/migration#commit-32aa7e)) - **ngAria/ngModel** no longer overrides the default `$inEmpty()` method for custom `checkbox`-shaped controls. ([Details](guide/migration#commit-975a61))
Below is the full list of breaking changes: - Core: - [Directives](guide/migration#migrate1.5to1.6-ng-directives) - [form](guide/migration#migrate1.5to1.6-ng-directives-form) - [input[number]](guide/migration#migrate1.5to1.6-ng-directives-input[number]) - [input[radio]](guide/migration#migrate1.5to1.6-ng-directives-input[radio]) - [input[range]](guide/migration#migrate1.5to1.6-ng-directives-input[range]) - [ngBind](guide/migration#migrate1.5to1.6-ng-directives-ngBind) - [ngModel](guide/migration#migrate1.5to1.6-ng-directives-ngModel) - [ngModelOptions](guide/migration#migrate1.5to1.6-ng-directives-ngModelOptions) - [ngTransclude](guide/migration#migrate1.5to1.6-ng-directives-ngTransclude) - [select](guide/migration#migrate1.5to1.6-ng-directives-select) - [Services](guide/migration#migrate1.5to1.6-ng-services) - [$compile](guide/migration#migrate1.5to1.6-ng-services-$compile) - [$http](guide/migration#migrate1.5to1.6-ng-services-$http) - [$interpolate](guide/migration#migrate1.5to1.6-ng-services-$interpolate) - [$location](guide/migration#migrate1.5to1.6-ng-services-$location) - [$q](guide/migration#migrate1.5to1.6-ng-services-$q) - [Miscellaneous](guide/migration#migrate1.5to1.6-ng-misc) - [jqLite](guide/migration#migrate1.5to1.6-ng-misc-jqLite) - [decorator()](guide/migration#migrate1.5to1.6-ng-misc-decorator) - Modules: - [ngAria](guide/migration#migrate1.5to1.6-ngAria) - [$aria](guide/migration#migrate1.5to1.6-ngAria-$aria) - [ngClick](guide/migration#migrate1.5to1.6-ngAria-ngClick) - [ngModel](guide/migration#migrate1.5to1.6-ngAria-ngModel) - [ngMock](guide/migration#migrate1.5to1.6-ngMock) - [$httpBackend](guide/migration#migrate1.5to1.6-ngMock-$httpBackend) - [ngResource](guide/migration#migrate1.5to1.6-ngResource) - [$resource](guide/migration#migrate1.5to1.6-ngResource-$resource) - [ngRoute](guide/migration#migrate1.5to1.6-ngRoute) - [$route](guide/migration#migrate1.5to1.6-ngRoute-$route)
### Core: _Directives_ #### **form**: **Due to [9e24e7](https://github.com/angular/angular.js/commit/9e24e774a558143b3478536911a3a4c1714564ba)**, `FormController` now defines its methods on its prototype, instead of on each instance. As a consequence, `FormController` methods always need to be called in the correct context. For example `$scope.$watch('something', myFormCtrl.$setDirty)` will no longer work, because the `$setDirty` method is passed without any context. The code must now be changed to: ```js $scope.$watch('something', function() { myFormCtrl.$setDirty(); }) ``` or you can use `Function.prototype.bind` or `angular.bind`. #### **input[type=number]**: **Due to [e1da4be](https://github.com/angular/angular.js/commit/e1da4bed8e291003d485a8ad346ab80bed8ae2e3)**, number inputs that use `ngModel` and specify a `step` constraint (via `step`/`ngStep` attributes) will now have a new validator (`step`), which will verify that the current value is valid under the `step` constraint (according to the [spec](https://www.w3.org/TR/html5/forms.html#the-step-attribute)). Previously, the `step` constraint was ignored by `ngModel`, treating values as valid even when there was a step-mismatch. If you want to restore the previous behavior (use the `step` attribute while disabling step validation), you can overwrite the built-in `step` validator with a custom directive. For example: ```js // For all `input` elements... .directive('input', function() { return { restrict: 'E', require: '?ngModel', link: function (scope, elem, attrs, ngModelCtrl) { // ...that are of type "number" and have `ngModel`... if ((attrs.type === 'number') && ngModelCtrl) { // ...remove the `step` validator. delete ngModelCtrl.$validators.step; } } }; }) ``` #### **input[type=radio]**: **Due to [5ac7da](https://github.com/angular/angular.js/commit/5ac7daea72ec31cf337d1d21b13f0d17ff33994f)**, the "checked" status of radio inputs is now determined by doing a strict comparison (`===`) between the value of the input and the `ngModelController.$viewValue`. Previously, this was a non-strict comparison (`==`). This means in the following examples the radio is no longer checked: ```html ``` If your code relied on the non-strict comparison, you need to convert the values so that they continue to match with strict comparison. #### **input[type=range]**: **Due to [913016](https://github.com/angular/angular.js/commit/9130166767c4792c5d32d08a918fc7becf32c9a6)** and the built-in support for range inputs, the behavior of such elements when bound to `ngModel` will be different than before: - Like `input[type=number]`, it requires the model to be a Number, and will set the model to a Number. - It supports setting the min/max values only via the min/max attributes. - It follows the browser behavior of never allowing an invalid value. That means, when the browser converts an invalid value (empty: `null`, `undefined`, `false` ..., out of bounds: greater than max, less than min) to a valid value, the input will in turn set the model to this new valid value via `$setViewValue`. - This means a range input will never have the required validation error and never have a non-Number model value, once the `ngModel` directive is initialized. - This behavior is supported when the model changes and when the min/max attributes change in a way that prompts the browser to update the input value. - Browsers that do not support `input[type=range]` (IE9) handle the input like a number input (with validation etc). #### **ngBind**: **Due to [fa80a6](https://github.com/angular/angular.js/commit/fa80a61a05a3b49a2c770d5544cb8480907a18d3)**, `ngBind` now uses the same logic as `$interpolate` (i.e. `{{ myObject }}`) when binding, which means values other than strings are now transformed as follows: - `null`/`undefined` become the empty string. - If an object is not Array, Number or Date and has a custom `toString()` function, use that. - Otherwise use `JSON.stringify()`. Previously, `ngBind` would always use `toString()`. The following examples show the difference: ```js $scope.myPlainObject = {a: 1, b: 2}; $scope.myCustomObject = {a: 1, b: 2, toString: function() { return 'a+b'; }}; ``` Plain Object: ```html [object Object] {'a':1,'b':2} ``` Object with custom `toString()`: ```html [object Object] a+b ``` If you want the output of `toString()`, you can call it manually on the value in `ngBind`: ```html [object Object] ``` #### **ngModel**: **Due to [9e24e7](https://github.com/angular/angular.js/commit/9e24e774a558143b3478536911a3a4c1714564ba)**, `NgModelController` now defines its methods on its prototype, instead of on each instance. As a consequence, `NgModelController` methods always need to be called in the correct context. For example `$scope.$watch('something', myNgModelCtrl.$setDirty)` will no longer work, because the `$setDirty` method is passed without any context. The code must now be changed to: ```js $scope.$watch('something', function() { myNgModelCtrl.$setDirty(); }) ```
**Due to [7bc71a](https://github.com/angular/angular.js/commit/7bc71adc63bb6bb609b44dd2d3ea8fb0cd3f300b)**, the values returned by synchronous validators are always treated as boolean. Previously, only a literal `false` return value would cause the validation to fail. Now, _all_ falsy values will cause the validation to fail, as one would naturally expect. Specifically, the values `0`, `null`, `NaN` and `''` (the empty string) used to cause the validation to pass and they will now cause it to fail. The value `undefined` was treated similarly to a pending asynchronous validator, causing the validation to be pending. `undefined` is now also treated as `false`. If your synchronous validators are always returning boolean values (which should already be the case for most applications anyway), then this change does not affect you. If not, make sure you always return a boolean value (`true/false`) indicating whether the input is valid or not. #### **ngModelOptions**: **Due to [296cfc](https://github.com/angular/angular.js/commit/296cfce40c25e9438bfa46a0eb27240707a10ffa)**, the programmatic API for `ngModelOptions` has changed. You must now read options via the `ngModelController.$options.getOption(name)` method, rather than accessing the option directly as a property of the `ngModelContoller.$options` object. One benefit of these changes, though, is that the `ngModelControler.$options` property is now guaranteed to be defined so there is no need to check before accessing. This does not affect the usage in templates and only affects custom directives that might have been reading options for their own purposes. If you were programmatically accessing the options, you need to change your code as follows: Before: ```js var myOption = ngModelController.$options && ngModelController.$options['my-option']; ``` After: ```js var myOption = ngModelController.$options.getOption('my-option'); ``` #### **ngTransclude**: **Due to [32aa7e](https://github.com/angular/angular.js/commit/32aa7e7395527624119e3917c54ee43b4d219301)**, if you only provide whitespace as the transclusion content, it will be assumed to be empty and the fallback content will be used instead. Previously, whitespace only transclusion would be treated as the transclusion being "not empty", which meant that fallback content was not used in that case. If you actually want whitespace to appear as the transcluded content, then you can force it to be used by adding an HTML comment to the whitespace: ```html ``` #### **select**: **Due to [f02b70](https://github.com/angular/angular.js/commit/f02b707b5e4a5ffd1e1a20d910754cfabfc19622)**, using `ngValue` on `