Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be240b1176 | |||
| 61b33543ff | |||
| 1dcba9cd88 | |||
| 8cd54d7794 | |||
| 3105b2c26a | |||
| bc5a48d4a4 | |||
| 2bbc7c464f | |||
| e85f91d582 | |||
| 862a78dfd2 | |||
| bd772abf34 | |||
| b074d719ae | |||
| a43a40b778 | |||
| 2ceeb739f3 | |||
| fa715abf45 | |||
| f943e377e8 | |||
| 30084c1369 | |||
| 668a33da34 | |||
| 19e2347759 | |||
| f17292e1b5 | |||
| 77b4330011 |
@@ -1,3 +1,27 @@
|
||||
<a name="1.7.4"></a>
|
||||
# 1.7.4 interstellar-exploration (2018-09-07)
|
||||
|
||||
## Bug Fixes
|
||||
- **ngAria.ngClick:** prevent default event on space/enter only for non-interactive elements
|
||||
([61b335](https://github.com/angular/angular.js/commit/61b33543ff8e7f32464dec98a46bf0a35e9b03a4),
|
||||
[#16664](https://github.com/angular/angular.js/issues/16664),
|
||||
[#16680](https://github.com/angular/angular.js/issues/16680))
|
||||
- **ngAnimate:** remove the "prepare" classes with multiple structural animations
|
||||
([3105b2](https://github.com/angular/angular.js/commit/3105b2c26a71594c4e7904efc18f4b2e9da25b1b),
|
||||
[#16681](https://github.com/angular/angular.js/issues/16681),
|
||||
[#16677](https://github.com/angular/angular.js/issues/16677))
|
||||
- **$route:** correctly extract path params if the path contains a question mark or a hash
|
||||
([2ceeb7](https://github.com/angular/angular.js/commit/2ceeb739f35e01fcebcabac4beeeb7684ae9f86d))
|
||||
- **ngHref:** allow numbers and other objects in interpolation
|
||||
([30084c](https://github.com/angular/angular.js/commit/30084c13699c814ff6703d7aa2d3947a9b2f7067),
|
||||
[#16652](https://github.com/angular/angular.js/issues/16652),
|
||||
[#16626](https://github.com/angular/angular.js/issues/16626))
|
||||
- **select:** allow to select first option with value `undefined`
|
||||
([668a33](https://github.com/angular/angular.js/commit/668a33da3439f17e61dfa8f6d9b114ebde8c9d87),
|
||||
[#16653](https://github.com/angular/angular.js/issues/16653),
|
||||
[#16656](https://github.com/angular/angular.js/issues/16656))
|
||||
|
||||
|
||||
<a name="1.7.3"></a>
|
||||
# 1.7.3 eventful-proposal (2018-08-03)
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('repeatAnimateBenchmark', ['ngAnimate'])
|
||||
.config(function($animateProvider) {
|
||||
$animateProvider.classNameFilter(/animate-/);
|
||||
})
|
||||
.run(function($rootScope) {
|
||||
$rootScope.fileType = 'classfilter';
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('repeatAnimateBenchmark', [])
|
||||
.run(function($rootScope) {
|
||||
$rootScope.fileType = 'noanimate';
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('repeatAnimateBenchmark', ['ngAnimate'])
|
||||
.run(function($rootScope) {
|
||||
$rootScope.fileType = 'default';
|
||||
});
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/* eslint-env node */
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
scripts: [
|
||||
{
|
||||
id: 'angular',
|
||||
src: '/build/angular.js'
|
||||
},
|
||||
{
|
||||
id: 'angular-animate',
|
||||
src: '/build/angular-animate.js'
|
||||
},
|
||||
{
|
||||
id: 'app',
|
||||
src: 'app.js'
|
||||
},
|
||||
{
|
||||
src: 'common.js'
|
||||
}]
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
var app = angular.module('repeatAnimateBenchmark');
|
||||
|
||||
app.config(function($compileProvider, $animateProvider) {
|
||||
if ($compileProvider.debugInfoEnabled) {
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.run(function($animate) {
|
||||
if ($animate.enabled) {
|
||||
$animate.enabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
app.controller('DataController', function($scope, $rootScope, $animate) {
|
||||
var totalRows = 500;
|
||||
var totalColumns = 20;
|
||||
|
||||
var data = $scope.data = [];
|
||||
|
||||
function fillData() {
|
||||
if ($animate.enabled) {
|
||||
$animate.enabled($scope.benchmarkType !== 'globallyDisabled');
|
||||
}
|
||||
|
||||
for (var i = 0; i < totalRows; i++) {
|
||||
data[i] = [];
|
||||
for (var j = 0; j < totalColumns; j++) {
|
||||
data[i][j] = {
|
||||
i: i
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'enter',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
fillData();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'leave',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
data = $scope.data = [];
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.directive('disableAnimations', function($animate) {
|
||||
return {
|
||||
link: {
|
||||
pre: function(s, e) {
|
||||
$animate.enabled(e, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.directive('noop', function($animate) {
|
||||
return {
|
||||
link: {
|
||||
pre: angular.noop
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.directive('baseline', function($document) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, $element) {
|
||||
var document = $document[0];
|
||||
|
||||
var i, j, row, cell, comment;
|
||||
var template = document.createElement('span');
|
||||
template.setAttribute('ng-repeat', 'foo in foos');
|
||||
template.classList.add('ng-scope');
|
||||
template.appendChild(document.createElement('span'));
|
||||
template.appendChild(document.createTextNode(':'));
|
||||
|
||||
function createList() {
|
||||
for (i = 0; i < $scope.data.length; i++) {
|
||||
row = document.createElement('div');
|
||||
$element[0].appendChild(row);
|
||||
for (j = 0; j < $scope.data[i].length; j++) {
|
||||
cell = template.cloneNode(true);
|
||||
row.appendChild(cell);
|
||||
cell.childNodes[0].textContent = i;
|
||||
cell.ng339 = 'xxx';
|
||||
comment = document.createComment('ngRepeat end: bar in foo');
|
||||
row.appendChild(comment);
|
||||
}
|
||||
|
||||
comment = document.createComment('ngRepeat end: foo in foos');
|
||||
$element[0].appendChild(comment);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch('data.length', function(newVal) {
|
||||
if (newVal === 0) {
|
||||
while ($element[0].firstChild) {
|
||||
$element[0].removeChild($element[0].firstChild);
|
||||
}
|
||||
} else {
|
||||
createList();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,70 @@
|
||||
<div ng-app="repeatAnimateBenchmark" ng-cloak>
|
||||
<div ng-controller="DataController">
|
||||
<div class="container-fluid">
|
||||
<p>
|
||||
Tests rendering of an ngRepeat with 500 elements.<br>
|
||||
Animations can be enabled / disabled in different ways.<br>
|
||||
Two tests require reloading the app with different module / app configurations.
|
||||
</p>
|
||||
|
||||
<div><label><input type="radio" ng-model="benchmarkType" value="none">none: </label></div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" value="baseline">baseline (vanilla Javascript): </label></div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'default'" value="enabled">enabled : </label> (requires <a href="./">app.js</a>)</div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'default' && fileType !== 'classfilter'" value="globallyDisabled">globally disabled:</label> (requires <a href="./">app.js</a> or <a href="?app=app-classfilter.js">app-classfilter.js</a>)</div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'default'" value="disabledParentElement">disabled by $animate.enabled() on parent element: </label> (requires <a href="./">app.js</a>)</div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'noanimate'" value="noanimate">Without ngAnimate:</label> (requires <a href="?app=app-noanimate.js">app-noanimate.js</a>)</div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'classfilter'" value="disabledClassFilter">disabled by classNameFilter on element:</label> (requires <a href="?app=app-classfilter.js">app-classfilter.js</a>)</div>
|
||||
|
||||
<ng-switch on="benchmarkType">
|
||||
<baseline ng-switch-when="baseline">
|
||||
</baseline>
|
||||
<div ng-switch-when="noanimate">
|
||||
<div noop>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="enabled">
|
||||
<div noop>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="globallyDisabled">
|
||||
<div noop>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="disabledClassFilter">
|
||||
<div noop>
|
||||
<div ng-repeat="row in data">
|
||||
<span class="disable-animations" ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="disabledParentElement">
|
||||
<div disable-animations>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-switch>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+127
-111
@@ -25,7 +25,7 @@ Additionally, we have removed some long-deprecated modules and APIs.
|
||||
|
||||
The most notable changes are:
|
||||
|
||||
- $resource has now support for request and requestError interceptors
|
||||
- `$resource` has now support for request and requestError interceptors
|
||||
|
||||
- Several deprecated features have been removed:
|
||||
- the `$controllerProvider.allowGlobals()` flag
|
||||
@@ -36,8 +36,8 @@ The most notable changes are:
|
||||
- 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.
|
||||
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.
|
||||
|
||||
|
||||
<br />
|
||||
@@ -48,11 +48,11 @@ Below is the full list of breaking changes:
|
||||
<a name="migrate1.6to1.7-ng-directives"></a>
|
||||
### Core: _Directives_
|
||||
|
||||
<a name="migrate1.6to1.7-ng-directives-form"></a>
|
||||
|
||||
#### **form**
|
||||
|
||||
**Due to [223de5](https://github.com/angular/angular.js/commit/223de59e988dc0cc8b4ec3a045b7c0735eba1c77)**,
|
||||
forms will now set $submitted on child forms when they are submitted.
|
||||
forms will now set `$submitted` on child forms when they are submitted.
|
||||
For example:
|
||||
```
|
||||
<form name="parentform" ng-submit="$ctrl.submit()">
|
||||
@@ -63,15 +63,16 @@ For example:
|
||||
</form>
|
||||
```
|
||||
|
||||
Submitting this form will set $submitted on "parentform" and "childform".
|
||||
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
|
||||
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"
|
||||
@@ -84,10 +85,10 @@ Two scenarios might need migration:
|
||||
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.
|
||||
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
|
||||
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:
|
||||
@@ -95,50 +96,52 @@ needs to register a change event listener instead.
|
||||
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.
|
||||
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('<input type="checkbox" ng-model="checkbox" />')($rootScope);
|
||||
it('should update the model', inject(function($compile, $rootScope) {
|
||||
var inputElm = $compile('<input type="checkbox" ng-model="checkbox" />')($rootScope);
|
||||
|
||||
inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger()
|
||||
expect($rootScope.checkbox).toBe(true);
|
||||
});
|
||||
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 the inputElm to the app's
|
||||
`$rootElement`, and the `$rootElement` to the `$document`.
|
||||
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('<input type="checkbox" ng-model="checkbox" />')($rootScope);
|
||||
it('should update the model', inject(function($compile, $rootScope, $rootElement, $document) {
|
||||
var inputElm = $compile('<input type="checkbox" ng-model="checkbox" />')($rootScope);
|
||||
|
||||
$rootElement.append(inputElm);
|
||||
$document.append($rootElement);
|
||||
$rootElement.append(inputElm);
|
||||
$document.append($rootElement);
|
||||
|
||||
inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger()
|
||||
expect($rootScope.checkbox).toBe(true);
|
||||
});
|
||||
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:
|
||||
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',
|
||||
@@ -154,11 +157,11 @@ If you rely on the $modelValue validation, you can overwrite the `min`/`max` val
|
||||
|
||||
|
||||
#### **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" do no
|
||||
longer set `ngModelController.$error.number` and the `ng-invalid-number` class.
|
||||
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
|
||||
@@ -166,6 +169,7 @@ 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.
|
||||
@@ -179,24 +183,24 @@ 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 }
|
||||
@@ -207,14 +211,15 @@ ng-model-options="{
|
||||
#### **ngStyle**
|
||||
|
||||
**Due to [15bbd3](https://github.com/angular/angular.js/commit/15bbd3e18cd89b91f7206a06c73d40e54a8a48a0)**,
|
||||
previously the use of deep watch by ng-style would trigger styles to be
|
||||
re-applied when nested state changed. Now only changes to direct
|
||||
properties of the watched object will trigger changes.
|
||||
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.
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ng-services"></a>
|
||||
### Core: _Services_
|
||||
|
||||
|
||||
#### **$compile**
|
||||
|
||||
**Due to [38f8c9](https://github.com/angular/angular.js/commit/38f8c97af74649ce224b6dd45f433cc665acfbfb)**,
|
||||
@@ -238,16 +243,16 @@ 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
|
||||
guide: https://docs.angularjs.org/guide/migration#migrating-from-1-5-to-1-6
|
||||
Afterwards, remove the `$compileProvider.preAssignBindingsEnabled(true)`
|
||||
statement.
|
||||
|
||||
<hr />
|
||||
|
||||
**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
|
||||
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 `<a>` or `<image>`
|
||||
elements and if the values do not pass the regular URL sanitization, they will
|
||||
break.
|
||||
@@ -258,37 +263,39 @@ To fix this you need to ensure that the values used for binding to the affected
|
||||
`imgSrcSanitizationWhitelist` (for `<image>` elements).
|
||||
|
||||
<hr />
|
||||
**Due to [fd4f01](https://github.com/angular/angular.js/commit/fd4f0111188b62773b99ab6eab38b4d2b5d8d727)**,
|
||||
deepWatch is no longer used in in literal one-way bindings.
|
||||
|
||||
Previously when a literal value was passed into a directive/component via
|
||||
**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 `<my-component input="[a]">`, a new instance of the array
|
||||
would be passed into the directive/component (and trigger $onChanges) not
|
||||
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
|
||||
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.
|
||||
literal. In the example above, the input would be `a`. Changes are only
|
||||
triggered, when the inputs to the literal change.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to [1cf728](https://github.com/angular/angular.js/commit/1cf728e209a9e0016068fac2769827e8f747760e)**,
|
||||
`base[href]` was added to the list of RESOURCE_URL context attributes.
|
||||
`base[href]` was added to the list of `RESOURCE_URL` context attributes.
|
||||
|
||||
Previously, `<base href="{{ $ctrl.baseUrl }}" />` 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
|
||||
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.
|
||||
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: `<base href="/something/{{ $ctrl.partialPath }}" />`.
|
||||
@@ -315,10 +322,10 @@ except for the simplest of cases):
|
||||
**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
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -343,7 +350,7 @@ Now the `oldValue` will always equal the previous `newValue`:
|
||||
|
||||
Note the last call now shows `a === 2` in the `oldValues` array.
|
||||
|
||||
This also makes the `oldValue` of one-time watchers more clear. Previously
|
||||
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:
|
||||
|
||||
@@ -367,7 +374,7 @@ Where now the `oldValue` will always equal the previous `newValue`:
|
||||
#### **$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.cancel()` will throw an error if called with a promise that was not generated by
|
||||
`$interval()`. Previously, it would silently do nothing.
|
||||
|
||||
Before:
|
||||
@@ -393,7 +400,7 @@ $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.cancel()` will throw an error if called with a promise that was not generated by
|
||||
`$timeout()`. Previously, it would silently do nothing.
|
||||
|
||||
Before:
|
||||
@@ -417,10 +424,11 @@ $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 them.
|
||||
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
|
||||
@@ -433,29 +441,31 @@ $cookieStore.remove('name');
|
||||
#### **$templateRequest**
|
||||
|
||||
**Due to [c617d6](https://github.com/angular/angular.js/commit/c617d6dceee5b000bfceda44ced22fc16b48b18b)**,
|
||||
give 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]`.
|
||||
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]`.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to ([fb0099](https://github.com/angular/angular.js/commit/fb00991460cf69ae8bc7f1f826363d09c73c0d5e)**,
|
||||
the service now returns the result of `$templateCache.put()` when making a server request to the
|
||||
template. Previously it would return the content of the response directly.
|
||||
This now means if you are decorating `$templateCache.put()` to manipulate the template, you will
|
||||
now get this manipulated result also on the first `$templateRequest` rather than only on subsequent
|
||||
calls (when the template is retrived from the cache).
|
||||
In practice this should not affect any apps, as it is unlikely that they rely on the template being
|
||||
`$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.
|
||||
`$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:
|
||||
|
||||
@@ -468,7 +478,7 @@ 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.
|
||||
To migrate, add a `catch()` handler to your animation runners.
|
||||
|
||||
|
||||
#### **$controller**
|
||||
@@ -479,26 +489,29 @@ 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 bad. To migrate, remove the call to $controllerProvider.allowGlobals() in the config, and
|
||||
register your controller via the Module API or the $controllerProvider, e.g.
|
||||
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
|
||||
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.
|
||||
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
|
||||
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
|
||||
@@ -507,40 +520,43 @@ these values again.
|
||||
|
||||
<hr/>
|
||||
|
||||
Due to **[1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**,
|
||||
**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:
|
||||
({@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));
|
||||
};
|
||||
$scope.imgThumbFn = function(id) {
|
||||
return $sce.trustAsResourceUrl(someService.someUrl(id));
|
||||
};
|
||||
```
|
||||
|
||||
```html
|
||||
<img ng-src="{{imgThumbFn(imgId)}}">
|
||||
<img ng-src="{{ imgThumbFn(imgId) }}" />
|
||||
```
|
||||
|
||||
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));
|
||||
});
|
||||
$scope.$watch('imgId', function(id) {
|
||||
$scope.imgThumb = $sce.trustAsResourceUrl(someService.someUrl(id));
|
||||
});
|
||||
```
|
||||
|
||||
```html
|
||||
<img ng-src="{{imgThumb}}">
|
||||
<img ng-src="{{ imgThumb }}" />
|
||||
```
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ng-filters"></a>
|
||||
### 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`.
|
||||
@@ -565,8 +581,9 @@ orderByFilter(['a', undefined, 'o', null, 'z']);
|
||||
|
||||
|
||||
#### **jqLite**
|
||||
|
||||
**Due to [b7d396](https://github.com/angular/angular.js/commit/b7d396b8b6e8f27a1f4556d58fc903321e8d532a)**,
|
||||
removeData() no longer removes event handlers.
|
||||
`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:
|
||||
@@ -591,22 +608,20 @@ elem.remove();
|
||||
will remove event handlers as well.
|
||||
|
||||
|
||||
|
||||
#### **Angular**
|
||||
#### **Helpers**
|
||||
|
||||
**Due to [1daa4f](https://github.com/angular/angular.js/commit/1daa4f2231a89ee88345689f001805ffffa9e7de)**,
|
||||
the helper functions `angular.lowercase` `and angular.uppercase` have been removed.
|
||||
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-ins `toLowerCase` and `toUpperCase`
|
||||
Developers should generally use the built-in methods `toLowerCase` and `toUpperCase`
|
||||
or `toLocaleLowerCase` and `toLocaleUpperCase` for special cases.
|
||||
|
||||
Further, we generally discourage using the angular.x helpers in application code.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to [e3ece2](https://github.com/angular/angular.js/commit/e3ece2fad9e1e6d47b5f06815ff186d7e6f44948)**,
|
||||
`angular.isArray()` now supports Array subclasses.
|
||||
|
||||
@@ -627,18 +642,19 @@ be able to handle these objects better when copying or watching.
|
||||
### ngAria
|
||||
|
||||
**Due to [6d5ef3](https://github.com/angular/angular.js/commit/6d5ef34fc6a974cde73157ba94f9706723dd8f5b)**,
|
||||
the ngAria directive 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.
|
||||
`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.
|
||||
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ngResource"></a>
|
||||
### ngResource
|
||||
|
||||
|
||||
#### **$resource**
|
||||
|
||||
**Due to [ea0585](https://github.com/angular/angular.js/commit/ea0585773bb93fd891576e2271254a17e15f1ddd)**,
|
||||
the behavior of interceptors and success/error callbacks has changed.
|
||||
|
||||
@@ -720,6 +736,7 @@ User.get({id: 2}, onSuccess, onError);
|
||||
```
|
||||
|
||||
<hr />
|
||||
|
||||
**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).
|
||||
@@ -758,7 +775,6 @@ it('...', function() {
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ngScenario"></a>
|
||||
### ngScenario
|
||||
|
||||
@@ -766,7 +782,7 @@ it('...', function() {
|
||||
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 was deprecated and removed from the documentation in 2014.
|
||||
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
|
||||
@@ -778,10 +794,10 @@ not changed. However, we do not guarantee future compatibility.
|
||||
### ngTouch
|
||||
|
||||
**Due to [11d9ad](https://github.com/angular/angular.js/commit/11d9ad1eb25eaf5967195e424108207427835d50)**,
|
||||
the `ngClick` directive from the ngTouch module has been removed, and with it the
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -102,7 +102,7 @@ For more information please visit {@link $http#json-vulnerability-protection JSO
|
||||
|
||||
Bear in mind that calling `$http.jsonp` gives the remote server (and, if the request is not secured, any Man-in-the-Middle attackers)
|
||||
instant remote code execution in your application: the result of these requests is handed off
|
||||
to the browser as regular `<script>` tag.
|
||||
to the browser as a regular `<script>` tag.
|
||||
|
||||
## Strict Contextual Escaping
|
||||
|
||||
|
||||
@@ -7,26 +7,9 @@
|
||||
This page describes the support status of the significant versions of AngularJS.
|
||||
|
||||
<div class="alert alert-info">
|
||||
AngularJS is planning one more significant release, version 1.7, and on July 1, 2018 it will enter a 3 year Long Term Support period.
|
||||
On July 1, 2018 AngularJS entered a 3 year Long Term Support period.
|
||||
</div>
|
||||
|
||||
### Until July 1st 2018
|
||||
|
||||
Any version branch not shown in the following table (e.g. 1.5.x) is no longer being developed.
|
||||
|
||||
<table class="dev-status table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Version</th><th>Status</th><th>Comments</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="security"><td><span>1.2.x</span></td><td>Security patches only</td><td>Last version to provide IE 8 support</td></tr>
|
||||
<tr class="stable"><td><span>1.6.x</span></td><td>Patch Releases</td><td>Minor features, bug fixes, security patches - no breaking changes</td></tr>
|
||||
<tr class="current"><td><span>1.7.x</span></td><td>Active Development</td><td>1.7.0 (not yet released) will be the last release of AngularJS to contain breaking changes</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### After July 1st 2018
|
||||
|
||||
Any version branch not shown in the following table (e.g. 1.6.x) is no longer being developed.
|
||||
|
||||
<table class="dev-status table table-bordered">
|
||||
@@ -36,7 +19,7 @@ Any version branch not shown in the following table (e.g. 1.6.x) is no longer be
|
||||
<tbody>
|
||||
<tr class="security">
|
||||
<td><span>1.2.x</span></td>
|
||||
<td>Long Term Support</td>
|
||||
<td>Security patches only</td>
|
||||
<td>Last version to provide IE 8 support</td>
|
||||
</tr>
|
||||
<tr class="stable">
|
||||
@@ -49,14 +32,16 @@ Any version branch not shown in the following table (e.g. 1.6.x) is no longer be
|
||||
|
||||
### Long Term Support
|
||||
|
||||
On July 1st 2018, we will enter a Long Term Support period for AngularJS.
|
||||
On July 1st 2018, AngularJS entered a Long Term Support period for AngularJS.
|
||||
|
||||
At this time we will focus exclusively on providing fixes to bugs that satisfy at least one of the following criteria:
|
||||
We now focus exclusively on providing fixes to bugs that satisfy at least one of the following criteria:
|
||||
|
||||
* A security flaw is detected in the 1.7.x branch of the framework
|
||||
* One of the major browsers releases a version that will cause current production applications using AngularJS 1.7.x to stop working
|
||||
* The jQuery library releases a version that will cause current production applications using AngularJS 1.7.x to stop working.
|
||||
|
||||
AngularJS 1.2.x will get a new version if and only if a new severe security issue is discovered.
|
||||
|
||||
### Blog Post
|
||||
|
||||
You can read more about these plans in our [blog post announcement](https://blog.angular.io/stable-angularjs-and-long-term-support-7e077635ee9c).
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
"cross-spawn": "^4.0.0",
|
||||
"cz-conventional-changelog": "1.1.4",
|
||||
"dgeni": "^0.4.9",
|
||||
"dgeni-packages": "^0.26.2",
|
||||
"dgeni-packages": "^0.26.5",
|
||||
"eslint-plugin-promise": "^3.6.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"glob": "^6.0.1",
|
||||
|
||||
+5
-4
@@ -792,15 +792,16 @@ function arrayRemove(array, value) {
|
||||
* * If `source` is identical to `destination` an exception will be thrown.
|
||||
*
|
||||
* <br />
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* Only enumerable properties are taken into account. Non-enumerable properties (both on `source`
|
||||
* and on `destination`) will be ignored.
|
||||
* </div>
|
||||
*
|
||||
* @param {*} source The source that will be used to make a copy.
|
||||
* Can be any type, including primitives, `null`, and `undefined`.
|
||||
* @param {(Object|Array)=} destination Destination into which the source is copied. If
|
||||
* provided, must be of the same type as `source`.
|
||||
* @param {*} source The source that will be used to make a copy. Can be any type, including
|
||||
* primitives, `null`, and `undefined`.
|
||||
* @param {(Object|Array)=} destination Destination into which the source is copied. If provided,
|
||||
* must be of the same type as `source`.
|
||||
* @returns {*} The copy or updated `destination`, if `destination` was specified.
|
||||
*
|
||||
* @example
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
* For example, if an item is added to the collection, `ngRepeat` will know that all other items
|
||||
* already have DOM elements, and will not re-render them.
|
||||
*
|
||||
* All different types of tracking functions, their syntax, and and their support for duplicate
|
||||
* All different types of tracking functions, their syntax, and their support for duplicate
|
||||
* items in collections can be found in the
|
||||
* {@link ngRepeat#ngRepeat-arguments ngRepeat expression description}.
|
||||
*
|
||||
|
||||
@@ -383,7 +383,7 @@ var SelectController =
|
||||
|
||||
if (optionAttrs.$attr.ngValue) {
|
||||
// The value attribute is set by ngValue
|
||||
var oldVal, hashedVal = NaN;
|
||||
var oldVal, hashedVal;
|
||||
optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
|
||||
|
||||
var removal;
|
||||
@@ -556,18 +556,6 @@ var SelectController =
|
||||
* {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive.
|
||||
*
|
||||
*
|
||||
* @knownIssue
|
||||
*
|
||||
* In Firefox, the select model is only updated when the select element is blurred. For example,
|
||||
* when switching between options with the keyboard, the select model is only set to the
|
||||
* currently selected option when the select is blurred, e.g via tab key or clicking the mouse
|
||||
* outside the select.
|
||||
*
|
||||
* This is due to an ambiguity in the select element specification. See the
|
||||
* [issue on the Firefox bug tracker](https://bugzilla.mozilla.org/show_bug.cgi?id=126379)
|
||||
* for more information, and this
|
||||
* [Github comment for a workaround](https://github.com/angular/angular.js/issues/9134#issuecomment-130800488)
|
||||
*
|
||||
* @example
|
||||
* ### Simple `select` elements with static options
|
||||
*
|
||||
|
||||
+1
-1
@@ -440,7 +440,7 @@ function $SceDelegateProvider() {
|
||||
// If we get here, then we will either sanitize the value or throw an exception.
|
||||
if (type === SCE_CONTEXTS.MEDIA_URL || type === SCE_CONTEXTS.URL) {
|
||||
// we attempt to sanitize non-resource URLs
|
||||
return $$sanitizeUri(maybeTrusted, type === SCE_CONTEXTS.MEDIA_URL);
|
||||
return $$sanitizeUri(maybeTrusted.toString(), type === SCE_CONTEXTS.MEDIA_URL);
|
||||
} else if (type === SCE_CONTEXTS.RESOURCE_URL) {
|
||||
if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
|
||||
return maybeTrusted;
|
||||
|
||||
@@ -113,8 +113,6 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
|
||||
// TODO(matsko): document the signature in a better way
|
||||
return function(element, event, options) {
|
||||
var node = getDomNode(element);
|
||||
|
||||
options = prepareAnimationOptions(options);
|
||||
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
|
||||
|
||||
@@ -186,8 +184,9 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
forEach(groupedAnimations, function(animationEntry) {
|
||||
var element = animationEntry.from ? animationEntry.from.element : animationEntry.element;
|
||||
var extraClasses = options.addClass;
|
||||
|
||||
extraClasses = (extraClasses ? (extraClasses + ' ') : '') + NG_ANIMATE_CLASSNAME;
|
||||
var cacheKey = $$animateCache.cacheKey(node, event, extraClasses, options.removeClass);
|
||||
var cacheKey = $$animateCache.cacheKey(element[0], animationEntry.event, extraClasses, options.removeClass);
|
||||
|
||||
toBeSortedAnimations.push({
|
||||
element: element,
|
||||
|
||||
+6
-2
@@ -389,8 +389,12 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
var keyCode = event.which || event.keyCode;
|
||||
|
||||
if (keyCode === 13 || keyCode === 32) {
|
||||
// Prevent the default browser behavior (e.g. scrolling when pressing spacebar).
|
||||
event.preventDefault();
|
||||
// If the event is triggered on a non-interactive element ...
|
||||
if (nodeBlackList.indexOf(event.target.nodeName) === -1) {
|
||||
// ... prevent the default browser behavior (e.g. scrolling when pressing spacebar)
|
||||
// See https://github.com/angular/angular.js/issues/16664
|
||||
event.preventDefault();
|
||||
}
|
||||
scope.$apply(callback);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+109
-89
@@ -1771,8 +1771,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* See {@link ngMock.$httpBackend#when `when`} for more info.
|
||||
*/
|
||||
$httpBackend.whenRoute = function(method, url) {
|
||||
var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true});
|
||||
return $httpBackend.when(method, pathObj.regexp, undefined, undefined, pathObj.keys);
|
||||
var parsed = parseRouteUrl(url);
|
||||
return $httpBackend.when(method, parsed.regexp, undefined, undefined, parsed.keys);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1955,8 +1955,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* See {@link ngMock.$httpBackend#expect `expect`} for more info.
|
||||
*/
|
||||
$httpBackend.expectRoute = function(method, url) {
|
||||
var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true});
|
||||
return $httpBackend.expect(method, pathObj.regexp, undefined, undefined, pathObj.keys);
|
||||
var parsed = parseRouteUrl(url);
|
||||
return $httpBackend.expect(method, parsed.regexp, undefined, undefined, parsed.keys);
|
||||
};
|
||||
|
||||
|
||||
@@ -2084,6 +2084,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function parseRouteUrl(url) {
|
||||
var strippedUrl = stripQueryAndHash(url);
|
||||
var parseOptions = {caseInsensitiveMatch: true, ignoreTrailingSlashes: true};
|
||||
return routeToRegExp(strippedUrl, parseOptions);
|
||||
}
|
||||
}
|
||||
|
||||
function assertArgDefined(args, index, name) {
|
||||
@@ -2092,110 +2098,124 @@ function assertArgDefined(args, index, name) {
|
||||
}
|
||||
}
|
||||
|
||||
function stripQueryAndHash(url) {
|
||||
return url.replace(/[?#].*$/, '');
|
||||
}
|
||||
|
||||
function MockHttpExpectation(method, url, data, headers, keys) {
|
||||
function MockHttpExpectation(expectedMethod, expectedUrl, expectedData, expectedHeaders,
|
||||
expectedKeys) {
|
||||
|
||||
function getUrlParams(u) {
|
||||
var params = u.slice(u.indexOf('?') + 1).split('&');
|
||||
return params.sort();
|
||||
}
|
||||
this.data = expectedData;
|
||||
this.headers = expectedHeaders;
|
||||
|
||||
function compareUrl(u) {
|
||||
return (url.slice(0, url.indexOf('?')) === u.slice(0, u.indexOf('?')) &&
|
||||
getUrlParams(url).join() === getUrlParams(u).join());
|
||||
}
|
||||
|
||||
this.data = data;
|
||||
this.headers = headers;
|
||||
|
||||
this.match = function(m, u, d, h) {
|
||||
if (method !== m) return false;
|
||||
if (!this.matchUrl(u)) return false;
|
||||
if (angular.isDefined(d) && !this.matchData(d)) return false;
|
||||
if (angular.isDefined(h) && !this.matchHeaders(h)) return false;
|
||||
this.match = function(method, url, data, headers) {
|
||||
if (expectedMethod !== method) return false;
|
||||
if (!this.matchUrl(url)) return false;
|
||||
if (angular.isDefined(data) && !this.matchData(data)) return false;
|
||||
if (angular.isDefined(headers) && !this.matchHeaders(headers)) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
this.matchUrl = function(u) {
|
||||
if (!url) return true;
|
||||
if (angular.isFunction(url.test)) return url.test(u);
|
||||
if (angular.isFunction(url)) return url(u);
|
||||
return (url === u || compareUrl(u));
|
||||
this.matchUrl = function(url) {
|
||||
if (!expectedUrl) return true;
|
||||
if (angular.isFunction(expectedUrl.test)) return expectedUrl.test(url);
|
||||
if (angular.isFunction(expectedUrl)) return expectedUrl(url);
|
||||
return (expectedUrl === url || compareUrlWithQuery(url));
|
||||
};
|
||||
|
||||
this.matchHeaders = function(h) {
|
||||
if (angular.isUndefined(headers)) return true;
|
||||
if (angular.isFunction(headers)) return headers(h);
|
||||
return angular.equals(headers, h);
|
||||
this.matchHeaders = function(headers) {
|
||||
if (angular.isUndefined(expectedHeaders)) return true;
|
||||
if (angular.isFunction(expectedHeaders)) return expectedHeaders(headers);
|
||||
return angular.equals(expectedHeaders, headers);
|
||||
};
|
||||
|
||||
this.matchData = function(d) {
|
||||
if (angular.isUndefined(data)) return true;
|
||||
if (data && angular.isFunction(data.test)) return data.test(d);
|
||||
if (data && angular.isFunction(data)) return data(d);
|
||||
if (data && !angular.isString(data)) {
|
||||
return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d));
|
||||
this.matchData = function(data) {
|
||||
if (angular.isUndefined(expectedData)) return true;
|
||||
if (expectedData && angular.isFunction(expectedData.test)) return expectedData.test(data);
|
||||
if (expectedData && angular.isFunction(expectedData)) return expectedData(data);
|
||||
if (expectedData && !angular.isString(expectedData)) {
|
||||
return angular.equals(angular.fromJson(angular.toJson(expectedData)), angular.fromJson(data));
|
||||
}
|
||||
// eslint-disable-next-line eqeqeq
|
||||
return data == d;
|
||||
return expectedData == data;
|
||||
};
|
||||
|
||||
this.toString = function() {
|
||||
return method + ' ' + url;
|
||||
return expectedMethod + ' ' + expectedUrl;
|
||||
};
|
||||
|
||||
this.params = function(u) {
|
||||
return angular.extend(parseQuery(), pathParams());
|
||||
this.params = function(url) {
|
||||
var queryStr = url.indexOf('?') === -1 ? '' : url.substring(url.indexOf('?') + 1);
|
||||
var strippedUrl = stripQueryAndHash(url);
|
||||
|
||||
function pathParams() {
|
||||
var keyObj = {};
|
||||
if (!url || !angular.isFunction(url.test) || !keys || keys.length === 0) return keyObj;
|
||||
|
||||
var m = url.exec(u);
|
||||
if (!m) return keyObj;
|
||||
for (var i = 1, len = m.length; i < len; ++i) {
|
||||
var key = keys[i - 1];
|
||||
var val = m[i];
|
||||
if (key && val) {
|
||||
keyObj[key.name || key] = val;
|
||||
}
|
||||
}
|
||||
|
||||
return keyObj;
|
||||
}
|
||||
|
||||
function parseQuery() {
|
||||
var obj = {}, key_value, key,
|
||||
queryStr = u.indexOf('?') > -1
|
||||
? u.substring(u.indexOf('?') + 1)
|
||||
: '';
|
||||
|
||||
angular.forEach(queryStr.split('&'), function(keyValue) {
|
||||
if (keyValue) {
|
||||
key_value = keyValue.replace(/\+/g,'%20').split('=');
|
||||
key = tryDecodeURIComponent(key_value[0]);
|
||||
if (angular.isDefined(key)) {
|
||||
var val = angular.isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
|
||||
if (!hasOwnProperty.call(obj, key)) {
|
||||
obj[key] = val;
|
||||
} else if (angular.isArray(obj[key])) {
|
||||
obj[key].push(val);
|
||||
} else {
|
||||
obj[key] = [obj[key],val];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
function tryDecodeURIComponent(value) {
|
||||
try {
|
||||
return decodeURIComponent(value);
|
||||
} catch (e) {
|
||||
// Ignore any invalid uri component
|
||||
}
|
||||
}
|
||||
return angular.extend(extractParamsFromQuery(queryStr), extractParamsFromPath(strippedUrl));
|
||||
};
|
||||
|
||||
function compareUrlWithQuery(url) {
|
||||
var urlWithQueryRe = /^([^?]*)\?(.*)$/;
|
||||
|
||||
var expectedMatch = urlWithQueryRe.exec(expectedUrl);
|
||||
var actualMatch = urlWithQueryRe.exec(url);
|
||||
|
||||
return !!(expectedMatch && actualMatch) &&
|
||||
(expectedMatch[1] === actualMatch[1]) &&
|
||||
(normalizeQuery(expectedMatch[2]) === normalizeQuery(actualMatch[2]));
|
||||
}
|
||||
|
||||
function normalizeQuery(queryStr) {
|
||||
return queryStr.split('&').sort().join('&');
|
||||
}
|
||||
|
||||
function extractParamsFromPath(strippedUrl) {
|
||||
var keyObj = {};
|
||||
|
||||
if (!expectedUrl || !angular.isFunction(expectedUrl.test) ||
|
||||
!expectedKeys || !expectedKeys.length) return keyObj;
|
||||
|
||||
var match = expectedUrl.exec(strippedUrl);
|
||||
if (!match) return keyObj;
|
||||
|
||||
for (var i = 1, len = match.length; i < len; ++i) {
|
||||
var key = expectedKeys[i - 1];
|
||||
var val = match[i];
|
||||
if (key && val) {
|
||||
keyObj[key.name || key] = val;
|
||||
}
|
||||
}
|
||||
|
||||
return keyObj;
|
||||
}
|
||||
|
||||
function extractParamsFromQuery(queryStr) {
|
||||
var obj = {},
|
||||
keyValuePairs = queryStr.split('&').
|
||||
filter(angular.identity). // Ignore empty segments.
|
||||
map(function(keyValue) { return keyValue.replace(/\+/g, '%20').split('='); });
|
||||
|
||||
angular.forEach(keyValuePairs, function(pair) {
|
||||
var key = tryDecodeURIComponent(pair[0]);
|
||||
if (angular.isDefined(key)) {
|
||||
var val = angular.isDefined(pair[1]) ? tryDecodeURIComponent(pair[1]) : true;
|
||||
if (!hasOwnProperty.call(obj, key)) {
|
||||
obj[key] = val;
|
||||
} else if (angular.isArray(obj[key])) {
|
||||
obj[key].push(val);
|
||||
} else {
|
||||
obj[key] = [obj[key], val];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function tryDecodeURIComponent(value) {
|
||||
try {
|
||||
return decodeURIComponent(value);
|
||||
} catch (e) {
|
||||
// Ignore any invalid uri component
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createMockXhr() {
|
||||
|
||||
@@ -225,6 +225,7 @@ function $RouteProvider() {
|
||||
}
|
||||
routes[path] = angular.extend(
|
||||
routeCopy,
|
||||
{originalPath: path},
|
||||
path && routeToRegExp(path, routeCopy)
|
||||
);
|
||||
|
||||
@@ -235,7 +236,7 @@ function $RouteProvider() {
|
||||
: path + '/';
|
||||
|
||||
routes[redirectPath] = angular.extend(
|
||||
{redirectTo: path},
|
||||
{originalPath: path, redirectTo: path},
|
||||
routeToRegExp(redirectPath, routeCopy)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
/* global routeToRegExp: true */
|
||||
|
||||
/**
|
||||
* @param path {string} path
|
||||
* @param opts {Object} options
|
||||
* @return {?Object}
|
||||
* @param {string} path - The path to parse. (It is assumed to have query and hash stripped off.)
|
||||
* @param {Object} opts - Options.
|
||||
* @return {Object} - An object containing an array of path parameter names (`keys`) and a regular
|
||||
* expression (`regexp`) that can be used to identify a matching URL and extract the path
|
||||
* parameter values.
|
||||
*
|
||||
* @description
|
||||
* Normalizes the given path, returning a regular expression
|
||||
* and the original path.
|
||||
* Parses the given path, extracting path parameter names and a regular expression to match URLs.
|
||||
*
|
||||
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
|
||||
* Originally inspired by `pathRexp` in `visionmedia/express/lib/utils.js`.
|
||||
*/
|
||||
function routeToRegExp(path, opts) {
|
||||
var keys = [];
|
||||
@@ -21,11 +22,11 @@ function routeToRegExp(path, opts) {
|
||||
.replace(/(\/)?:(\w+)(\*\?|[?*])?/g, function(_, slash, key, option) {
|
||||
var optional = option === '?' || option === '*?';
|
||||
var star = option === '*' || option === '*?';
|
||||
keys.push({ name: key, optional: optional });
|
||||
keys.push({name: key, optional: optional});
|
||||
slash = slash || '';
|
||||
return (
|
||||
(optional ? '(?:' + slash : slash + '(?:') +
|
||||
(star ? '([^?#]+?)' : '([^/?#]+)') +
|
||||
(star ? '(.+?)' : '([^/]+)') +
|
||||
(optional ? '?)?' : ')')
|
||||
);
|
||||
})
|
||||
@@ -36,7 +37,6 @@ function routeToRegExp(path, opts) {
|
||||
}
|
||||
|
||||
return {
|
||||
originalPath: path,
|
||||
keys: keys,
|
||||
regexp: new RegExp(
|
||||
'^' + pattern + '(?:[?#]|$)',
|
||||
|
||||
@@ -79,6 +79,42 @@ describe('ngHref', function() {
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
it('should bind numbers', inject(function($rootScope, $compile) {
|
||||
element = $compile('<a ng-href="{{1234}}"></a>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('1234');
|
||||
}));
|
||||
|
||||
|
||||
it('should bind and sanitize the result of a (custom) toString() function', inject(function($rootScope, $compile) {
|
||||
$rootScope.value = {};
|
||||
element = $compile('<a ng-href="{{value}}"></a>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('[object Object]');
|
||||
|
||||
function SafeClass() {}
|
||||
|
||||
SafeClass.prototype.toString = function() {
|
||||
return 'custom value';
|
||||
};
|
||||
|
||||
$rootScope.value = new SafeClass();
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('custom value');
|
||||
|
||||
function UnsafeClass() {}
|
||||
|
||||
UnsafeClass.prototype.toString = function() {
|
||||
return 'javascript:alert(1);';
|
||||
};
|
||||
|
||||
$rootScope.value = new UnsafeClass();
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('unsafe:javascript:alert(1);');
|
||||
}));
|
||||
|
||||
|
||||
if (isDefined(window.SVGElement)) {
|
||||
describe('SVGAElement', function() {
|
||||
it('should interpolate the expression and bind to xlink:href', inject(function($compile, $rootScope) {
|
||||
|
||||
@@ -1530,11 +1530,14 @@ describe('select', function() {
|
||||
['a'],
|
||||
NaN
|
||||
], function(prop) {
|
||||
|
||||
scope.option1 = prop;
|
||||
scope.option2 = 'red';
|
||||
scope.selected = 'NOMATCH';
|
||||
|
||||
compile('<select ng-model="selected">' +
|
||||
'<option ng-value="option1">{{option1}}</option>' +
|
||||
'<option ng-value="option2">{{option2}}</option>' +
|
||||
'</select>');
|
||||
|
||||
scope.$digest();
|
||||
@@ -1571,10 +1574,12 @@ describe('select', function() {
|
||||
NaN
|
||||
], function(prop) {
|
||||
scope.option = prop;
|
||||
scope.option2 = 'red';
|
||||
scope.selected = 'NOMATCH';
|
||||
|
||||
compile('<select ng-model="selected">' +
|
||||
'<option ng-value="option">{{option}}</option>' +
|
||||
'<option ng-value="option2">{{option2}}</option>' +
|
||||
'</select>');
|
||||
|
||||
var selectController = element.controller('select');
|
||||
@@ -1604,7 +1609,7 @@ describe('select', function() {
|
||||
|
||||
expect(scope.selected).toBe(null);
|
||||
expect(element[0].selectedIndex).toBe(0);
|
||||
expect(element.find('option').length).toBe(2);
|
||||
expect(element.find('option').length).toBe(3);
|
||||
expect(element.find('option').eq(0).prop('selected')).toBe(true);
|
||||
expect(element.find('option').eq(0).val()).toBe(unknownValue(prop));
|
||||
expect(element.find('option').eq(1).prop('selected')).toBe(false);
|
||||
@@ -1617,6 +1622,7 @@ describe('select', function() {
|
||||
expect(element.find('option').eq(0).val()).toBe('string:UPDATEDVALUE');
|
||||
});
|
||||
|
||||
|
||||
it('should interact with custom attribute $observe and $set calls', function() {
|
||||
var log = [], optionAttr;
|
||||
|
||||
@@ -1638,26 +1644,43 @@ describe('select', function() {
|
||||
optionAttr.$set('value', 'update');
|
||||
expect(log[1]).toBe('update');
|
||||
expect(element.find('option').eq(1).val()).toBe('string:update');
|
||||
|
||||
});
|
||||
|
||||
it('should ignore the option text / value attribute if the ngValue attribute exists', function() {
|
||||
scope.ngvalue = 'abc';
|
||||
scope.value = 'def';
|
||||
scope.textvalue = 'ghi';
|
||||
|
||||
compile('<select ng-model="x"><option ng-value="ngvalue" value="{{value}}">{{textvalue}}</option></select>');
|
||||
expect(element).toEqualSelect([unknownValue(undefined)], 'string:abc');
|
||||
});
|
||||
it('should ignore the option text / value attribute if the ngValue attribute exists', function() {
|
||||
scope.ngvalue = 'abc';
|
||||
scope.value = 'def';
|
||||
scope.textvalue = 'ghi';
|
||||
|
||||
it('should ignore option text with multiple interpolations if the ngValue attribute exists', function() {
|
||||
scope.ngvalue = 'abc';
|
||||
scope.textvalue = 'def';
|
||||
scope.textvalue2 = 'ghi';
|
||||
compile('<select ng-model="x"><option ng-value="ngvalue" value="{{value}}">{{textvalue}}</option></select>');
|
||||
expect(element).toEqualSelect([unknownValue(undefined)], 'string:abc');
|
||||
});
|
||||
|
||||
|
||||
it('should ignore option text with multiple interpolations if the ngValue attribute exists', function() {
|
||||
scope.ngvalue = 'abc';
|
||||
scope.textvalue = 'def';
|
||||
scope.textvalue2 = 'ghi';
|
||||
|
||||
compile('<select ng-model="x"><option ng-value="ngvalue">{{textvalue}} {{textvalue2}}</option></select>');
|
||||
expect(element).toEqualSelect([unknownValue(undefined)], 'string:abc');
|
||||
});
|
||||
|
||||
|
||||
it('should select the first option if it is `undefined`', function() {
|
||||
scope.selected = undefined;
|
||||
|
||||
scope.option1 = undefined;
|
||||
scope.option2 = 'red';
|
||||
|
||||
compile('<select ng-model="selected">' +
|
||||
'<option ng-value="option1">{{option1}}</option>' +
|
||||
'<option ng-value="option2">{{option2}}</option>' +
|
||||
'</select>');
|
||||
|
||||
expect(element).toEqualSelect(['undefined:undefined'], 'string:red');
|
||||
});
|
||||
|
||||
compile('<select ng-model="x"><option ng-value="ngvalue">{{textvalue}} {{textvalue2}}</option></select>');
|
||||
expect(element).toEqualSelect([unknownValue(undefined)], 'string:abc');
|
||||
});
|
||||
|
||||
describe('and select[multiple]', function() {
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('ngAnimate integration tests', function() {
|
||||
ss.destroy();
|
||||
});
|
||||
|
||||
|
||||
it('should cancel a running and started removeClass animation when a follow-up addClass animation adds the same class',
|
||||
inject(function($animate, $rootScope, $$rAF, $document, $rootElement) {
|
||||
|
||||
@@ -362,6 +363,7 @@ describe('ngAnimate integration tests', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should add the preparation class for an enter animation before a parent class-based animation is applied', function() {
|
||||
module('ngAnimateMock');
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $document) {
|
||||
@@ -397,6 +399,7 @@ describe('ngAnimate integration tests', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should avoid adding the ng-enter-prepare method to a parent structural animation that contains child animations', function() {
|
||||
module('ngAnimateMock');
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $document, $$rAF) {
|
||||
@@ -468,6 +471,84 @@ describe('ngAnimate integration tests', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should remove the prepare classes when different structural animations happen in the same digest', function() {
|
||||
module('ngAnimateMock');
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $document, $$animateCache) {
|
||||
element = jqLite(
|
||||
// Class animation on parent element is neeeded so the child elements get the prepare class
|
||||
'<div id="outer" ng-class="{blue: cond}" ng-switch="cond">' +
|
||||
'<div id="default" ng-switch-default></div>' +
|
||||
'<div id="truthy" ng-switch-when="true"></div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
$compile(element)($rootScope);
|
||||
$rootScope.cond = false;
|
||||
$rootScope.$digest();
|
||||
|
||||
$rootScope.cond = true;
|
||||
$rootScope.$digest();
|
||||
|
||||
var parent = element;
|
||||
var truthySwitch = jqLite(parent[0].querySelector('#truthy'));
|
||||
var defaultSwitch = jqLite(parent[0].querySelector('#default'));
|
||||
|
||||
expect(parent).not.toHaveClass('blue');
|
||||
expect(parent).toHaveClass('blue-add');
|
||||
expect(truthySwitch).toHaveClass('ng-enter-prepare');
|
||||
expect(defaultSwitch).toHaveClass('ng-leave-prepare');
|
||||
|
||||
$animate.flush();
|
||||
|
||||
expect(parent).toHaveClass('blue');
|
||||
expect(parent).not.toHaveClass('blue-add');
|
||||
expect(truthySwitch).not.toHaveClass('ng-enter-prepare');
|
||||
expect(defaultSwitch).not.toHaveClass('ng-leave-prepare');
|
||||
});
|
||||
});
|
||||
|
||||
it('should respect the element node for caching when animations with the same type happen in the same digest', function() {
|
||||
module('ngAnimateMock');
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $document, $$animateCache) {
|
||||
ss.addRule('.animate.ng-enter', 'transition:2s linear all;');
|
||||
|
||||
element = jqLite(
|
||||
'<div>' +
|
||||
'<div>' +
|
||||
'<div id="noanimate" ng-if="cond"></div>' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<div id="animate" class="animate" ng-if="cond"></div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
$compile(element)($rootScope);
|
||||
$rootScope.cond = true;
|
||||
$rootScope.$digest();
|
||||
|
||||
var parent = element;
|
||||
var noanimate = jqLite(parent[0].querySelector('#noanimate'));
|
||||
var animate = jqLite(parent[0].querySelector('#animate'));
|
||||
|
||||
expect(noanimate).not.toHaveClass('ng-enter');
|
||||
expect(animate).toHaveClass('ng-enter');
|
||||
|
||||
$animate.closeAndFlush();
|
||||
|
||||
expect(noanimate).not.toHaveClass('ng-enter');
|
||||
expect(animate).not.toHaveClass('ng-enter');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should pack level elements into their own RAF flush', function() {
|
||||
module('ngAnimateMock');
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $document) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/* globals nodeBlackList false */
|
||||
|
||||
describe('$aria', function() {
|
||||
var scope, $compile, element;
|
||||
|
||||
@@ -952,6 +954,33 @@ describe('$aria', function() {
|
||||
expect(clickEvents).toEqual(['div(true)', 'li(true)', 'div(true)', 'li(true)']);
|
||||
});
|
||||
|
||||
they('should not prevent default keyboard action if an interactive $type element' +
|
||||
'is nested inside ng-click', nodeBlackList, function(elementType) {
|
||||
function createHTML(type) {
|
||||
return '<' + type + '></' + type + '>';
|
||||
}
|
||||
|
||||
compileElement(
|
||||
'<section>' +
|
||||
'<div ng-click="onClick($event)">' + createHTML(elementType) + '</div>' +
|
||||
'</section>');
|
||||
|
||||
var divElement = element.find('div');
|
||||
var interactiveElement = element.find(elementType);
|
||||
|
||||
// Use browserTrigger because it supports event bubbling
|
||||
// 13 Enter
|
||||
browserTrigger(interactiveElement, 'keydown', {cancelable: true, bubbles: true, keyCode: 13});
|
||||
expect(clickEvents).toEqual([elementType.toLowerCase() + '(false)']);
|
||||
|
||||
clickEvents = [];
|
||||
|
||||
// 32 Space
|
||||
browserTrigger(interactiveElement, 'keydown', {cancelable: true, bubbles: true, keyCode: 32});
|
||||
expect(clickEvents).toEqual([elementType.toLowerCase() + '(false)']);
|
||||
}
|
||||
);
|
||||
|
||||
it('should trigger a click in browsers that provide `event.which` instead of `event.keyCode`',
|
||||
function() {
|
||||
compileElement(
|
||||
|
||||
Vendored
+3
-3
@@ -2251,7 +2251,7 @@ describe('ngMock', function() {
|
||||
}
|
||||
);
|
||||
they('should ignore query params when matching in ' + routeShortcut + ' $prop method', methods,
|
||||
function() {
|
||||
function(method) {
|
||||
angular.forEach([
|
||||
{route: '/route1/:id', url: '/route1/Alpha', expectedParams: {id: 'Alpha'}},
|
||||
{route: '/route2/:id', url: '/route2/Bravo/?', expectedParams: {id: 'Bravo'}},
|
||||
@@ -2268,14 +2268,14 @@ describe('ngMock', function() {
|
||||
], function(testDataEntry) {
|
||||
callback.calls.reset();
|
||||
var paramsSpy = jasmine.createSpy('params');
|
||||
hb[routeShortcut](this, testDataEntry.route).respond(
|
||||
hb[routeShortcut](method, testDataEntry.route).respond(
|
||||
function(method, url, data, headers, params) {
|
||||
paramsSpy(params);
|
||||
// status, response, headers, statusText, xhrStatus
|
||||
return [200, 'path', { 'x-header': 'foo' }, 'OK', 'complete'];
|
||||
}
|
||||
);
|
||||
hb(this, testDataEntry.url, undefined, callback);
|
||||
hb(method, testDataEntry.url, undefined, callback);
|
||||
hb.flush();
|
||||
expect(callback).toHaveBeenCalledOnceWith(200, 'path', 'x-header: foo', 'OK', 'complete');
|
||||
expect(paramsSpy).toHaveBeenCalledOnceWith(testDataEntry.expectedParams);
|
||||
|
||||
@@ -77,5 +77,45 @@ describe('$routeParams', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly extract path params containing hashes and/or question marks', function() {
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/foo/:bar', {});
|
||||
$routeProvider.when('/zoo/:bar/:baz/:qux', {});
|
||||
});
|
||||
|
||||
inject(function($location, $rootScope, $routeParams) {
|
||||
$location.path('/foo/bar?baz');
|
||||
$rootScope.$digest();
|
||||
expect($routeParams).toEqual({bar: 'bar?baz'});
|
||||
|
||||
$location.path('/foo/bar?baz=val');
|
||||
$rootScope.$digest();
|
||||
expect($routeParams).toEqual({bar: 'bar?baz=val'});
|
||||
|
||||
$location.path('/foo/bar#baz');
|
||||
$rootScope.$digest();
|
||||
expect($routeParams).toEqual({bar: 'bar#baz'});
|
||||
|
||||
$location.path('/foo/bar?baz#qux');
|
||||
$rootScope.$digest();
|
||||
expect($routeParams).toEqual({bar: 'bar?baz#qux'});
|
||||
|
||||
$location.path('/foo/bar?baz=val#qux');
|
||||
$rootScope.$digest();
|
||||
expect($routeParams).toEqual({bar: 'bar?baz=val#qux'});
|
||||
|
||||
$location.path('/foo/bar#baz?qux');
|
||||
$rootScope.$digest();
|
||||
expect($routeParams).toEqual({bar: 'bar#baz?qux'});
|
||||
|
||||
$location.path('/zoo/bar?p1=v1#h1/baz?p2=v2#h2/qux?p3=v3#h3');
|
||||
$rootScope.$digest();
|
||||
expect($routeParams).toEqual({
|
||||
bar: 'bar?p1=v1#h1',
|
||||
baz: 'baz?p2=v2#h2',
|
||||
qux: 'qux?p3=v3#h3'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1727,9 +1727,9 @@ detective@^4.0.0:
|
||||
acorn "^3.1.0"
|
||||
defined "^1.0.0"
|
||||
|
||||
dgeni-packages@^0.26.2:
|
||||
version "0.26.2"
|
||||
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.26.2.tgz#dac22d7e861d4d72ed42af5272714f42b6b5bf3d"
|
||||
dgeni-packages@^0.26.5:
|
||||
version "0.26.5"
|
||||
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.26.5.tgz#a76da27b40ce92dfc37a9e629ef9f1d3897f6bde"
|
||||
dependencies:
|
||||
canonical-path "0.0.2"
|
||||
catharsis "^0.8.1"
|
||||
|
||||
Reference in New Issue
Block a user