diff --git a/CHANGELOG.md b/CHANGELOG.md
index b0f1531a96b2..29176742999d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,59 @@
+
+# 1.7.1 momentum-defiance (2018-06-08)
+
+
+## Bug Fixes
+- **$compile:** support transcluding multi-element directives
+ ([789db8](https://github.com/angular/angular.js/commit/789db83a8ae0e2db5db13289b2c29e56093d967a),
+ [#15554](https://github.com/angular/angular.js/issues/15554),
+ [#15555](https://github.com/angular/angular.js/issues/15555))
+- **ngModel:** do not throw if view value changes on destroyed scope
+ ([2b6c98](https://github.com/angular/angular.js/commit/2b6c9867369fd3ef1ddb687af1153478ab62ee1b),
+ [#16583](https://github.com/angular/angular.js/issues/16583),
+ [#16585](https://github.com/angular/angular.js/issues/16585))
+
+
+## New Features
+- **$compile:** add one-way collection bindings
+ ([f9d1ca](https://github.com/angular/angular.js/commit/f9d1ca20c38f065f15769fbe23aee5314cb58bd4),
+ [#14039](https://github.com/angular/angular.js/issues/14039),
+ [#16553](https://github.com/angular/angular.js/issues/16553),
+ [#15874](https://github.com/angular/angular.js/issues/15874))
+- **ngRef:** add directive to publish controller, or element into scope
+ ([bf841d](https://github.com/angular/angular.js/commit/bf841d35120bf3c4655fde46af4105c85a0f1cdc),
+ [#16511](https://github.com/angular/angular.js/issues/16511))
+- **errorHandlingConfig:** add option to exclude error params from url
+ ([3d6c45](https://github.com/angular/angular.js/commit/3d6c45d76e30b1b3c4eb9672cf4a93e5251c06b3),
+ [#14744](https://github.com/angular/angular.js/issues/14744),
+ [#15707](https://github.com/angular/angular.js/issues/15707),
+ [#16283](https://github.com/angular/angular.js/issues/16283),
+ [#16299](https://github.com/angular/angular.js/issues/16299),
+ [#16591](https://github.com/angular/angular.js/issues/16591))
+- **ngAria:** add support for ignoring a specific element
+ ([7d9d38](https://github.com/angular/angular.js/commit/7d9d387195292cb5e04984602b752d31853cfea6),
+ [#14602](https://github.com/angular/angular.js/issues/14602),
+ [#14672](https://github.com/angular/angular.js/issues/14672),
+ [#14833](https://github.com/angular/angular.js/issues/14833))
+- **ngCookies:** support samesite option
+ ([10a229](https://github.com/angular/angular.js/commit/10a229ce1befdeaf6295d1635dc11391c252a91a),
+ [#16543](https://github.com/angular/angular.js/issues/16543),
+ [#16544](https://github.com/angular/angular.js/issues/16544))
+- **ngMessages:** add support for default message
+ ([a8c263](https://github.com/angular/angular.js/commit/a8c263c1947cc85ee60b4732f7e4bcdc7ba463e8),
+ [#12008](https://github.com/angular/angular.js/issues/12008),
+ [#12213](https://github.com/angular/angular.js/issues/12213),
+ [#16587](https://github.com/angular/angular.js/issues/16587))
+- **ngMock, ngMockE2E:** add option to match latest definition for `$httpBackend` request
+ ([773f39](https://github.com/angular/angular.js/commit/773f39c9345479f5f8b6321236ce6ad96f77aa92),
+ [#16251](https://github.com/angular/angular.js/issues/16251),
+ [#11637](https://github.com/angular/angular.js/issues/11637),
+ [#16560](https://github.com/angular/angular.js/issues/16560))
+- **$route:** add support for the `reloadOnUrl` configuration option
+ ([f4f571](https://github.com/angular/angular.js/commit/f4f571efdf86d6acbcd5c6b1de66b4b33a259125),
+ [#7925](https://github.com/angular/angular.js/issues/7925),
+ [#15002](https://github.com/angular/angular.js/issues/15002))
+
+
# 1.7.0 nonexistent-physiology (2018-05-11)
diff --git a/src/ngAria/aria.js b/src/ngAria/aria.js
index bf82141b2123..904d9a0e5959 100644
--- a/src/ngAria/aria.js
+++ b/src/ngAria/aria.js
@@ -14,8 +14,8 @@
*
* For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
* directives are supported:
- * `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
- * `ngDblClick`, and `ngMessages`.
+ * `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`,
+ * `ngClick`, `ngDblClick`, and `ngMessages`.
*
* Below is a more detailed breakdown of the attributes handled by ngAria:
*
@@ -46,11 +46,17 @@
*
* ```
*
- * ## Disabling Attributes
- * It's possible to disable individual attributes added by ngAria with the
+ * ## Disabling Specific Attributes
+ * It is possible to disable individual attributes added by ngAria with the
* {@link ngAria.$ariaProvider#config config} method. For more details, see the
* {@link guide/accessibility Developer Guide}.
+ *
+ * ## Disabling `ngAria` on Specific Elements
+ * It is possible to make `ngAria` ignore a specific element, by adding the `ng-aria-disable`
+ * attribute on it. Note that only the element itself (and not its child elements) will be ignored.
*/
+var ARIA_DISABLE_ATTR = 'ngAriaDisable';
+
var ngAriaModule = angular.module('ngAria', ['ng']).
info({ angularVersion: '"NG_VERSION_FULL"' }).
provider('$aria', $AriaProvider);
@@ -132,6 +138,8 @@ function $AriaProvider() {
function watchExpr(attrName, ariaAttr, nodeBlackList, negate) {
return function(scope, elem, attr) {
+ if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
+
var ariaCamelName = attr.$normalize(ariaAttr);
if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) {
scope.$watch(attr[attrName], function(boolVal) {
@@ -251,6 +259,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
require: 'ngModel',
priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value
compile: function(elem, attr) {
+ if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
+
var shape = getShape(attr, elem);
return {
@@ -347,6 +357,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
restrict: 'A',
require: '?ngMessages',
link: function(scope, elem, attr, ngMessages) {
+ if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
+
if (!elem.attr('aria-live')) {
elem.attr('aria-live', 'assertive');
}
@@ -357,6 +369,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
return {
restrict: 'A',
compile: function(elem, attr) {
+ if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
+
var fn = $parse(attr.ngClick);
return function(scope, elem, attr) {
@@ -389,6 +403,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
}])
.directive('ngDblclick', ['$aria', function($aria) {
return function(scope, elem, attr) {
+ if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
+
if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) {
elem.attr('tabindex', 0);
}
diff --git a/src/ngRoute/route.js b/src/ngRoute/route.js
index f0e6c19b9079..6902a3e654d2 100644
--- a/src/ngRoute/route.js
+++ b/src/ngRoute/route.js
@@ -183,11 +183,22 @@ function $RouteProvider() {
* `redirectTo` takes precedence over `resolveRedirectTo`, so specifying both on the same
* route definition, will cause the latter to be ignored.
*
+ * - `[reloadOnUrl=true]` - `{boolean=}` - reload route when any part of the URL changes
+ * (inluding the path) even if the new URL maps to the same route.
+ *
+ * If the option is set to `false` and the URL in the browser changes, but the new URL maps
+ * to the same route, then a `$routeUpdate` event is broadcasted on the root scope (without
+ * reloading the route).
+ *
* - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
* or `$location.hash()` changes.
*
- * If the option is set to `false` and url in the browser changes, then
- * `$routeUpdate` event is broadcasted on the root scope.
+ * If the option is set to `false` and the URL in the browser changes, then a `$routeUpdate`
+ * event is broadcasted on the root scope (without reloading the route).
+ *
+ *
+ * **Note:** This option has no effect if `reloadOnUrl` is set to `false`.
+ *
*
* - `[caseInsensitiveMatch=false]` - `{boolean=}` - match routes without being case sensitive
*
@@ -202,6 +213,9 @@ function $RouteProvider() {
this.when = function(path, route) {
//copy original route object to preserve params inherited from proto chain
var routeCopy = shallowCopy(route);
+ if (angular.isUndefined(routeCopy.reloadOnUrl)) {
+ routeCopy.reloadOnUrl = true;
+ }
if (angular.isUndefined(routeCopy.reloadOnSearch)) {
routeCopy.reloadOnSearch = true;
}
@@ -544,8 +558,9 @@ function $RouteProvider() {
* @name $route#$routeUpdate
* @eventType broadcast on root scope
* @description
- * The `reloadOnSearch` property has been set to false, and we are reusing the same
- * instance of the Controller.
+ * Broadcasted if the same instance of a route (including template, controller instance,
+ * resolved dependencies, etc.) is being reused. This can happen if either `reloadOnSearch` or
+ * `reloadOnUrl` has been set to `false`.
*
* @param {Object} angularEvent Synthetic event object
* @param {Route} current Current/previous route information.
@@ -653,9 +668,7 @@ function $RouteProvider() {
var lastRoute = $route.current;
preparedRoute = parseRoute();
- preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route
- && angular.equals(preparedRoute.pathParams, lastRoute.pathParams)
- && !preparedRoute.reloadOnSearch && !forceReload;
+ preparedRouteIsUpdateOnly = isNavigationUpdateOnly(preparedRoute, lastRoute);
if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) {
if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) {
@@ -835,6 +848,29 @@ function $RouteProvider() {
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
}
+ /**
+ * @param {Object} newRoute - The new route configuration (as returned by `parseRoute()`).
+ * @param {Object} oldRoute - The previous route configuration (as returned by `parseRoute()`).
+ * @returns {boolean} Whether this is an "update-only" navigation, i.e. the URL maps to the same
+ * route and it can be reused (based on the config and the type of change).
+ */
+ function isNavigationUpdateOnly(newRoute, oldRoute) {
+ // IF this is not a forced reload
+ return !forceReload
+ // AND both `newRoute`/`oldRoute` are defined
+ && newRoute && oldRoute
+ // AND they map to the same Route Definition Object
+ && (newRoute.$$route === oldRoute.$$route)
+ // AND `reloadOnUrl` is disabled
+ && (!newRoute.reloadOnUrl
+ // OR `reloadOnSearch` is disabled
+ || (!newRoute.reloadOnSearch
+ // AND both routes have the same path params
+ && angular.equals(newRoute.pathParams, oldRoute.pathParams)
+ )
+ );
+ }
+
/**
* @returns {string} interpolation of the redirect path with the parameters
*/
diff --git a/test/ngAria/ariaSpec.js b/test/ngAria/ariaSpec.js
index 201608498961..1970b01438b0 100644
--- a/test/ngAria/ariaSpec.js
+++ b/test/ngAria/ariaSpec.js
@@ -9,17 +9,236 @@ describe('$aria', function() {
dealoc(element);
});
- function injectScopeAndCompiler() {
- return inject(function(_$compile_, _$rootScope_) {
- $compile = _$compile_;
- scope = _$rootScope_;
+ describe('with `ngAriaDisable`', function() {
+ beforeEach(injectScopeAndCompiler);
+ beforeEach(function() {
+ jasmine.addMatchers({
+ toHaveAttribute: function toHaveAttributeMatcher() {
+ return {
+ compare: function toHaveAttributeCompare(element, attr) {
+ var node = element[0];
+ var pass = node.hasAttribute(attr);
+ var message = 'Expected `' + node.outerHTML + '` ' + (pass ? 'not ' : '') +
+ 'to have attribute `' + attr + '`.';
+
+ return {
+ pass: pass,
+ message: message
+ };
+ }
+ };
+ }
+ });
});
- }
- function compileElement(inputHtml) {
- element = $compile(inputHtml)(scope);
- scope.$digest();
- }
+ // ariaChecked
+ it('should not attach aria-checked to custom checkbox', function() {
+ compileElement('');
+
+ scope.$apply('val = false');
+ expect(element).not.toHaveAttribute('aria-checked');
+
+ scope.$apply('val = true');
+ expect(element).not.toHaveAttribute('aria-checked');
+ });
+
+ it('should not attach aria-checked to custom radio controls', function() {
+ compileElement(
+ '' +
+ '');
+
+ var radio1 = element.eq(0);
+ var radio2 = element.eq(1);
+
+ scope.$apply('val = "one"');
+ expect(radio1).not.toHaveAttribute('aria-checked');
+ expect(radio2).not.toHaveAttribute('aria-checked');
+
+ scope.$apply('val = "two"');
+ expect(radio1).not.toHaveAttribute('aria-checked');
+ expect(radio2).not.toHaveAttribute('aria-checked');
+ });
+
+ // ariaDisabled
+ it('should not attach aria-disabled to custom controls', function() {
+ compileElement('');
+
+ scope.$apply('val = false');
+ expect(element).not.toHaveAttribute('aria-disabled');
+
+ scope.$apply('val = true');
+ expect(element).not.toHaveAttribute('aria-disabled');
+ });
+
+ // ariaHidden
+ it('should not attach aria-hidden to `ngShow`', function() {
+ compileElement('');
+
+ scope.$apply('val = false');
+ expect(element).not.toHaveAttribute('aria-hidden');
+
+ scope.$apply('val = true');
+ expect(element).not.toHaveAttribute('aria-hidden');
+ });
+
+ it('should not attach aria-hidden to `ngHide`', function() {
+ compileElement('');
+
+ scope.$apply('val = false');
+ expect(element).not.toHaveAttribute('aria-hidden');
+
+ scope.$apply('val = true');
+ expect(element).not.toHaveAttribute('aria-hidden');
+ });
+
+ // ariaInvalid
+ it('should not attach aria-invalid to input', function() {
+ compileElement('');
+
+ scope.$apply('val = "lt 10"');
+ expect(element).not.toHaveAttribute('aria-invalid');
+
+ scope.$apply('val = "gt 10 characters"');
+ expect(element).not.toHaveAttribute('aria-invalid');
+ });
+
+ it('should not attach aria-invalid to custom controls', function() {
+ compileElement('');
+
+ scope.$apply('val = "lt 10"');
+ expect(element).not.toHaveAttribute('aria-invalid');
+
+ scope.$apply('val = "gt 10 characters"');
+ expect(element).not.toHaveAttribute('aria-invalid');
+ });
+
+ // ariaLive
+ it('should not attach aria-live to `ngMessages`', function() {
+ compileElement('