Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b586bfdfab | |||
| 1d69015e3d | |||
| f13c33bf10 | |||
| ba62e975f1 | |||
| b89d941cdf | |||
| 07fa87a8a8 | |||
| 0af70eb99e | |||
| cb713e6045 | |||
| 89ca859734 | |||
| bc6fb7cc94 | |||
| 0c80df21b6 | |||
| 3141dbf179 | |||
| aad502bad8 | |||
| dca8972367 | |||
| 284de57435 | |||
| f780ccfa1c | |||
| 55f99e0710 | |||
| 1a99ca9c08 | |||
| 37500fca83 | |||
| 4fe4fc5abf | |||
| 74e1cc683b | |||
| a4faa5cde7 | |||
| 32cb40b86d | |||
| d1cd677433 | |||
| 43c735a816 | |||
| ab2e83c8c8 | |||
| e5f454c8af | |||
| 67c11b9a39 | |||
| 5a306b7ba3 | |||
| 8ce61bf178 | |||
| 9d452bc845 | |||
| 192fecc790 | |||
| 32be6369e4 | |||
| 2a45cea0ba | |||
| ea653e4cdd | |||
| 03777445e8 | |||
| 8b25ea129a | |||
| d71f16e745 | |||
| ed59370d80 | |||
| d8e5acfe27 | |||
| af59f4e69a | |||
| 24aee81634 | |||
| f81d56e66c | |||
| f0904cf12e | |||
| 81b7e5ab0e | |||
| 1b1890274e | |||
| 6d418ef5e3 | |||
| 3fa1606c43 | |||
| 8661a9e3d4 | |||
| cf63292742 | |||
| fd420c4061 | |||
| 1382d4e88e | |||
| b9ddef2a49 | |||
| eafba9e2e5 | |||
| 6f1d9f8ca6 | |||
| bb9310974b | |||
| 30279d7b9b | |||
| 8df5f3259a | |||
| 14e797c1a1 | |||
| 82cd6b87f0 | |||
| 6d7cc572b5 | |||
| 2ebbe00eb5 | |||
| 8b86d363aa | |||
| 9b51067516 | |||
| a3208bf66e | |||
| 4e1fb82628 | |||
| ad466412c6 | |||
| 299a32740c | |||
| eb799bcb71 | |||
| 7314c1b69e | |||
| fcfe2b3793 | |||
| 5e140a99c7 | |||
| 4da169d15d | |||
| f37c6f9f73 | |||
| eae658fd96 | |||
| 286f269753 | |||
| 73640a6b7c | |||
| 2dc55ff5c2 | |||
| 98a2563ec4 | |||
| 8c02122837 | |||
| 1e069532fc | |||
| 6f6f7e82a4 | |||
| d852122442 | |||
| 66cb161221 | |||
| 6c14fb1eb6 | |||
| e906aafb0a |
+46
-3
@@ -1,3 +1,49 @@
|
||||
<a name="1.2.18"></a>
|
||||
# 1.2.18 ear-extendability (2014-06-13)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- ensure transclude works at root of templateUrl
|
||||
([fd420c40](https://github.com/angular/angular.js/commit/fd420c40613d02b3a3f7b14d00a98664518c28f0),
|
||||
[#7183](https://github.com/angular/angular.js/issues/7183), [#7772](https://github.com/angular/angular.js/issues/7772))
|
||||
- bound transclusion to correct scope
|
||||
([1382d4e8](https://github.com/angular/angular.js/commit/1382d4e88ec486b7749e45e6ccc864b3ec388cfe))
|
||||
- don't pass transcludes to non-transclude templateUrl directives
|
||||
([b9ddef2a](https://github.com/angular/angular.js/commit/b9ddef2a495b44cb5fe678b8753de0b7a369244d))
|
||||
- don't pass transclude to template of non-transclude directive
|
||||
([eafba9e2](https://github.com/angular/angular.js/commit/eafba9e2e5ddc668c534e930d83031d2e8dc32b9))
|
||||
- fix nested isolated transclude directives
|
||||
([bb931097](https://github.com/angular/angular.js/commit/bb9310974b6765c2b87e74ee7b8485a6e9c24740),
|
||||
[#1809](https://github.com/angular/angular.js/issues/1809), [#7499](https://github.com/angular/angular.js/issues/7499))
|
||||
- pass transcludeFn down to nested transclude directives
|
||||
([8df5f325](https://github.com/angular/angular.js/commit/8df5f3259aa776f28bf3d869fb1c03e10a897c84),
|
||||
[#7240](https://github.com/angular/angular.js/issues/7240), [#7387](https://github.com/angular/angular.js/issues/7387))
|
||||
- **$injector:** report circularity in circular dependency error message
|
||||
([14e797c1](https://github.com/angular/angular.js/commit/14e797c1a10eabd15bf8e845b62213398bcc0f58),
|
||||
[#7500](https://github.com/angular/angular.js/issues/7500))
|
||||
- **ngResource:** don't convert literal values into Resource objects when isArray is true
|
||||
([f0904cf1](https://github.com/angular/angular.js/commit/f0904cf12e4f01daa2d4fcbb20c762050125ca55),
|
||||
[#6314](https://github.com/angular/angular.js/issues/6314), [#7741](https://github.com/angular/angular.js/issues/7741))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** move ng-binding class stamping for interpolation into compile phase
|
||||
([81b7e5ab](https://github.com/angular/angular.js/commit/81b7e5ab0ee3fea410b16b09144359ceb99f5191))
|
||||
- **$http:** move xsrf cookie check to after cache check in $http
|
||||
([8b86d363](https://github.com/angular/angular.js/commit/8b86d363aa252c3264201b54b57c3e34f9632d45),
|
||||
[#7717](https://github.com/angular/angular.js/issues/7717))
|
||||
- **isArray:** use native Array.isArray
|
||||
([6c14fb1e](https://github.com/angular/angular.js/commit/6c14fb1eb61dc0a0552fbcb2ca3ace11c9a2f6a5))
|
||||
- **jqLite:** cache collection length for all methods that work on a single element
|
||||
([6d418ef5](https://github.com/angular/angular.js/commit/6d418ef5e3a775577996caf0709f79f447f77025))
|
||||
- **ngBind:** set the ng-binding class during compilation instead of linking
|
||||
([1b189027](https://github.com/angular/angular.js/commit/1b1890274e5a75553ddf9915bb23da48800275f9))
|
||||
|
||||
|
||||
|
||||
<a name="1.2.17"></a>
|
||||
# 1.2.17 - quantum disentanglement (2014-06-06)
|
||||
|
||||
@@ -80,9 +126,6 @@
|
||||
|
||||
## Features
|
||||
|
||||
- **injector:** "strict-DI" mode which disables "automatic" function annotation
|
||||
([f5a04f59](https://github.com/angular/angular.js/commit/f5a04f59cf8e8dd6d1806059e3d7fe440aa1613e),
|
||||
[#6719](https://github.com/angular/angular.js/issues/6719), [#6717](https://github.com/angular/angular.js/issues/6717), [#4504](https://github.com/angular/angular.js/issues/4504), [#6069](https://github.com/angular/angular.js/issues/6069), [#3611](https://github.com/angular/angular.js/issues/3611))
|
||||
- **ngMock:** add support of mocha tdd interface
|
||||
([6d1c6772](https://github.com/angular/angular.js/commit/6d1c67727ab872c44addc783ef1406952142d89e),
|
||||
[#7489](https://github.com/angular/angular.js/issues/7489))
|
||||
|
||||
+1
-1
@@ -269,6 +269,6 @@ You can find out more detailed information about contributing in the
|
||||
[ngDocs]: https://github.com/angular/angular.js/wiki/Writing-AngularJS-Documentation
|
||||
[plunker]: http://plnkr.co/edit
|
||||
[stackoverflow]: http://stackoverflow.com/questions/tagged/angularjs
|
||||
[unit-testing]: http://docs.angularjs.org/guide/dev_guide.unit-testing
|
||||
[unit-testing]: https://docs.angularjs.org/guide/unit-testing
|
||||
|
||||
[](https://github.com/igrigorik/ga-beacon)
|
||||
|
||||
+4
-1
@@ -220,8 +220,11 @@ module.exports = function(grunt) {
|
||||
|
||||
"ddescribe-iit": {
|
||||
files: [
|
||||
'src/**/*.js',
|
||||
'test/**/*.js',
|
||||
'!test/ngScenario/DescribeSpec.js'
|
||||
'!test/ngScenario/DescribeSpec.js',
|
||||
'!src/ng/directive/booleanAttrs.js', // legitimate xit here
|
||||
'!src/ngScenario/**/*.js'
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ This process based on the idea of minimizing user pain
|
||||
* Check if there are comments that link to a dupe. If so verify that this is indeed a dupe, [close it][], and go to the last step.
|
||||
1. Bugs:
|
||||
* Label `Type: Bug`
|
||||
* Reproducible? - Steps to reproduce the bug are clear. If they are not,
|
||||
* Reproducible? - Steps to reproduce the bug are clear. If they are not, ask for a clarification. If there's no reply after a week, [close it][].
|
||||
* Reproducible on master? - <http://code.angularjs.org/snapshot/>
|
||||
|
||||
1. Non bugs:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Welcome to the AngularJS API docs page. These pages contain the AngularJS reference materials for version <strong ng-bind="version"></strong>.
|
||||
|
||||
The documentation is organized into **{@link guide/module modules}** which contain various components of an AngularJS application.
|
||||
These components are {@link guide/directive directives}, {@link guide/services services}, {@link guide/filter filters}, {@link guide/providers providers}, {@link guide/templates templates}, global APIs and testing mocks.
|
||||
These components are {@link guide/directive directives}, {@link guide/services services}, {@link guide/filter filters}, {@link guide/providers providers}, {@link guide/templates templates}, global APIs, and testing mocks.
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Angular Namespaces `$` and `$$`**
|
||||
@@ -212,7 +212,7 @@ Use ngTouch when developing for mobile browsers/devices.
|
||||
{@link ngTouch#service Services / Factories}
|
||||
</td>
|
||||
<td>
|
||||
The {@link ngTouch.$swipe $swipe} service is used to register and manage mobile DOM events.
|
||||
The {@link ngTouch.$swipe $swipe} service is used to register and manage mobile DOM events.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -252,7 +252,7 @@ Use ngSanitize to securely parse and manipulate HTML data in your application.
|
||||
|
||||
## {@link ngMock ngMock}
|
||||
|
||||
Use ngMock to inject and mock modules, factories, services and providers within your unit tests
|
||||
Use ngMock to inject and mock modules, factories, services and providers within your unit tests
|
||||
|
||||
<div class="alert alert-info">Include the **angular-mocks.js** file into your test runner for this to work.</div>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ myModule.directive('myDirective', function factory() {
|
||||
return {
|
||||
...
|
||||
scope: {
|
||||
'bind': '=localValue'
|
||||
localValue: '=bind'
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
@@ -37,3 +37,17 @@ elements. For example:
|
||||
```
|
||||
<b>Hello</b> World!
|
||||
```
|
||||
|
||||
Watch out for html comments at the beginning or end of templates, as these can cause this error as
|
||||
well. Consider the following template:
|
||||
|
||||
```
|
||||
<div class='container'>
|
||||
<div class='wrapper>
|
||||
...
|
||||
</div> <!-- wrapper -->
|
||||
</div> <!-- container -->
|
||||
```
|
||||
|
||||
The `<!-- container -->` comment is interpreted as a second root element and causes the template to
|
||||
be invalid.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecff
|
||||
@fullName Referencing 'call', 'apply' and 'bind' Disallowed
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to invoke Function's 'call', 'apply' or 'bind'.
|
||||
|
||||
Angular bans the invocation of 'call', 'apply' and 'bind' from within expressions
|
||||
since access is a known way to modify the behaviour of existing functions.
|
||||
|
||||
To resolve this error, avoid using these methods in expressions.
|
||||
|
||||
Example expression that would result in this error:
|
||||
|
||||
```
|
||||
<div>{{user.sendInfo.call({}, true)}}</div>
|
||||
```
|
||||
@@ -1,18 +1,27 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecfld
|
||||
@fullName Referencing 'constructor' Field in Expression
|
||||
@fullName Referencing Disallowed Field in Expression
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to access an objects constructor field.
|
||||
Occurs when an expression attempts to access one of the following fields:
|
||||
|
||||
AngularJS bans constructor access from within expressions since constructor
|
||||
access is a known way to execute arbitrary Javascript code.
|
||||
* __proto__
|
||||
* __defineGetter__
|
||||
* __defineSetter__
|
||||
* __lookupGetter__
|
||||
* __lookupSetter__
|
||||
|
||||
To resolve this error, avoid constructor access. As a last resort, alias
|
||||
the constructor and access it through the alias instead.
|
||||
AngularJS bans access to these fields from within expressions since
|
||||
access is a known way to mess with native objects or
|
||||
to execute arbitrary Javascript code.
|
||||
|
||||
Example expression that would result in this error:
|
||||
To resolve this error, avoid using these fields in expressions. As a last resort,
|
||||
alias their value and access them through the alias instead.
|
||||
|
||||
Example expressions that would result in this error:
|
||||
|
||||
```
|
||||
<div>{{user.constructor.name}}</div>
|
||||
```
|
||||
<div>{{user.__proto__.hasOwnProperty = $emit}}</div>
|
||||
|
||||
<div>{{user.__defineGetter__('name', noop)}}</div>
|
||||
```
|
||||
@@ -3,72 +3,310 @@
|
||||
@fullName Action Already In Progress
|
||||
@description
|
||||
|
||||
At any point in time there can be only one `$digest` or $apply operation in progress.
|
||||
The stack trace of this error allows you to trace the origin of the currently executing $apply or $digest call.
|
||||
At any point in time there can be only one `$digest` or `$apply` operation in progress. This is to
|
||||
prevent very hard to detect bugs from entering your application. The stack trace of this error
|
||||
allows you to trace the origin of the currently executing `$apply` or `$digest` call, which caused
|
||||
the error.
|
||||
|
||||
`$digest` or `$apply` are processing operational states of the Scope - data-structure in Angular that provides context for models and enables model mutation observation.
|
||||
## Background
|
||||
|
||||
Trying to reenter a `$digest` or `$apply` while one of them is already in progress is typically a sign of programming error that needs to be fixed.
|
||||
Angular uses a dirty-checking digest mechanism to monitor and update values of the scope during
|
||||
the processing of your application. The digest works by checking all the values that are being
|
||||
watched against their previous value and running any watch handlers that have been defined for those
|
||||
values that have changed.
|
||||
|
||||
This digest mechanism is triggered by calling `$digest` on a scope object. Normally you do not need
|
||||
to trigger a digest manually, because every external action that can trigger changes in your
|
||||
application, such as mouse events, timeouts or server responses, wrap the Angular application code
|
||||
in a block of code that will run `$digest` when the code completes.
|
||||
|
||||
You wrap Angular code in a block that will be followed by a `$digest` by calling `$apply` on a scope
|
||||
object. So, in pseudo-code, the process looks like this:
|
||||
|
||||
```
|
||||
element.on('mouseup', function() {
|
||||
scope.$apply(function() {
|
||||
$scope.doStuff();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
where `$apply()` looks something like:
|
||||
|
||||
```
|
||||
$apply = function(fn) {
|
||||
try {
|
||||
fn();
|
||||
} finally() {
|
||||
$digest();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Digest Phases
|
||||
|
||||
Angular keeps track of what phase of processing we are in, the relevant ones being `$apply` and
|
||||
`$digest`. Trying to reenter a `$digest` or `$apply` while one of them is already in progress is
|
||||
typically a sign of programming error that needs to be fixed. So Angular will throw this error when
|
||||
that occurs.
|
||||
|
||||
In most situations it should be well defined whether a piece of code will be run inside an `$apply`,
|
||||
in which case you should not be calling `$apply` or `$digest`, or it will be run outside, in which
|
||||
case you should wrap any code that will be interacting with Angular scope or services, in a call to
|
||||
`$apply`.
|
||||
|
||||
As an example, all Controller code should expect to be run within Angular, so it should have no need
|
||||
to call `$apply` or `$digest`. Conversely, code that is being trigger directly as a call back to
|
||||
some external event, from the DOM or 3rd party library, should expect that it is never called from
|
||||
within Angular, and so any Angular application code that it calls should first be wrapped in a call
|
||||
to $apply.
|
||||
|
||||
## Common Causes
|
||||
|
||||
Apart from simply incorrect calls to `$apply` or `$digest` there are some cases when you may get
|
||||
this error through no fault of your own.
|
||||
|
||||
### Inconsistent API (Sync/Async)
|
||||
|
||||
This error is often seen when interacting with an API that is sometimes sync and sometimes async.
|
||||
|
||||
For example:
|
||||
For example, imagine a 3rd party library that has a method which will retrieve data for us. Since it
|
||||
may be making an asynchronous call to a server, it accepts a callback function, which will be called
|
||||
when the data arrives.
|
||||
|
||||
```
|
||||
function MyController() {
|
||||
function MyController($scope, thirdPartyComponent) {
|
||||
thirdPartyComponent.getData(function(someData) {
|
||||
scope.$apply(function() {
|
||||
scope.someData = someData;
|
||||
$scope.$apply(function() {
|
||||
$scope.someData = someData;
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The controller constructor is always instantiated from within an $apply cycle, so if the third-party component called our callback synchronously, we'd be trying to enter the $apply again.
|
||||
We expect that our callback will be called asynchronously, and so from outside Angular. Therefore, we
|
||||
correctly wrap our application code that interacts with Angular in a call to `$apply`.
|
||||
|
||||
To resolve this type of issue, either fix the api to be always synchronous or asynchronous or wrap the call to the api with setTimeout call to make it always asynchronous.
|
||||
The problem comes if `getData()` decides to call the callback handler synchronously; perhaps it has
|
||||
the data already cached in memory and so it immediately calls the callback to return the data,
|
||||
synchronously.
|
||||
|
||||
Since, the `MyController` constructor is always instantiated from within an `$apply` call, our
|
||||
handler is trying to enter a new `$apply` block from within one.
|
||||
|
||||
Other situation that leads to this error is when you are trying to reuse a function to by using it as a callback for code that is called by various apis inside and outside of $apply.
|
||||
This is not an ideal design choice on the part of the 3rd party library.
|
||||
|
||||
For example:
|
||||
To resolve this type of issue, either fix the api to be always synchronous or asynchronous or force
|
||||
your callback handler to always run asynchronously by using the `$timeout` service.
|
||||
|
||||
```
|
||||
myApp.directive('myDirective', function() {
|
||||
function MyController($scope, thirdPartyComponent) {
|
||||
thirdPartyComponent.getData(function(someData) {
|
||||
$timeout(function() {
|
||||
$scope.someData = someData;
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Here we have used `$timeout` to schedule the changes to the scope in a future call stack.
|
||||
By providing a timeout period of 0ms, this will occur as soon as possible and `$timeout` will ensure
|
||||
that the code will be called in a single `$apply` block.
|
||||
|
||||
### Triggering Events Programmatically
|
||||
|
||||
The other situation that often leads to this error is when you trigger code (such as a DOM event)
|
||||
programmatically (from within Angular), which is normally called by an external trigger.
|
||||
|
||||
For example, consider a directive that will set focus on an input control when a value in the scope
|
||||
is true:
|
||||
|
||||
```
|
||||
myApp.directive('setFocusIf', function() {
|
||||
return {
|
||||
link: function($scope, $element) {
|
||||
function doSomeWork() {
|
||||
$scope.$apply(function() {
|
||||
// do work here, and update the model
|
||||
};
|
||||
}
|
||||
|
||||
$element.on('click', doSomeWork);
|
||||
doSomeWork(); // << this will throw an exception because templates are compiled within $apply
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
The fix for the example above looks like this:
|
||||
```
|
||||
myApp.directive('myDirective', function() {
|
||||
return {
|
||||
link: function($scope, $element) {
|
||||
function doSomeWork() {
|
||||
// do work here, and update the model
|
||||
}
|
||||
|
||||
$element.on('click', function() {
|
||||
$scope.$apply(doSomeWork); // <<< the $apply call was moved to the callsite that doesn't execute in $apply call already
|
||||
link: function($scope, $element, $attr) {
|
||||
$scope.$watch($attr.setFocusIf, function(value) {
|
||||
if ( value ) { $element[0].focus(); }
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
doSomeWork();
|
||||
If we applied this directive to an input which also used the `ngFocus` directive to trigger some
|
||||
work when the element receives focus we will have a problem:
|
||||
|
||||
```
|
||||
<input set-focus-if="hasFocus" ng-focus="msg='has focus'">
|
||||
<button ng-click="hasFocus = true">Focus</button>
|
||||
```
|
||||
|
||||
In this setup, there are two ways to trigger ngFocus. First from a user interaction:
|
||||
|
||||
* Click on the input control
|
||||
* The input control gets focus
|
||||
* The `ngFocus` directive is triggered, setting `$scope.msg='has focus'` from within a new call to
|
||||
`$apply()`
|
||||
|
||||
Second programmatically:
|
||||
|
||||
* Click the button
|
||||
* The `ngClick` directive sets the value of `$scope.hasFocus` to true inside a call to `$apply`
|
||||
* The `$digest` runs, which triggers the watch inside the `setFocusIf` directive
|
||||
* The watch's handle runs, which gives the focus to the input
|
||||
* The `ngFocus` directive is triggered, setting `$scope.msg='has focus'` from within a new call to
|
||||
`$apply()`
|
||||
|
||||
In this second scenario, we are already inside a `$digest` when the ngFocus directive makes another
|
||||
call to `$apply()`, causing this error to be thrown.
|
||||
|
||||
It is possible to workaround this problem by moving the call to set the focus outside of the digest,
|
||||
by using `$timeOut(fn, 0, false)`, where the `false` value tells Angular not to wrap this `fn` in a
|
||||
`$apply` block:
|
||||
|
||||
```
|
||||
myApp.directive('setFocusIf', function($timeout) {
|
||||
return {
|
||||
link: function($scope, $element, $attr) {
|
||||
$scope.$watch($attr.setFocusIf, function(value) {
|
||||
if ( value ) {
|
||||
$timeout(function() {
|
||||
// We must reevaluate the value in case it was changed by a subsequent
|
||||
// watch handler in the digest.
|
||||
if ( $scope.$eval($attr.setFocusIf) ) {
|
||||
$element[0].focus();
|
||||
}
|
||||
}, 0, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
To learn more about Angular processing model please check out the {@link guide/concepts concepts doc} as well as the {@link ng.$rootScope.Scope api} doc.
|
||||
## Diagnosing This Error
|
||||
|
||||
When you get this error it can be rather daunting to diagnose the cause of the issue. The best
|
||||
course of action is to investigate the stack trace from the error. You need to look for places
|
||||
where `$apply` or `$digest` have been called and find the context in which this occurred.
|
||||
|
||||
There should be two calls:
|
||||
|
||||
* The first call is the good `$apply`/`$digest` and would normally be triggered by some event near
|
||||
the top of the call stack.
|
||||
|
||||
* The second call is the bad `$apply`/`$digest` and this is the one to investigate.
|
||||
|
||||
Once you have identified this call you work your way up the stack to see what the problem is.
|
||||
|
||||
* If the second call was made in your application code then you should look at why this code has been
|
||||
called from within a `$apply`/`$digest`. It may be a simple oversight or maybe it fits with the
|
||||
sync/async scenario described earlier.
|
||||
|
||||
* If the second call was made inside an Angular directive then it is likely that it matches the second
|
||||
programmatic event trigger scenario described earlier. In this case you may need to look further up
|
||||
the tree to what triggered the event in the first place.
|
||||
|
||||
### Example Problem
|
||||
|
||||
Let's look at how to investigate this error using the `setFocusIf` example from above. This example
|
||||
defines a new `setFocusIf` directive that sets the focus on the element where it is defined when the
|
||||
value of its attribute becomes true.
|
||||
|
||||
<example name="error-$rootScope-inprog" module="app">
|
||||
<file name="index.html">
|
||||
<button ng-click="focusInput = true">Focus</button>
|
||||
<input ng-focus="count = count + 1" set-focus-if="focusInput" />
|
||||
</file>
|
||||
<file name="app.js">
|
||||
angular.module('app', []).directive('setFocusIf', function() {
|
||||
return function link($scope, $element, $attr) {
|
||||
$scope.$watch($attr.setFocusIf, function(value) {
|
||||
if ( value ) { $element[0].focus(); }
|
||||
});
|
||||
};
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
|
||||
When you click on the button to cause the focus to occur we get our `$rootScope:inprog` error. The
|
||||
stacktrace looks like this:
|
||||
|
||||
```
|
||||
Error: [$rootScope:inprog]
|
||||
at Error (native)
|
||||
at angular.min.js:6:467
|
||||
at n (angular.min.js:105:60)
|
||||
at g.$get.g.$apply (angular.min.js:113:195)
|
||||
at HTMLInputElement.<anonymous> (angular.min.js:198:401)
|
||||
at angular.min.js:32:32
|
||||
at Array.forEach (native)
|
||||
at q (angular.min.js:7:295)
|
||||
at HTMLInputElement.c (angular.min.js:32:14)
|
||||
at Object.fn (app.js:12:38) angular.js:10111
|
||||
(anonymous function) angular.js:10111
|
||||
$get angular.js:7412
|
||||
$get.g.$apply angular.js:12738 <--- $apply
|
||||
(anonymous function) angular.js:19833 <--- called here
|
||||
(anonymous function) angular.js:2890
|
||||
q angular.js:320
|
||||
c angular.js:2889
|
||||
(anonymous function) app.js:12
|
||||
$get.g.$digest angular.js:12469
|
||||
$get.g.$apply angular.js:12742 <--- $apply
|
||||
(anonymous function) angular.js:19833 <--- called here
|
||||
(anonymous function) angular.js:2890
|
||||
q angular.js:320
|
||||
```
|
||||
|
||||
We can see (even though the Angular code is minified) that there were two calls to `$apply`, first
|
||||
on line `19833`, then on line `12738` of `angular.js`.
|
||||
|
||||
It is this second call that caused the error. If we look at the angular.js code, we can see that
|
||||
this call is made by an Angular directive.
|
||||
|
||||
```
|
||||
var ngEventDirectives = {};
|
||||
forEach(
|
||||
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
|
||||
function(name) {
|
||||
var directiveName = directiveNormalize('ng-' + name);
|
||||
ngEventDirectives[directiveName] = ['$parse', function($parse) {
|
||||
return {
|
||||
compile: function($element, attr) {
|
||||
var fn = $parse(attr[directiveName]);
|
||||
return function(scope, element, attr) {
|
||||
element.on(lowercase(name), function(event) {
|
||||
scope.$apply(function() {
|
||||
fn(scope, {$event:event});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}];
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
It is not possible to tell which from the stack trace, but we happen to know in this case that it is
|
||||
the `ngFocus` directive.
|
||||
|
||||
Now look up the stack to see that our application code is only entered once in `app.js` at line `12`.
|
||||
This is where our problem is:
|
||||
|
||||
```
|
||||
10: link: function($scope, $element, $attr) {
|
||||
11: $scope.$watch($attr.setFocusIf, function(value) {
|
||||
12: if ( value ) { $element[0].focus(); } <---- This is the source of the problem
|
||||
13: });
|
||||
14: }
|
||||
```
|
||||
|
||||
We can now see that the second `$apply` was caused by us programmatically triggering a DOM event
|
||||
(i.e. focus) to occur. We must fix this by moving the code outside of the $apply block using
|
||||
`$timeout` as described above.
|
||||
|
||||
## Further Reading
|
||||
To learn more about Angular processing model please check out the
|
||||
{@link guide/concepts concepts doc} as well as the {@link ng.$rootScope.Scope api} doc.
|
||||
|
||||
@@ -13,3 +13,4 @@ Angular template from a URL requires that the URL is one considered safe for loa
|
||||
This helps prevent XSS and other security issues. Read more at {@link
|
||||
api/ng.$sce Strict Contextual Escaping (SCE)}
|
||||
|
||||
You may want to include the ngSanitize module to use the automatic sanitizing.
|
||||
|
||||
@@ -60,7 +60,7 @@ changes to $location are reflected into the browser address bar.
|
||||
|
||||
<tr>
|
||||
<td class="head">aware of docroot/context from which the application is loaded</td>
|
||||
<td>no - window.location.path returns "/docroot/actual/path"</td>
|
||||
<td>no - window.location.pathname returns "/docroot/actual/path"</td>
|
||||
<td>yes - $location.path() returns "/actual/path"</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ Here is an example of manually initializing Angular:
|
||||
<html>
|
||||
<body>
|
||||
Hello {{'World'}}!
|
||||
<script src="http://code.angularjs.org/angular.js"></script>
|
||||
<script src="http://code.angularjs.org/snapshot/angular.js"></script>
|
||||
|
||||
<script>
|
||||
angular.module('myApp', [])
|
||||
|
||||
@@ -226,7 +226,7 @@ moved to the compile function for performance reasons.
|
||||
To understand, let's look at a real-world example with `ngRepeat`:
|
||||
|
||||
```html
|
||||
Hello {{user}}, you have these actions:
|
||||
Hello {{user.name}}, you have these actions:
|
||||
<ul>
|
||||
<li ng-repeat="action in user.actions">
|
||||
{{action.description}}
|
||||
@@ -236,7 +236,7 @@ Hello {{user}}, you have these actions:
|
||||
|
||||
When the above example is compiled, the compiler visits every node and looks for directives.
|
||||
|
||||
`{{user}}` matches the {@link ng.$interpolate interpolation directive}
|
||||
`{{user.name}}` matches the {@link ng.$interpolate interpolation directive}
|
||||
and `ng-repeat` matches the {@link ng.directive:ngRepeat `ngRepeat` directive}.
|
||||
|
||||
But {@link ng.directive:ngRepeat ngRepeat} has a dilemma.
|
||||
|
||||
@@ -9,7 +9,7 @@ When the model changes, the view reflects the change, and vice versa.
|
||||
|
||||
## Data Binding in Classical Template Systems
|
||||
|
||||
<img class="right" src="img/One_Way_Data_Binding.png"/>
|
||||
<img class="right" src="img/One_Way_Data_Binding.png"/><br />
|
||||
Most templating systems bind data in only one direction: they merge template and model components
|
||||
together into a view. After the merge occurs, changes to the model
|
||||
or related sections of the view are NOT automatically reflected in the view. Worse, any changes
|
||||
@@ -18,7 +18,7 @@ to write code that constantly syncs the view with the model and the model with t
|
||||
|
||||
## Data Binding in Angular Templates
|
||||
|
||||
<img class="right" src="img/Two_Way_Data_Binding.png"/>
|
||||
<img class="right" src="img/Two_Way_Data_Binding.png"/><br />
|
||||
Angular templates work differently. First the template (which is the uncompiled HTML along with
|
||||
any additional markup or directives) is compiled on the browser. The compilation step produces a
|
||||
live view. Any changes to the view are immediately reflected in the model, and any changes in
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
A filter formats the value of an expression for display to the user. They can be used in view templates,
|
||||
controllers or services and it is easy to define your own filter.
|
||||
|
||||
The underlying API is the {@link ng.$filterProvider filterProvider}.
|
||||
The underlying API is the {@link ng.$filterProvider `filterProvider`}.
|
||||
|
||||
## Using filters in view templates
|
||||
|
||||
|
||||
@@ -73,8 +73,9 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* **Internationalization:** [angular-translate](http://angular-translate.github.io), [angular-gettext](http://angular-gettext.rocketeer.be/)
|
||||
* **RESTful services:** [Restangular](https://github.com/mgonto/restangular)
|
||||
* **SQL and NoSQL backends:** [BreezeJS](http://www.breezejs.com/), [AngularFire](http://angularfire.com/)
|
||||
* **UI Widgets: **[KendoUI](http://kendo-labs.github.io/angular-kendo/#/), [UI Bootstrap](http://angular-ui.github.io/bootstrap/), [Wijmo](http://wijmo.com/tag/angularjs-2/)
|
||||
* **UI Widgets: **[KendoUI](http://kendo-labs.github.io/angular-kendo/#/), [UI Bootstrap](http://angular-ui.github.io/bootstrap/), [Wijmo](http://wijmo.com/tag/angularjs-2/), [ngTagsInput](https://github.com/mbenford/ngTagsInput)
|
||||
* **Advanced Routing:** [UI-Router](https://github.com/angular-ui/ui-router)
|
||||
* **Maps:** [UI-Map (Google Maps)](https://github.com/angular-ui/ui-map)
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -112,6 +113,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* **Free online:**
|
||||
[thinkster.io](http://thinkster.io),
|
||||
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1)
|
||||
[CodeSchool](https://www.codeschool.com/courses/shaping-up-with-angular-js)
|
||||
* **Paid online:**
|
||||
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
|
||||
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
|
||||
|
||||
@@ -22,7 +22,7 @@ The impedance mismatch between dynamic applications and static documents is ofte
|
||||
in charge and it calls into the library when it sees fit. E.g., `jQuery`.
|
||||
* **frameworks** - a particular implementation of a web application, where your code fills in
|
||||
the details. The framework is in charge and it calls into your code when it needs something
|
||||
app specific. E.g., `knockout`, `ember`, etc.
|
||||
app specific. E.g., `durandal`, `ember`, etc.
|
||||
|
||||
|
||||
Angular takes another approach. It attempts to minimize the impedance mismatch between document
|
||||
|
||||
@@ -130,7 +130,7 @@ injection of `$window`, `$scope`, and our `notify` service:
|
||||
</example>
|
||||
|
||||
<div class="alert alert-danger">
|
||||
**Careful:** If you plan to [minify](http://en.wikipedia.org/wiki/Minification_(programming)) your
|
||||
**Careful:** If you plan to [minify](http://en.wikipedia.org/wiki/Minification_(programming)) your
|
||||
code, your variable names will get renamed unless you use one of the annotation techniques above.
|
||||
</div>
|
||||
|
||||
@@ -299,5 +299,5 @@ it('should clear messages after alert', function() {
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link ./ng Angular Service API}
|
||||
* {@link ./api/ng/service Angular Service API}
|
||||
* {@link angular.injector Injector API}
|
||||
|
||||
@@ -49,7 +49,7 @@ Out of the four options in the list above, only the last one is testable. Let's
|
||||
### Using the `new` operator
|
||||
|
||||
While there is nothing wrong with the `new` operator fundamentally, a problem arises when calling `new`
|
||||
on a constructor. This permanently binds the call site to the type. For example, lets say that we try to
|
||||
on a constructor. This permanently binds the call site to the type. For example, let's say that we try to
|
||||
instantiate an `XHR` that will retrieve data from the server.
|
||||
|
||||
```js
|
||||
|
||||
@@ -10,7 +10,7 @@ becoming an Angular expert.
|
||||
1. Read the {@link guide/concepts conceptual overview}.<br/>Understand Angular's vocabulary and how all the Angular
|
||||
components work together.
|
||||
1. Do the {@link tutorial/ AngularJS Tutorial}.<br/>Walk end-to-end through building an application complete with tests
|
||||
on top of a node.js web server. Covers every major AngularJS feature and show you how to set up your development
|
||||
on top of a node.js web server. Covers every major AngularJS feature and shows you how to set up your development
|
||||
environment.
|
||||
1. Download or clone the [Seed App project template](https://github.com/angular/angular-seed).<br/>Gives you a
|
||||
starter app with a directory layout, test harness, and scripts to begin building your application.
|
||||
|
||||
@@ -105,9 +105,12 @@ Check the version of Node.js that you have installed by running the following co
|
||||
node --version
|
||||
```
|
||||
|
||||
Or in Debian based distributions:
|
||||
In Debian based distributions, there is a name clash with another utility called `node`. The
|
||||
suggested solution is to also install the `nodejs-legacy` apt package, which renames `node` to
|
||||
`nodejs`.
|
||||
|
||||
```
|
||||
apt-get install nodejs-legacy
|
||||
nodejs --version
|
||||
```
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ __`test/e2e/scenarios.js`:__
|
||||
"MOTOROLA XOOM\u2122"
|
||||
]);
|
||||
|
||||
element(by.model('orderProp')).findElement(by.css('option[value="name"]')).click();
|
||||
element(by.model('orderProp')).element(by.css('option[value="name"]')).click();
|
||||
|
||||
expect(getNames()).toEqual([
|
||||
"MOTOROLA XOOM\u2122",
|
||||
|
||||
@@ -109,6 +109,10 @@ for this test run.
|
||||
Note that we call the helper function, `inject(function(checkmarkFilter) { ... })`, to get
|
||||
access to the filter that we want to test. See {@link angular.mock.inject angular.mock.inject()}.
|
||||
|
||||
Notice that the suffix 'Filter' is appended to your filter name when injected.
|
||||
See the {@link guide/filter#using-filters-in-controllers-services-and-directives Filter Guide}
|
||||
section where this is outlined.
|
||||
|
||||
You should now see the following output in the Karma tab:
|
||||
|
||||
<pre>Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)</pre>
|
||||
|
||||
+1
-1
@@ -38,7 +38,6 @@
|
||||
"uppercase": false,
|
||||
"manualLowercase": false,
|
||||
"manualUppercase": false,
|
||||
"nodeName_": false,
|
||||
"isArrayLike": false,
|
||||
"forEach": false,
|
||||
"sortedKeys": false,
|
||||
@@ -101,6 +100,7 @@
|
||||
"assertNotHasOwnProperty": false,
|
||||
"getter": false,
|
||||
"getBlockElements": false,
|
||||
"VALIDITY_STATE_PROPERTY": false,
|
||||
|
||||
/* AngularPublic.js */
|
||||
"version": false,
|
||||
|
||||
+15
-6
@@ -13,6 +13,7 @@
|
||||
-angularModule,
|
||||
-nodeName_,
|
||||
-uid,
|
||||
-VALIDITY_STATE_PROPERTY,
|
||||
|
||||
-lowercase,
|
||||
-uppercase,
|
||||
@@ -102,6 +103,10 @@
|
||||
* <div doc-module-components="ng"></div>
|
||||
*/
|
||||
|
||||
// The name of a form control's ValidityState property.
|
||||
// This is used so that it's possible for internal tests to create mock ValidityStates.
|
||||
var VALIDITY_STATE_PROPERTY = 'validity';
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.lowercase
|
||||
@@ -510,10 +515,14 @@ function isDate(value) {
|
||||
* @param {*} value Reference to check.
|
||||
* @returns {boolean} True if `value` is an `Array`.
|
||||
*/
|
||||
function isArray(value) {
|
||||
return toString.call(value) === '[object Array]';
|
||||
}
|
||||
|
||||
var isArray = (function() {
|
||||
if (!isFunction(Array.isArray)) {
|
||||
return function(value) {
|
||||
return toString.call(value) === '[object Array]';
|
||||
};
|
||||
}
|
||||
return Array.isArray;
|
||||
})();
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
@@ -1270,7 +1279,7 @@ function angularInit(element, bootstrap) {
|
||||
*
|
||||
* Angular will detect if it has been loaded into the browser more than once and only allow the
|
||||
* first loaded script to be bootstrapped and will report a warning to the browser console for
|
||||
* each of the subsequent scripts. This prevents strange results in applications, where otherwise
|
||||
* each of the subsequent scripts. This prevents strange results in applications, where otherwise
|
||||
* multiple instances of Angular try to work on the DOM.
|
||||
*
|
||||
* <example name="multi-bootstrap" module="multi-bootstrap">
|
||||
@@ -1400,7 +1409,7 @@ function assertArgFn(arg, name, acceptArrayAnnotation) {
|
||||
}
|
||||
|
||||
assertArg(isFunction(arg), name, 'not a function, got ' +
|
||||
(arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));
|
||||
(arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
|
||||
return arg;
|
||||
}
|
||||
|
||||
|
||||
+13
-7
@@ -13,16 +13,16 @@
|
||||
* @returns {string} hash string such that the same input will have the same hash string.
|
||||
* The resulting string key is in 'type:hashKey' format.
|
||||
*/
|
||||
function hashKey(obj) {
|
||||
function hashKey(obj, nextUidFn) {
|
||||
var objType = typeof obj,
|
||||
key;
|
||||
|
||||
if (objType == 'object' && obj !== null) {
|
||||
if (objType == 'function' || (objType == 'object' && obj !== null)) {
|
||||
if (typeof (key = obj.$$hashKey) == 'function') {
|
||||
// must invoke on object to keep the right this
|
||||
key = obj.$$hashKey();
|
||||
} else if (key === undefined) {
|
||||
key = obj.$$hashKey = nextUid();
|
||||
key = obj.$$hashKey = (nextUidFn || nextUid)();
|
||||
}
|
||||
} else {
|
||||
key = obj;
|
||||
@@ -34,7 +34,13 @@ function hashKey(obj) {
|
||||
/**
|
||||
* HashMap which can use objects as keys
|
||||
*/
|
||||
function HashMap(array){
|
||||
function HashMap(array, isolatedUid) {
|
||||
if (isolatedUid) {
|
||||
var uid = 0;
|
||||
this.nextUid = function() {
|
||||
return ++uid;
|
||||
};
|
||||
}
|
||||
forEach(array, this.put, this);
|
||||
}
|
||||
HashMap.prototype = {
|
||||
@@ -44,7 +50,7 @@ HashMap.prototype = {
|
||||
* @param value value to store can be any type
|
||||
*/
|
||||
put: function(key, value) {
|
||||
this[hashKey(key)] = value;
|
||||
this[hashKey(key, this.nextUid)] = value;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -52,7 +58,7 @@ HashMap.prototype = {
|
||||
* @returns {Object} the value for the key
|
||||
*/
|
||||
get: function(key) {
|
||||
return this[hashKey(key)];
|
||||
return this[hashKey(key, this.nextUid)];
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -60,7 +66,7 @@ HashMap.prototype = {
|
||||
* @param key
|
||||
*/
|
||||
remove: function(key) {
|
||||
var value = this[key = hashKey(key)];
|
||||
var value = this[key = hashKey(key, this.nextUid)];
|
||||
delete this[key];
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ function annotate(fn) {
|
||||
argDecl,
|
||||
last;
|
||||
|
||||
if (typeof fn == 'function') {
|
||||
if (typeof fn === 'function') {
|
||||
if (!($inject = fn.$inject)) {
|
||||
$inject = [];
|
||||
if (fn.length) {
|
||||
@@ -285,7 +285,7 @@ function annotate(fn) {
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc object
|
||||
* @ngdoc service
|
||||
* @name $provide
|
||||
*
|
||||
* @description
|
||||
@@ -591,7 +591,7 @@ function createInjector(modulesToLoad) {
|
||||
var INSTANTIATING = {},
|
||||
providerSuffix = 'Provider',
|
||||
path = [],
|
||||
loadedModules = new HashMap(),
|
||||
loadedModules = new HashMap([], true),
|
||||
providerCache = {
|
||||
$provide: {
|
||||
provider: supportObject(provider),
|
||||
@@ -724,7 +724,8 @@ function createInjector(modulesToLoad) {
|
||||
function getService(serviceName) {
|
||||
if (cache.hasOwnProperty(serviceName)) {
|
||||
if (cache[serviceName] === INSTANTIATING) {
|
||||
throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- '));
|
||||
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
|
||||
serviceName + ' <- ' + path.join(' <- '));
|
||||
}
|
||||
return cache[serviceName];
|
||||
} else {
|
||||
@@ -761,8 +762,7 @@ function createInjector(modulesToLoad) {
|
||||
: getService(key)
|
||||
);
|
||||
}
|
||||
if (!fn.$inject) {
|
||||
// this means that we must be an array.
|
||||
if (isArray(fn)) {
|
||||
fn = fn[length];
|
||||
}
|
||||
|
||||
|
||||
+11
-8
@@ -98,8 +98,9 @@
|
||||
* @returns {Object} jQuery object.
|
||||
*/
|
||||
|
||||
JQLite.expando = 'ng339';
|
||||
|
||||
var jqCache = JQLite.cache = {},
|
||||
jqName = JQLite.expando = 'ng' + new Date().getTime(),
|
||||
jqId = 1,
|
||||
addEventListenerFn = (window.document.addEventListener
|
||||
? function(element, type, fn) {element.addEventListener(type, fn, false);}
|
||||
@@ -309,7 +310,7 @@ function jqLiteOff(element, type, fn, unsupported) {
|
||||
}
|
||||
|
||||
function jqLiteRemoveData(element, name) {
|
||||
var expandoId = element[jqName],
|
||||
var expandoId = element.ng339,
|
||||
expandoStore = jqCache[expandoId];
|
||||
|
||||
if (expandoStore) {
|
||||
@@ -323,17 +324,17 @@ function jqLiteRemoveData(element, name) {
|
||||
jqLiteOff(element);
|
||||
}
|
||||
delete jqCache[expandoId];
|
||||
element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
|
||||
element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
|
||||
}
|
||||
}
|
||||
|
||||
function jqLiteExpandoStore(element, key, value) {
|
||||
var expandoId = element[jqName],
|
||||
var expandoId = element.ng339,
|
||||
expandoStore = jqCache[expandoId || -1];
|
||||
|
||||
if (isDefined(value)) {
|
||||
if (!expandoStore) {
|
||||
element[jqName] = expandoId = jqNextId();
|
||||
element.ng339 = expandoId = jqNextId();
|
||||
expandoStore = jqCache[expandoId] = {};
|
||||
}
|
||||
expandoStore[key] = value;
|
||||
@@ -652,6 +653,7 @@ forEach({
|
||||
*/
|
||||
JQLite.prototype[name] = function(arg1, arg2) {
|
||||
var i, key;
|
||||
var nodeCount = this.length;
|
||||
|
||||
// jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
|
||||
// in a way that survives minification.
|
||||
@@ -661,7 +663,7 @@ forEach({
|
||||
if (isObject(arg1)) {
|
||||
|
||||
// we are a write, but the object properties are the key/values
|
||||
for (i = 0; i < this.length; i++) {
|
||||
for (i = 0; i < nodeCount; i++) {
|
||||
if (fn === jqLiteData) {
|
||||
// data() takes the whole object in jQuery
|
||||
fn(this[i], arg1);
|
||||
@@ -675,9 +677,10 @@ forEach({
|
||||
return this;
|
||||
} else {
|
||||
// we are a read, so read the first child.
|
||||
// TODO: do we still need this?
|
||||
var value = fn.$dv;
|
||||
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
|
||||
var jj = (value === undefined) ? Math.min(this.length, 1) : this.length;
|
||||
var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount;
|
||||
for (var j = 0; j < jj; j++) {
|
||||
var nodeValue = fn(this[j], arg1, arg2);
|
||||
value = value ? value + nodeValue : nodeValue;
|
||||
@@ -686,7 +689,7 @@ forEach({
|
||||
}
|
||||
} else {
|
||||
// we are a write, so apply to all children
|
||||
for (i = 0; i < this.length; i++) {
|
||||
for (i = 0; i < nodeCount; i++) {
|
||||
fn(this[i], arg1, arg2);
|
||||
}
|
||||
// return self for chaining
|
||||
|
||||
+4
-4
@@ -44,7 +44,7 @@ function setupModuleLoader(window) {
|
||||
*
|
||||
* # Module
|
||||
*
|
||||
* A module is a collection of services, directives, filters, and configuration information.
|
||||
* A module is a collection of services, directives, controllers, filters, and configuration information.
|
||||
* `angular.module` is used to configure the {@link auto.$injector $injector}.
|
||||
*
|
||||
* ```js
|
||||
@@ -72,9 +72,9 @@ function setupModuleLoader(window) {
|
||||
* {@link angular.bootstrap} to simplify this process for you.
|
||||
*
|
||||
* @param {!string} name The name of the module to create or retrieve.
|
||||
<<<<<* @param {!Array.<string>=} requires If specified then new module is being created. If
|
||||
>>>>>* unspecified then the module is being retrieved for further configuration.
|
||||
* @param {Function} configFn Optional configuration function for the module. Same as
|
||||
* @param {!Array.<string>=} requires If specified then new module is being created. If
|
||||
* unspecified then the module is being retrieved for further configuration.
|
||||
* @param {Function=} configFn Optional configuration function for the module. Same as
|
||||
* {@link angular.Module#config Module#config()}.
|
||||
* @returns {module} new module with the {@link angular.Module} api.
|
||||
*/
|
||||
|
||||
+78
-44
@@ -830,7 +830,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
compileNodes($compileNodes, transcludeFn, $compileNodes,
|
||||
maxPriority, ignoreDirective, previousCompileContext);
|
||||
safeAddClass($compileNodes, 'ng-scope');
|
||||
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){
|
||||
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){
|
||||
assertArg(scope, 'scope');
|
||||
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
|
||||
// and sometimes changes the structure of the DOM.
|
||||
@@ -852,7 +852,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
if (cloneConnectFn) cloneConnectFn($linkNode, scope);
|
||||
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
|
||||
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
|
||||
return $linkNode;
|
||||
};
|
||||
}
|
||||
@@ -907,7 +907,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
!childNodes.length)
|
||||
? null
|
||||
: compileNodes(childNodes,
|
||||
nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
|
||||
nodeLinkFn ? (
|
||||
(nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
|
||||
&& nodeLinkFn.transclude) : transcludeFn);
|
||||
|
||||
linkFns.push(nodeLinkFn, childLinkFn);
|
||||
linkFnFound = linkFnFound || nodeLinkFn || childLinkFn;
|
||||
@@ -918,8 +920,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// return a linking function if we have found anything, null otherwise
|
||||
return linkFnFound ? compositeLinkFn : null;
|
||||
|
||||
function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
|
||||
var nodeLinkFn, childLinkFn, node, $node, childScope, childTranscludeFn, i, ii, n;
|
||||
function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
|
||||
var nodeLinkFn, childLinkFn, node, $node, childScope, i, ii, n, childBoundTranscludeFn;
|
||||
|
||||
// copy nodeList so that linking doesn't break due to live list updates.
|
||||
var nodeListLength = nodeList.length,
|
||||
@@ -941,23 +943,32 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
} else {
|
||||
childScope = scope;
|
||||
}
|
||||
childTranscludeFn = nodeLinkFn.transclude;
|
||||
if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement,
|
||||
createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn)
|
||||
);
|
||||
|
||||
if ( nodeLinkFn.transcludeOnThisElement ) {
|
||||
childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
|
||||
|
||||
} else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
|
||||
childBoundTranscludeFn = parentBoundTranscludeFn;
|
||||
|
||||
} else if (!parentBoundTranscludeFn && transcludeFn) {
|
||||
childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
|
||||
|
||||
} else {
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn);
|
||||
childBoundTranscludeFn = null;
|
||||
}
|
||||
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
|
||||
|
||||
} else if (childLinkFn) {
|
||||
childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
|
||||
childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createBoundTranscludeFn(scope, transcludeFn) {
|
||||
return function boundTranscludeFn(transcludedScope, cloneFn, controllers) {
|
||||
function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
|
||||
|
||||
var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) {
|
||||
var scopeCreated = false;
|
||||
|
||||
if (!transcludedScope) {
|
||||
@@ -966,12 +977,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
scopeCreated = true;
|
||||
}
|
||||
|
||||
var clone = transcludeFn(transcludedScope, cloneFn, controllers);
|
||||
var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn);
|
||||
if (scopeCreated) {
|
||||
clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy));
|
||||
clone.on('$destroy', function() { transcludedScope.$destroy(); });
|
||||
}
|
||||
return clone;
|
||||
};
|
||||
|
||||
return boundTranscludeFn;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -997,7 +1010,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective);
|
||||
|
||||
// iterate over the attributes
|
||||
for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
|
||||
for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
|
||||
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
|
||||
var attrStartName = false;
|
||||
var attrEndName = false;
|
||||
@@ -1005,9 +1018,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
attr = nAttrs[j];
|
||||
if (!msie || msie >= 8 || attr.specified) {
|
||||
name = attr.name;
|
||||
value = trim(attr.value);
|
||||
|
||||
// support ngAttr attribute binding
|
||||
ngAttrName = directiveNormalize(name);
|
||||
if (NG_ATTR_BINDING.test(ngAttrName)) {
|
||||
if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
|
||||
name = snake_case(ngAttrName.substr(6), '-');
|
||||
}
|
||||
|
||||
@@ -1020,9 +1035,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
attrsMap[nName] = name;
|
||||
attrs[nName] = value = trim(attr.value);
|
||||
if (getBooleanAttrName(node, nName)) {
|
||||
attrs[nName] = true; // presence means true
|
||||
if (isNgAttr || !attrs.hasOwnProperty(nName)) {
|
||||
attrs[nName] = value;
|
||||
if (getBooleanAttrName(node, nName)) {
|
||||
attrs[nName] = true; // presence means true
|
||||
}
|
||||
}
|
||||
addAttrInterpolateDirective(node, directives, value, nName);
|
||||
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
|
||||
@@ -1149,6 +1166,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
templateDirective = previousCompileContext.templateDirective,
|
||||
nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
|
||||
hasTranscludeDirective = false,
|
||||
hasTemplate = false,
|
||||
hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
|
||||
$compileNode = templateAttrs.$$element = jqLite(compileNode),
|
||||
directive,
|
||||
@@ -1239,6 +1257,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
if (directive.template) {
|
||||
hasTemplate = true;
|
||||
assertNoDuplicate('template', templateDirective, directive, $compileNode);
|
||||
templateDirective = directive;
|
||||
|
||||
@@ -1288,6 +1307,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
if (directive.templateUrl) {
|
||||
hasTemplate = true;
|
||||
assertNoDuplicate('template', templateDirective, directive, $compileNode);
|
||||
templateDirective = directive;
|
||||
|
||||
@@ -1296,7 +1316,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
|
||||
templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, {
|
||||
templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
|
||||
controllerDirectives: controllerDirectives,
|
||||
newIsolateScopeDirective: newIsolateScopeDirective,
|
||||
templateDirective: templateDirective,
|
||||
@@ -1324,7 +1344,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
|
||||
nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn;
|
||||
nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
|
||||
nodeLinkFn.templateOnThisElement = hasTemplate;
|
||||
nodeLinkFn.transclude = childTranscludeFn;
|
||||
|
||||
previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
|
||||
|
||||
// might be normal or delayed nodeLinkFn depending on if templateUrl is present
|
||||
@@ -1719,7 +1742,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
});
|
||||
afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
|
||||
|
||||
|
||||
while(linkQueue.length) {
|
||||
var scope = linkQueue.shift(),
|
||||
beforeTemplateLinkNode = linkQueue.shift(),
|
||||
@@ -1741,8 +1763,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// Copy in CSS classes from original node
|
||||
safeAddClass(jqLite(linkNode), oldClasses);
|
||||
}
|
||||
if (afterTemplateNodeLinkFn.transclude) {
|
||||
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude);
|
||||
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
|
||||
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
|
||||
} else {
|
||||
childBoundTranscludeFn = boundTranscludeFn;
|
||||
}
|
||||
@@ -1756,13 +1778,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
});
|
||||
|
||||
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
|
||||
var childBoundTranscludeFn = boundTranscludeFn;
|
||||
if (linkQueue) {
|
||||
linkQueue.push(scope);
|
||||
linkQueue.push(node);
|
||||
linkQueue.push(rootElement);
|
||||
linkQueue.push(boundTranscludeFn);
|
||||
linkQueue.push(childBoundTranscludeFn);
|
||||
} else {
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn);
|
||||
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
|
||||
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
|
||||
}
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1787,23 +1813,31 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
|
||||
function addTextInterpolateDirective(directives, text) {
|
||||
var interpolateFn = $interpolate(text, true);
|
||||
if (interpolateFn) {
|
||||
directives.push({
|
||||
priority: 0,
|
||||
compile: valueFn(function textInterpolateLinkFn(scope, node) {
|
||||
var parent = node.parent(),
|
||||
bindings = parent.data('$binding') || [];
|
||||
bindings.push(interpolateFn);
|
||||
safeAddClass(parent.data('$binding', bindings), 'ng-binding');
|
||||
scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
|
||||
node[0].nodeValue = value;
|
||||
});
|
||||
})
|
||||
});
|
||||
function addTextInterpolateDirective(directives, text) {
|
||||
var interpolateFn = $interpolate(text, true);
|
||||
if (interpolateFn) {
|
||||
directives.push({
|
||||
priority: 0,
|
||||
compile: function textInterpolateCompileFn(templateNode) {
|
||||
// when transcluding a template that has bindings in the root
|
||||
// then we don't have a parent and should do this in the linkFn
|
||||
var parent = templateNode.parent(), hasCompileParent = parent.length;
|
||||
if (hasCompileParent) safeAddClass(templateNode.parent(), 'ng-binding');
|
||||
|
||||
return function textInterpolateLinkFn(scope, node) {
|
||||
var parent = node.parent(),
|
||||
bindings = parent.data('$binding') || [];
|
||||
bindings.push(interpolateFn);
|
||||
parent.data('$binding', bindings);
|
||||
if (!hasCompileParent) safeAddClass(parent, 'ng-binding');
|
||||
scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
|
||||
node[0].nodeValue = value;
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getTrustedContext(node, attrNormalizedName) {
|
||||
|
||||
@@ -74,7 +74,7 @@ function $ControllerProvider() {
|
||||
instance = $injector.instantiate(expression, locals);
|
||||
|
||||
if (identifier) {
|
||||
if (!(locals && typeof locals.$scope == 'object')) {
|
||||
if (!(locals && typeof locals.$scope === 'object')) {
|
||||
throw minErr('$controller')('noscp',
|
||||
"Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
|
||||
constructor || expression.name, identifier);
|
||||
|
||||
+31
-13
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
|
||||
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i;
|
||||
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i;
|
||||
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
|
||||
|
||||
var inputType = {
|
||||
@@ -435,15 +435,29 @@ function validate(ctrl, validatorName, validity, value){
|
||||
return validity ? value : undefined;
|
||||
}
|
||||
|
||||
function testFlags(validity, flags) {
|
||||
var i, flag;
|
||||
if (flags) {
|
||||
for (i=0; i<flags.length; ++i) {
|
||||
flag = flags[i];
|
||||
if (validity[flag]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function addNativeHtml5Validators(ctrl, validatorName, element) {
|
||||
var validity = element.prop('validity');
|
||||
// Pass validity so that behaviour can be mocked easier.
|
||||
function addNativeHtml5Validators(ctrl, validatorName, badFlags, ignoreFlags, validity) {
|
||||
if (isObject(validity)) {
|
||||
ctrl.$$hasNativeValidators = true;
|
||||
var validator = function(value) {
|
||||
// Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can
|
||||
// perform the required validation)
|
||||
if (!ctrl.$error[validatorName] && (validity.badInput || validity.customError ||
|
||||
validity.typeMismatch) && !validity.valueMissing) {
|
||||
if (!ctrl.$error[validatorName] &&
|
||||
!testFlags(validity, ignoreFlags) &&
|
||||
testFlags(validity, badFlags)) {
|
||||
ctrl.$setValidity(validatorName, false);
|
||||
return;
|
||||
}
|
||||
@@ -454,8 +468,9 @@ function addNativeHtml5Validators(ctrl, validatorName, element) {
|
||||
}
|
||||
|
||||
function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
var validity = element.prop('validity');
|
||||
var validity = element.prop(VALIDITY_STATE_PROPERTY);
|
||||
var placeholder = element[0].placeholder, noevent = {};
|
||||
ctrl.$$validityState = validity;
|
||||
|
||||
// In composition mode, users are still inputing intermediate text buffer,
|
||||
// hold the listener until composition is done.
|
||||
@@ -493,11 +508,11 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
value = trim(value);
|
||||
}
|
||||
|
||||
if (ctrl.$viewValue !== value ||
|
||||
// If the value is still empty/falsy, and there is no `required` error, run validators
|
||||
// again. This enables HTML5 constraint validation errors to affect Angular validation
|
||||
// even when the first character entered causes an error.
|
||||
(validity && value === '' && !validity.valueMissing)) {
|
||||
// If a control is suffering from bad input, browsers discard its value, so it may be
|
||||
// necessary to revalidate even if the control's value is the same empty value twice in
|
||||
// a row.
|
||||
var revalidate = validity && ctrl.$$hasNativeValidators;
|
||||
if (ctrl.$viewValue !== value || (value === '' && revalidate)) {
|
||||
if (scope.$$phase) {
|
||||
ctrl.$setViewValue(value);
|
||||
} else {
|
||||
@@ -603,6 +618,8 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
}
|
||||
|
||||
var numberBadFlags = ['badInput'];
|
||||
|
||||
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
|
||||
@@ -617,7 +634,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
});
|
||||
|
||||
addNativeHtml5Validators(ctrl, 'number', element);
|
||||
addNativeHtml5Validators(ctrl, 'number', numberBadFlags, null, ctrl.$$validityState);
|
||||
|
||||
ctrl.$formatters.push(function(value) {
|
||||
return ctrl.$isEmpty(value) ? '' : '' + value;
|
||||
@@ -749,6 +766,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
|
||||
* patterns defined as scope expressions.
|
||||
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
||||
* interaction with the input element.
|
||||
* @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1098,7 +1116,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* This method should be called by validators - i.e. the parser or formatter functions.
|
||||
*
|
||||
* @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
|
||||
* to `$error[validationErrorKey]=isValid` so that it is available for data-binding.
|
||||
* to `$error[validationErrorKey]=!isValid` so that it is available for data-binding.
|
||||
* The `validationErrorKey` should be in camelCase and will get converted into dash-case
|
||||
* for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
|
||||
* class and can be bound to as `{{someForm.someControl.$error.myError}}` .
|
||||
|
||||
@@ -50,14 +50,19 @@
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
var ngBindDirective = ngDirective(function(scope, element, attr) {
|
||||
element.addClass('ng-binding').data('$binding', attr.ngBind);
|
||||
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
|
||||
// We are purposefully using == here rather than === because we want to
|
||||
// catch when value is "null or undefined"
|
||||
// jshint -W041
|
||||
element.text(value == undefined ? '' : value);
|
||||
});
|
||||
var ngBindDirective = ngDirective({
|
||||
compile: function(templateElement) {
|
||||
templateElement.addClass('ng-binding');
|
||||
return function (scope, element, attr) {
|
||||
element.data('$binding', attr.ngBind);
|
||||
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
|
||||
// We are purposefully using == here rather than === because we want to
|
||||
// catch when value is "null or undefined"
|
||||
// jshint -W041
|
||||
element.text(value == undefined ? '' : value);
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*
|
||||
* MVC components in angular:
|
||||
*
|
||||
* * Model — The Model is scope properties; scopes are attached to the DOM where scope properties
|
||||
* * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
|
||||
* are accessed through bindings.
|
||||
* * View — The template (HTML with data bindings) that is rendered into the View.
|
||||
* * Controller — The `ngController` directive specifies a Controller class; the class contains business
|
||||
|
||||
@@ -45,7 +45,7 @@ forEach(
|
||||
return {
|
||||
compile: function($element, attr) {
|
||||
var fn = $parse(attr[directiveName]);
|
||||
return function(scope, element, attr) {
|
||||
return function ngEventHandler(scope, element) {
|
||||
element.on(lowercase(name), function(event) {
|
||||
scope.$apply(function() {
|
||||
fn(scope, {$event:event});
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
* When one person, perhaps John, views the document, "John is viewing" will be shown.
|
||||
* When three people view the document, no explicit number rule is found, so
|
||||
* an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
|
||||
* In this case, plural category 'one' is matched and "John, Marry and one other person are viewing"
|
||||
* In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
|
||||
* is shown.
|
||||
*
|
||||
* Note that when you specify offsets, you must provide explicit number rules for
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<file name="protractor.js" type="protractor">
|
||||
var colorSpan = element(by.css('span'));
|
||||
|
||||
iit('should check ng-style', function() {
|
||||
it('should check ng-style', function() {
|
||||
expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
|
||||
element(by.css('input[value=\'set color\']')).click();
|
||||
expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
|
||||
|
||||
@@ -553,7 +553,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// rather then the element.
|
||||
(element = optionTemplate.clone())
|
||||
.val(option.id)
|
||||
.attr('selected', option.selected)
|
||||
.prop('selected', option.selected)
|
||||
.text(option.label);
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ function filterFilter() {
|
||||
// jshint +W086
|
||||
for (var key in expression) {
|
||||
(function(path) {
|
||||
if (typeof expression[path] == 'undefined') return;
|
||||
if (typeof expression[path] === 'undefined') return;
|
||||
predicates.push(function(value) {
|
||||
return search(path == '$' ? value : (value && value[path]), expression[path]);
|
||||
});
|
||||
|
||||
@@ -131,6 +131,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
|
||||
if (match && match[2] == '-' && match[3] > fractionSize + 1) {
|
||||
numStr = '0';
|
||||
number = 0;
|
||||
} else {
|
||||
formatedText = numStr;
|
||||
hasExponent = true;
|
||||
@@ -145,8 +146,11 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
|
||||
}
|
||||
|
||||
var pow = Math.pow(10, fractionSize + 1);
|
||||
number = Math.floor(number * pow + 5) / pow;
|
||||
// safely round numbers in JS without hitting imprecisions of floating-point arithmetics
|
||||
// inspired by:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
|
||||
number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
|
||||
|
||||
var fraction = ('' + number).split(DECIMAL_SEP);
|
||||
var whole = fraction[0];
|
||||
fraction = fraction[1] || '';
|
||||
|
||||
+10
-9
@@ -674,14 +674,6 @@ function $HttpProvider() {
|
||||
config.headers = headers;
|
||||
config.method = uppercase(config.method);
|
||||
|
||||
var xsrfValue = urlIsSameOrigin(config.url)
|
||||
? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName]
|
||||
: undefined;
|
||||
if (xsrfValue) {
|
||||
headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
|
||||
}
|
||||
|
||||
|
||||
var serverRequest = function(config) {
|
||||
headers = config.headers;
|
||||
var reqData = transformData(config.data, headersGetter(headers), config.transformRequest);
|
||||
@@ -957,8 +949,17 @@ function $HttpProvider() {
|
||||
}
|
||||
}
|
||||
|
||||
// if we won't have the response in cache, send the request to the backend
|
||||
|
||||
// if we won't have the response in cache, set the xsrf headers and
|
||||
// send the request to the backend
|
||||
if (isUndefined(cachedResp)) {
|
||||
var xsrfValue = urlIsSameOrigin(config.url)
|
||||
? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName]
|
||||
: undefined;
|
||||
if (xsrfValue) {
|
||||
reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
|
||||
}
|
||||
|
||||
$httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
|
||||
config.withCredentials, config.responseType);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
// Safari respectively.
|
||||
if (xhr && xhr.readyState == 4) {
|
||||
var responseHeaders = null,
|
||||
response = null;
|
||||
response = null,
|
||||
statusText = '';
|
||||
|
||||
if(status !== ABORTED) {
|
||||
responseHeaders = xhr.getAllResponseHeaders();
|
||||
@@ -91,11 +92,17 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
response = ('response' in xhr) ? xhr.response : xhr.responseText;
|
||||
}
|
||||
|
||||
// Accessing statusText on an aborted xhr object will
|
||||
// throw an 'c00c023f error' in IE9 and lower, don't touch it.
|
||||
if (!(status === ABORTED && msie < 10)) {
|
||||
statusText = xhr.statusText;
|
||||
}
|
||||
|
||||
completeRequest(callback,
|
||||
status || xhr.status,
|
||||
response,
|
||||
responseHeaders,
|
||||
xhr.statusText || '');
|
||||
statusText);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -174,7 +174,7 @@ function $IntervalProvider() {
|
||||
interval.cancel = function(promise) {
|
||||
if (promise && promise.$$intervalId in intervals) {
|
||||
intervals[promise.$$intervalId].reject('canceled');
|
||||
clearInterval(promise.$$intervalId);
|
||||
$window.clearInterval(promise.$$intervalId);
|
||||
delete intervals[promise.$$intervalId];
|
||||
return true;
|
||||
}
|
||||
|
||||
+2
-2
@@ -551,7 +551,7 @@ function $LocationProvider(){
|
||||
html5Mode = false;
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @ngdoc method
|
||||
* @name $locationProvider#hashPrefix
|
||||
* @description
|
||||
* @param {string=} prefix Prefix for hash part (containing path and search)
|
||||
@@ -567,7 +567,7 @@ function $LocationProvider(){
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @ngdoc method
|
||||
* @name $locationProvider#html5Mode
|
||||
* @description
|
||||
* @param {boolean=} mode Use HTML5 strategy if available.
|
||||
|
||||
+36
-15
@@ -12,14 +12,7 @@ var promiseWarning;
|
||||
//
|
||||
// As an example, consider the following Angular expression:
|
||||
//
|
||||
// {}.toString.constructor(alert("evil JS code"))
|
||||
//
|
||||
// We want to prevent this type of access. For the sake of performance, during the lexing phase we
|
||||
// disallow any "dotted" access to any member named "constructor".
|
||||
//
|
||||
// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor
|
||||
// while evaluating the expression, which is a stronger but more expensive test. Since reflective
|
||||
// calls are expensive anyway, this is not such a big deal compared to static dereferencing.
|
||||
// {}.toString.constructor('alert("evil JS code")')
|
||||
//
|
||||
// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
|
||||
// against the expression language, but not to prevent exploits that were enabled by exposing
|
||||
@@ -27,17 +20,19 @@ var promiseWarning;
|
||||
// practice and therefore we are not even trying to protect against interaction with an object
|
||||
// explicitly exposed in this way.
|
||||
//
|
||||
// A developer could foil the name check by aliasing the Function constructor under a different
|
||||
// name on the scope.
|
||||
//
|
||||
// In general, it is not possible to access a Window object from an angular expression unless a
|
||||
// window or some DOM object that has a reference to window is published onto a Scope.
|
||||
// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
|
||||
// native objects.
|
||||
|
||||
|
||||
function ensureSafeMemberName(name, fullExpression) {
|
||||
if (name === "constructor") {
|
||||
if (name === "__defineGetter__" || name === "__defineSetter__"
|
||||
|| name === "__lookupGetter__" || name === "__lookupSetter__"
|
||||
|| name === "__proto__") {
|
||||
throw $parseMinErr('isecfld',
|
||||
'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
'Attempting to access a disallowed field in Angular expressions! '
|
||||
+'Expression: {0}', fullExpression);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
@@ -59,11 +54,34 @@ function ensureSafeObject(obj, fullExpression) {
|
||||
throw $parseMinErr('isecdom',
|
||||
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else if (// block Object so that we can't get hold of dangerous Object.* methods
|
||||
obj === Object) {
|
||||
throw $parseMinErr('isecobj',
|
||||
'Referencing Object in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
var CALL = Function.prototype.call;
|
||||
var APPLY = Function.prototype.apply;
|
||||
var BIND = Function.prototype.bind;
|
||||
|
||||
function ensureSafeFunction(obj, fullExpression) {
|
||||
if (obj) {
|
||||
if (obj.constructor === obj) {
|
||||
throw $parseMinErr('isecfn',
|
||||
'Referencing Function in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else if (obj === CALL || obj === APPLY || (BIND && obj === BIND)) {
|
||||
throw $parseMinErr('isecff',
|
||||
'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var OPERATORS = {
|
||||
/* jshint bitwise : false */
|
||||
'null':function(){return null;},
|
||||
@@ -698,6 +716,7 @@ Parser.prototype = {
|
||||
i = indexFn(self, locals),
|
||||
v, p;
|
||||
|
||||
ensureSafeMemberName(i, parser.text);
|
||||
if (!o) return undefined;
|
||||
v = ensureSafeObject(o[i], parser.text);
|
||||
if (v && v.then && parser.options.unwrapPromises) {
|
||||
@@ -740,7 +759,7 @@ Parser.prototype = {
|
||||
var fnPtr = fn(scope, locals, context) || noop;
|
||||
|
||||
ensureSafeObject(context, parser.text);
|
||||
ensureSafeObject(fnPtr, parser.text);
|
||||
ensureSafeFunction(fnPtr, parser.text);
|
||||
|
||||
// IE stupidity! (IE doesn't have apply for some native functions)
|
||||
var v = fnPtr.apply
|
||||
@@ -849,6 +868,8 @@ function setter(obj, path, setValue, fullExp, options) {
|
||||
}
|
||||
}
|
||||
key = ensureSafeMemberName(element.shift(), fullExp);
|
||||
ensureSafeObject(obj, fullExp);
|
||||
ensureSafeObject(obj[key], fullExp);
|
||||
obj[key] = setValue;
|
||||
return setValue;
|
||||
}
|
||||
|
||||
+6
-10
@@ -23,17 +23,13 @@
|
||||
* var deferred = $q.defer();
|
||||
*
|
||||
* setTimeout(function() {
|
||||
* // since this fn executes async in a future turn of the event loop, we need to wrap
|
||||
* // our code into an $apply call so that the model changes are properly observed.
|
||||
* scope.$apply(function() {
|
||||
* deferred.notify('About to greet ' + name + '.');
|
||||
* deferred.notify('About to greet ' + name + '.');
|
||||
*
|
||||
* if (okToGreet(name)) {
|
||||
* deferred.resolve('Hello, ' + name + '!');
|
||||
* } else {
|
||||
* deferred.reject('Greeting ' + name + ' is not allowed.');
|
||||
* }
|
||||
* });
|
||||
* if (okToGreet(name)) {
|
||||
* deferred.resolve('Hello, ' + name + '!');
|
||||
* } else {
|
||||
* deferred.reject('Greeting ' + name + ' is not allowed.');
|
||||
* }
|
||||
* }, 1000);
|
||||
*
|
||||
* return deferred.promise;
|
||||
|
||||
+1
-1
@@ -639,7 +639,7 @@ function $RootScopeProvider(){
|
||||
if ((value = watch.get(current)) !== (last = watch.last) &&
|
||||
!(watch.eq
|
||||
? equals(value, last)
|
||||
: (typeof value == 'number' && typeof last == 'number'
|
||||
: (typeof value === 'number' && typeof last === 'number'
|
||||
&& isNaN(value) && isNaN(last)))) {
|
||||
dirty = true;
|
||||
lastDirtyWatch = watch;
|
||||
|
||||
+1
-1
@@ -764,7 +764,7 @@ function $SceProvider() {
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $sce#parse
|
||||
* @name $sce#parseAs
|
||||
*
|
||||
* @description
|
||||
* Converts Angular {@link guide/expression expression} into a function. This is like {@link
|
||||
|
||||
@@ -744,7 +744,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
* @kind function
|
||||
*
|
||||
* @param {boolean=} value If provided then set the animation on or off.
|
||||
* @param {DOMElement} element If provided then the element will be used to represent the enable/disable operation
|
||||
* @param {DOMElement=} element If provided then the element will be used to represent the enable/disable operation
|
||||
* @return {boolean} Current animation state.
|
||||
*
|
||||
* @description
|
||||
|
||||
Vendored
+8
-2
@@ -455,7 +455,7 @@ angular.mock.$IntervalProvider = function() {
|
||||
iteration = 0,
|
||||
skipApply = (angular.isDefined(invokeApply) && !invokeApply);
|
||||
|
||||
count = (angular.isDefined(count)) ? count : 0,
|
||||
count = (angular.isDefined(count)) ? count : 0;
|
||||
promise.then(null, null, fn);
|
||||
|
||||
promise.$$intervalId = nextRepeatId;
|
||||
@@ -1719,7 +1719,7 @@ angular.mock.$RootElementProvider = function() {
|
||||
*
|
||||
* # ngMock
|
||||
*
|
||||
* The `ngMock` module providers support to inject and mock Angular services into unit tests.
|
||||
* The `ngMock` module provides support to inject and mock Angular services into unit tests.
|
||||
* In addition, ngMock also extends various core ng services such that they can be
|
||||
* inspected and controlled in a synchronous manner within test code.
|
||||
*
|
||||
@@ -1958,6 +1958,12 @@ if(window.jasmine || window.mocha) {
|
||||
(window.afterEach || window.teardown)(function() {
|
||||
var injector = currentSpec.$injector;
|
||||
|
||||
angular.forEach(currentSpec.$modules, function(module) {
|
||||
if (module && module.$$hashKey) {
|
||||
module.$$hashKey = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
currentSpec.$injector = null;
|
||||
currentSpec.$modules = null;
|
||||
currentSpec = null;
|
||||
|
||||
@@ -522,23 +522,32 @@ angular.module('ngResource', ['ng']).
|
||||
extend({}, extractParams(data, action.params || {}), params),
|
||||
action.url);
|
||||
|
||||
var promise = $http(httpConfig).then(function(response) {
|
||||
var promise = $http(httpConfig).then(function (response) {
|
||||
var data = response.data,
|
||||
promise = value.$promise;
|
||||
promise = value.$promise;
|
||||
|
||||
if (data) {
|
||||
// Need to convert action.isArray to boolean in case it is undefined
|
||||
// jshint -W018
|
||||
if (angular.isArray(data) !== (!!action.isArray)) {
|
||||
throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' +
|
||||
'response to contain an {0} but got an {1}',
|
||||
action.isArray?'array':'object', angular.isArray(data)?'array':'object');
|
||||
throw $resourceMinErr('badcfg',
|
||||
'Error in resource configuration. Expected ' +
|
||||
'response to contain an {0} but got an {1}',
|
||||
action.isArray ? 'array' : 'object',
|
||||
angular.isArray(data) ? 'array' : 'object');
|
||||
}
|
||||
// jshint +W018
|
||||
if (action.isArray) {
|
||||
value.length = 0;
|
||||
forEach(data, function(item) {
|
||||
value.push(new Resource(item));
|
||||
forEach(data, function (item) {
|
||||
if (typeof item === "object") {
|
||||
value.push(new Resource(item));
|
||||
} else {
|
||||
// Valid JSON values may be string literals, and these should not be converted
|
||||
// into objects. These items will not have access to the Resource prototype
|
||||
// methods, but unfortunately there
|
||||
value.push(item);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
shallowClearAndCopy(data, value);
|
||||
|
||||
@@ -294,7 +294,7 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
|
||||
function push(value) {
|
||||
if (value === undefined) {
|
||||
value = '';
|
||||
} else if (typeof value != 'string') {
|
||||
} else if (typeof value !== 'string') {
|
||||
value = angular.toJson(value);
|
||||
}
|
||||
result.push('' + value);
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
"assertNotHasOwnProperty": false,
|
||||
"getter": false,
|
||||
"getBlockElements": false,
|
||||
"VALIDITY_STATE_PROPERTY": true,
|
||||
|
||||
/* filters.js */
|
||||
"getFirstThursdayOfYear": false,
|
||||
|
||||
@@ -22,6 +22,36 @@ describe('api', function() {
|
||||
expect(map.get('b')).toBe(1);
|
||||
expect(map.get('c')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should maintain hashKey for object keys', function() {
|
||||
var map = new HashMap();
|
||||
var key = {};
|
||||
map.get(key);
|
||||
expect(key.$$hashKey).toBeDefined();
|
||||
});
|
||||
|
||||
it('should maintain hashKey for function keys', function() {
|
||||
var map = new HashMap();
|
||||
var key = function() {};
|
||||
map.get(key);
|
||||
expect(key.$$hashKey).toBeDefined();
|
||||
});
|
||||
|
||||
it('should share hashKey between HashMap by default', function() {
|
||||
var map1 = new HashMap(), map2 = new HashMap();
|
||||
var key1 = {}, key2 = {};
|
||||
map1.get(key1);
|
||||
map2.get(key2);
|
||||
expect(key1.$$hashKey).not.toEqual(key2.$$hashKey);
|
||||
});
|
||||
|
||||
it('should maintain hashKey per HashMap if flag is passed', function() {
|
||||
var map1 = new HashMap([], true), map2 = new HashMap([], true);
|
||||
var key1 = {}, key2 = {};
|
||||
map1.get(key1);
|
||||
map2.get(key2);
|
||||
expect(key1.$$hashKey).toEqual(key2.$$hashKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -293,6 +293,29 @@ describe('injector', function() {
|
||||
expect(log).toEqual('abc');
|
||||
});
|
||||
|
||||
it('should load different instances of dependent functions', function() {
|
||||
function generateValueModule(name, value) {
|
||||
return function ($provide) {
|
||||
$provide.value(name, value);
|
||||
};
|
||||
}
|
||||
var injector = createInjector([generateValueModule('name1', 'value1'),
|
||||
generateValueModule('name2', 'value2')]);
|
||||
expect(injector.get('name2')).toBe('value2');
|
||||
});
|
||||
|
||||
it('should load same instance of dependent function only once', function() {
|
||||
var count = 0;
|
||||
function valueModule($provide) {
|
||||
count++;
|
||||
$provide.value('name', 'value');
|
||||
}
|
||||
|
||||
var injector = createInjector([valueModule, valueModule]);
|
||||
expect(injector.get('name')).toBe('value');
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
it('should execute runBlocks after injector creation', function() {
|
||||
var log = '';
|
||||
angular.module('a', [], function(){ log += 'a'; }).run(function() { log += 'A'; });
|
||||
@@ -630,7 +653,7 @@ describe('injector', function() {
|
||||
$provide.factory('service', function(service){});
|
||||
return function(service) {};
|
||||
}]);
|
||||
}).toThrowMinErr('$injector', 'cdep', 'Circular dependency found: service');
|
||||
}).toThrowMinErr('$injector', 'cdep', 'Circular dependency found: service <- service');
|
||||
});
|
||||
|
||||
|
||||
@@ -641,7 +664,7 @@ describe('injector', function() {
|
||||
$provide.factory('b', function(a){});
|
||||
return function(a) {};
|
||||
}]);
|
||||
}).toThrowMinErr('$injector', 'cdep', 'Circular dependency found: b <- a');
|
||||
}).toThrowMinErr('$injector', 'cdep', 'Circular dependency found: a <- b <- a');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -248,13 +248,13 @@ function isCssVisible(node) {
|
||||
|
||||
function assertHidden(node) {
|
||||
if (isCssVisible(node)) {
|
||||
throw new Error('Node should be hidden but was visible: ' + angular.module.ngMock.dump(node));
|
||||
throw new Error('Node should be hidden but was visible: ' + angular.mock.dump(node));
|
||||
}
|
||||
}
|
||||
|
||||
function assertVisible(node) {
|
||||
if (!isCssVisible(node)) {
|
||||
throw new Error('Node should be visible but was hidden: ' + angular.module.ngMock.dump(node));
|
||||
throw new Error('Node should be visible but was hidden: ' + angular.mock.dump(node));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3764,6 +3764,88 @@ describe('$compile', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should not pass transclusion into a template directive when the directive didn\'t request transclusion', function() {
|
||||
|
||||
module(function($compileProvider) {
|
||||
|
||||
$compileProvider.directive('transFoo', valueFn({
|
||||
template: '<div>' +
|
||||
'<div no-trans-bar></div>' +
|
||||
'<div ng-transclude>this one should get replaced with content</div>' +
|
||||
'<div class="foo" ng-transclude></div>' +
|
||||
'</div>',
|
||||
transclude: true
|
||||
|
||||
}));
|
||||
|
||||
$compileProvider.directive('noTransBar', valueFn({
|
||||
template: '<div>' +
|
||||
// This ng-transclude is invalid. It should throw an error.
|
||||
'<div class="bar" ng-transclude></div>' +
|
||||
'</div>',
|
||||
transclude: false
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var message = 'Illegal use of ngTransclude directive in the template! No parent ' +
|
||||
'directive that requires a transclusion found. Element: <div class="bar" ' +
|
||||
'ng-transclude="">';
|
||||
if (msie <= 8) {
|
||||
// MSIE ヽ(`Д´)ノ
|
||||
message = 'Illegal use of ngTransclude directive in the template! No parent ' +
|
||||
'directive that requires a transclusion found. Element: <div class=bar ' +
|
||||
'ng-transclude>';
|
||||
}
|
||||
expect(function() {
|
||||
$compile('<div trans-foo>content</div>')($rootScope);
|
||||
}).toThrowMinErr('ngTransclude', 'orphan', message);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not pass transclusion into a templateUrl directive', function() {
|
||||
|
||||
module(function($compileProvider) {
|
||||
|
||||
$compileProvider.directive('transFoo', valueFn({
|
||||
template: '<div>' +
|
||||
'<div no-trans-bar></div>' +
|
||||
'<div ng-transclude>this one should get replaced with content</div>' +
|
||||
'<div class="foo" ng-transclude></div>' +
|
||||
'</div>',
|
||||
transclude: true
|
||||
|
||||
}));
|
||||
|
||||
$compileProvider.directive('noTransBar', valueFn({
|
||||
templateUrl: 'noTransBar.html',
|
||||
transclude: false
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope, $templateCache) {
|
||||
$templateCache.put('noTransBar.html',
|
||||
'<div>' +
|
||||
// This ng-transclude is invalid. It should throw an error.
|
||||
'<div class="bar" ng-transclude></div>' +
|
||||
'</div>');
|
||||
var message = 'Illegal use of ngTransclude directive in the template! No parent directive that '
|
||||
+ 'requires a transclusion found. Element: <div class="bar" ng-transclude="">';
|
||||
if (msie <= 8) {
|
||||
// MSIE ヽ(`Д´)ノ
|
||||
message = 'Illegal use of ngTransclude directive in the template! No parent directive that '
|
||||
+ 'requires a transclusion found. Element: <div class=bar ng-transclude>';
|
||||
}
|
||||
expect(function() {
|
||||
element = $compile('<div trans-foo>content</div>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
}).toThrowMinErr('ngTransclude', 'orphan', message);
|
||||
});
|
||||
});
|
||||
|
||||
it('should make the result of a transclusion available to the parent directive in post-linking phase' +
|
||||
'(template)', function() {
|
||||
module(function() {
|
||||
@@ -3982,6 +4064,182 @@ describe('$compile', function() {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('nested transcludes', function() {
|
||||
|
||||
beforeEach(module(function($compileProvider) {
|
||||
|
||||
$compileProvider.directive('noop', valueFn({}));
|
||||
|
||||
$compileProvider.directive('sync', valueFn({
|
||||
template: '<div ng-transclude></div>',
|
||||
transclude: true
|
||||
}));
|
||||
|
||||
$compileProvider.directive('async', valueFn({
|
||||
templateUrl: 'async',
|
||||
transclude: true
|
||||
}));
|
||||
|
||||
$compileProvider.directive('syncSync', valueFn({
|
||||
template: '<div noop><div sync><div ng-transclude></div></div></div>',
|
||||
transclude: true
|
||||
}));
|
||||
|
||||
$compileProvider.directive('syncAsync', valueFn({
|
||||
template: '<div noop><div async><div ng-transclude></div></div></div>',
|
||||
transclude: true
|
||||
}));
|
||||
|
||||
$compileProvider.directive('asyncSync', valueFn({
|
||||
templateUrl: 'asyncSync',
|
||||
transclude: true
|
||||
}));
|
||||
|
||||
$compileProvider.directive('asyncAsync', valueFn({
|
||||
templateUrl: 'asyncAsync',
|
||||
transclude: true
|
||||
}));
|
||||
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($templateCache) {
|
||||
$templateCache.put('async', '<div ng-transclude></div>');
|
||||
$templateCache.put('asyncSync', '<div noop><div sync><div ng-transclude></div></div></div>');
|
||||
$templateCache.put('asyncAsync', '<div noop><div async><div ng-transclude></div></div></div>');
|
||||
}));
|
||||
|
||||
|
||||
it('should allow nested transclude directives with sync template containing sync template', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div sync-sync>transcluded content</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('transcluded content');
|
||||
}));
|
||||
|
||||
it('should allow nested transclude directives with sync template containing async template', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div sync-async>transcluded content</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('transcluded content');
|
||||
}));
|
||||
|
||||
it('should allow nested transclude directives with async template containing sync template', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div async-sync>transcluded content</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('transcluded content');
|
||||
}));
|
||||
|
||||
it('should allow nested transclude directives with async template containing asynch template', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div async-async>transcluded content</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('transcluded content');
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('nested isolated scope transcludes', function() {
|
||||
beforeEach(module(function($compileProvider) {
|
||||
|
||||
$compileProvider.directive('trans', valueFn({
|
||||
restrict: 'E',
|
||||
template: '<div ng-transclude></div>',
|
||||
transclude: true
|
||||
}));
|
||||
|
||||
$compileProvider.directive('transAsync', valueFn({
|
||||
restrict: 'A',
|
||||
templateUrl: 'transAsync',
|
||||
transclude: true
|
||||
}));
|
||||
|
||||
$compileProvider.directive('iso', valueFn({
|
||||
restrict: 'A',
|
||||
transclude: true,
|
||||
template: '<div trans><span ng-transclude></span></div>',
|
||||
scope: {}
|
||||
}));
|
||||
$compileProvider.directive('isoAsync1', valueFn({
|
||||
restrict: 'A',
|
||||
transclude: true,
|
||||
template: '<div trans-async><span ng-transclude></span></div>',
|
||||
scope: {}
|
||||
}));
|
||||
$compileProvider.directive('isoAsync2', valueFn({
|
||||
restrict: 'A',
|
||||
transclude: true,
|
||||
templateUrl: 'isoAsync',
|
||||
scope: {}
|
||||
}));
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($templateCache) {
|
||||
$templateCache.put('transAsync', '<div ng-transclude></div>');
|
||||
$templateCache.put('isoAsync', '<div trans-async><span ng-transclude></span></div>');
|
||||
}));
|
||||
|
||||
|
||||
it('should pass the outer scope to the transclude on the isolated template sync-sync', inject(function($compile, $rootScope) {
|
||||
|
||||
$rootScope.val = 'transcluded content';
|
||||
element = $compile('<div iso><span ng-bind="val"></span></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('transcluded content');
|
||||
}));
|
||||
|
||||
it('should pass the outer scope to the transclude on the isolated template async-sync', inject(function($compile, $rootScope) {
|
||||
|
||||
$rootScope.val = 'transcluded content';
|
||||
element = $compile('<div iso-async1><span ng-bind="val"></span></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('transcluded content');
|
||||
}));
|
||||
|
||||
it('should pass the outer scope to the transclude on the isolated template async-async', inject(function($compile, $rootScope) {
|
||||
|
||||
$rootScope.val = 'transcluded content';
|
||||
element = $compile('<div iso-async2><span ng-bind="val"></span></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('transcluded content');
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('multiple siblings receiving transclusion', function() {
|
||||
|
||||
it("should only receive transclude from parent", function() {
|
||||
|
||||
module(function($compileProvider) {
|
||||
|
||||
$compileProvider.directive('myExample', valueFn({
|
||||
scope: {},
|
||||
link: function link(scope, element, attrs) {
|
||||
var foo = element[0].querySelector('.foo');
|
||||
scope.children = angular.element(foo).children().length;
|
||||
},
|
||||
template: '<div>' +
|
||||
'<div>myExample {{children}}!</div>' +
|
||||
'<div ng-if="children">has children</div>' +
|
||||
'<div class="foo" ng-transclude></div>' +
|
||||
'</div>',
|
||||
transclude: true
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var element = $compile('<div my-example></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('myExample 0!');
|
||||
dealoc(element);
|
||||
|
||||
element = $compile('<div my-example><p></p></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('myExample 1!has children');
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4318,6 +4576,57 @@ describe('$compile', function() {
|
||||
|
||||
expect(element.text()).toBe('-->|x|');
|
||||
}));
|
||||
|
||||
|
||||
// See https://github.com/angular/angular.js/issues/7183
|
||||
it("should pass transclusion through to template of a 'replace' directive", function() {
|
||||
module(function() {
|
||||
directive('transSync', function() {
|
||||
return {
|
||||
transclude: true,
|
||||
link: function(scope, element, attr, ctrl, transclude) {
|
||||
|
||||
expect(transclude).toEqual(jasmine.any(Function));
|
||||
|
||||
transclude(function(child) { element.append(child); });
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
directive('trans', function($timeout) {
|
||||
return {
|
||||
transclude: true,
|
||||
link: function(scope, element, attrs, ctrl, transclude) {
|
||||
|
||||
// We use timeout here to simulate how ng-if works
|
||||
$timeout(function() {
|
||||
transclude(function(child) { element.append(child); });
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
directive('replaceWithTemplate', function() {
|
||||
return {
|
||||
templateUrl: "template.html",
|
||||
replace: true
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope, $templateCache, $timeout) {
|
||||
|
||||
$templateCache.put('template.html', '<div trans-sync>Content To Be Transcluded</div>');
|
||||
|
||||
expect(function() {
|
||||
element = $compile('<div><div trans><div replace-with-template></div></div></div>')($rootScope);
|
||||
$timeout.flush();
|
||||
}).not.toThrow();
|
||||
|
||||
expect(element.text()).toEqual('Content To Be Transcluded');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4592,6 +4901,83 @@ describe('$compile', function() {
|
||||
expect(element.attr('test')).toBe('Misko');
|
||||
}));
|
||||
|
||||
it('should bind after digest but not before when after overridden attribute', inject(function($compile, $rootScope) {
|
||||
$rootScope.name = "Misko";
|
||||
element = $compile('<span test="123" ng-attr-test="{{name}}"></span>')($rootScope);
|
||||
expect(element.attr('test')).toBe('123');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('test')).toBe('Misko');
|
||||
}));
|
||||
|
||||
it('should bind after digest but not before when before overridden attribute', inject(function($compile, $rootScope) {
|
||||
$rootScope.name = "Misko";
|
||||
element = $compile('<span ng-attr-test="{{name}}" test="123"></span>')($rootScope);
|
||||
expect(element.attr('test')).toBe('123');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('test')).toBe('Misko');
|
||||
}));
|
||||
|
||||
|
||||
describe('in directive', function() {
|
||||
beforeEach(module(function() {
|
||||
directive('syncTest', function(log) {
|
||||
return {
|
||||
link: {
|
||||
pre: function(s, e, attr) { log(attr.test); },
|
||||
post: function(s, e, attr) { log(attr.test); }
|
||||
}
|
||||
};
|
||||
});
|
||||
directive('asyncTest', function(log) {
|
||||
return {
|
||||
templateUrl: 'async.html',
|
||||
link: {
|
||||
pre: function(s, e, attr) { log(attr.test); },
|
||||
post: function(s, e, attr) { log(attr.test); }
|
||||
}
|
||||
};
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($templateCache) {
|
||||
$templateCache.put('async.html', '<h1>Test</h1>');
|
||||
}));
|
||||
|
||||
it('should provide post-digest value in synchronous directive link functions when after overridden attribute',
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
$rootScope.test = "TEST";
|
||||
element = $compile('<div sync-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
|
||||
expect(element.attr('test')).toBe('123');
|
||||
expect(log.toArray()).toEqual(['TEST', 'TEST']);
|
||||
}));
|
||||
|
||||
it('should provide post-digest value in synchronous directive link functions when before overridden attribute',
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
$rootScope.test = "TEST";
|
||||
element = $compile('<div sync-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
|
||||
expect(element.attr('test')).toBe('123');
|
||||
expect(log.toArray()).toEqual(['TEST', 'TEST']);
|
||||
}));
|
||||
|
||||
|
||||
it('should provide post-digest value in asynchronous directive link functions when after overridden attribute',
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
$rootScope.test = "TEST";
|
||||
element = $compile('<div async-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
|
||||
expect(element.attr('test')).toBe('123');
|
||||
$rootScope.$digest();
|
||||
expect(log.toArray()).toEqual(['TEST', 'TEST']);
|
||||
}));
|
||||
|
||||
it('should provide post-digest value in asynchronous directive link functions when before overridden attribute',
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
$rootScope.test = "TEST";
|
||||
element = $compile('<div async-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
|
||||
expect(element.attr('test')).toBe('123');
|
||||
$rootScope.$digest();
|
||||
expect(log.toArray()).toEqual(['TEST', 'TEST']);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should work with different prefixes', inject(function($compile, $rootScope) {
|
||||
$rootScope.name = "Misko";
|
||||
|
||||
@@ -410,15 +410,33 @@ describe('ngModel', function() {
|
||||
|
||||
|
||||
describe('input', function() {
|
||||
var formElm, inputElm, scope, $compile, $sniffer, $browser, changeInputValueTo;
|
||||
var formElm, inputElm, scope, $compile, $sniffer, $browser, changeInputValueTo, currentSpec;
|
||||
|
||||
function compileInput(inputHtml) {
|
||||
function compileInput(inputHtml, mockValidity) {
|
||||
inputElm = jqLite(inputHtml);
|
||||
if (isObject(mockValidity)) {
|
||||
VALIDITY_STATE_PROPERTY = 'ngMockValidity';
|
||||
inputElm.prop(VALIDITY_STATE_PROPERTY, mockValidity);
|
||||
currentSpec.after(function() {
|
||||
VALIDITY_STATE_PROPERTY = 'validity';
|
||||
});
|
||||
}
|
||||
formElm = jqLite('<form name="form"></form>');
|
||||
formElm.append(inputElm);
|
||||
$compile(formElm)(scope);
|
||||
}
|
||||
|
||||
var attrs;
|
||||
beforeEach(function() { currentSpec = this; });
|
||||
afterEach(function() { currentSpec = null; });
|
||||
beforeEach(module(function($compileProvider) {
|
||||
$compileProvider.directive('attrCapture', function() {
|
||||
return function(scope, element, $attrs) {
|
||||
attrs = $attrs;
|
||||
};
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($injector, _$sniffer_, _$browser_) {
|
||||
$sniffer = _$sniffer_;
|
||||
$browser = _$browser_;
|
||||
@@ -844,6 +862,33 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should invalidate number if suffering from bad input', function() {
|
||||
compileInput('<input type="number" ng-model="age" />', {
|
||||
valid: false,
|
||||
badInput: true
|
||||
});
|
||||
|
||||
changeInputValueTo('10a');
|
||||
expect(scope.age).toBeUndefined();
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should validate number if transition from bad input to empty string', function() {
|
||||
var validity = {
|
||||
valid: false,
|
||||
badInput: true
|
||||
};
|
||||
compileInput('<input type="number" ng-model="age" />', validity);
|
||||
changeInputValueTo('10a');
|
||||
validity.badInput = false;
|
||||
validity.valid = true;
|
||||
changeInputValueTo('');
|
||||
expect(scope.age).toBeNull();
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
describe('min', function() {
|
||||
|
||||
it('should validate', function() {
|
||||
|
||||
@@ -199,6 +199,28 @@ describe('ngIf and transcludes', function() {
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should use the correct transcluded scope', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('iso', valueFn({
|
||||
link: function(scope) {
|
||||
scope.val = 'value in iso scope';
|
||||
},
|
||||
restrict: 'A',
|
||||
transclude: true,
|
||||
template: '<div ng-if="true">val={{val}}-<div ng-transclude></div></div>',
|
||||
scope: {}
|
||||
}));
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
$rootScope.val = 'transcluded content';
|
||||
var element = $compile('<div iso><span ng-bind="val"></span></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(trim(element.text())).toEqual('val=value in iso scope-transcluded content');
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngIf animations', function () {
|
||||
|
||||
@@ -145,6 +145,10 @@ describe('filters', function() {
|
||||
expect(number(.99, 2)).toEqual("0.99");
|
||||
expect(number(.999, 3)).toEqual("0.999");
|
||||
expect(number(.9999, 3)).toEqual("1.000");
|
||||
expect(number(1.9, 2)).toEqual("1.90");
|
||||
expect(number(1.99, 2)).toEqual("1.99");
|
||||
expect(number(1.999, 3)).toEqual("1.999");
|
||||
expect(number(1.9999, 3)).toEqual("2.000");
|
||||
expect(number(1234.567, 0)).toEqual("1,235");
|
||||
expect(number(1234.567, 1)).toEqual("1,234.6");
|
||||
expect(number(1234.567, 2)).toEqual("1,234.57");
|
||||
@@ -152,6 +156,7 @@ describe('filters', function() {
|
||||
expect(number(1.255, 1)).toEqual("1.3");
|
||||
expect(number(1.255, 2)).toEqual("1.26");
|
||||
expect(number(1.255, 3)).toEqual("1.255");
|
||||
expect(number(0, 8)).toEqual("0.00000000");
|
||||
});
|
||||
|
||||
it('should filter exponentially large numbers', function() {
|
||||
|
||||
@@ -98,6 +98,25 @@ describe('$httpBackend', function() {
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should not touch xhr.statusText when request is aborted on IE9 or lower', function() {
|
||||
callback.andCallFake(function(status, response, headers, statusText) {
|
||||
expect(statusText).toBe((!msie || msie >= 10) ? 'OK' : '');
|
||||
});
|
||||
|
||||
$backend('GET', '/url', null, callback, {}, 2000);
|
||||
xhr = MockXhr.$$lastInstance;
|
||||
spyOn(xhr, 'abort');
|
||||
|
||||
fakeTimeout.flush();
|
||||
expect(xhr.abort).toHaveBeenCalledOnce();
|
||||
|
||||
xhr.status = 0;
|
||||
xhr.readyState = 4;
|
||||
xhr.statusText = 'OK';
|
||||
xhr.onreadystatechange();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should call completion function with empty string if not present', function() {
|
||||
callback.andCallFake(function(status, response, headers, statusText) {
|
||||
expect(statusText).toBe('');
|
||||
|
||||
@@ -860,6 +860,25 @@ describe('$http', function() {
|
||||
|
||||
$httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should check the cache before checking the XSRF cookie', inject(function($browser, $cacheFactory) {
|
||||
var testCache = $cacheFactory('testCache'),
|
||||
executionOrder = [];
|
||||
|
||||
spyOn($browser, 'cookies').andCallFake(function() {
|
||||
executionOrder.push('cookies');
|
||||
return {'XSRF-TOKEN':'foo'};
|
||||
});
|
||||
spyOn(testCache, 'get').andCallFake(function() {
|
||||
executionOrder.push('cache');
|
||||
});
|
||||
|
||||
$httpBackend.expect('GET', '/url', undefined).respond('');
|
||||
$http({url: '/url', method: 'GET', cache: testCache});
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(executionOrder).toEqual(['cache', 'cookies']);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -267,4 +267,25 @@ describe('$interval', function() {
|
||||
expect($interval.cancel()).toBe(false);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('$window delegation', function() {
|
||||
it('should use $window.setInterval instead of the global function', inject(function ($interval, $window) {
|
||||
var setIntervalSpy = spyOn($window, 'setInterval');
|
||||
|
||||
$interval(noop, 1000);
|
||||
expect(setIntervalSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should use $window.clearInterval instead of the global function', inject(function ($interval, $window) {
|
||||
var clearIntervalSpy = spyOn($window, 'clearInterval');
|
||||
|
||||
$interval(noop, 1000, 1);
|
||||
$window.flush(1000);
|
||||
expect(clearIntervalSpy).toHaveBeenCalled();
|
||||
|
||||
clearIntervalSpy.reset();
|
||||
$interval.cancel($interval(noop, 1000));
|
||||
expect(clearIntervalSpy).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
+225
-92
@@ -637,90 +637,52 @@ describe('parser', function() {
|
||||
expect(scope.$eval('a + \n b.c + \r "\td" + \t \r\n\r "\r\n\n"')).toEqual("abc\td\r\n\n");
|
||||
});
|
||||
|
||||
|
||||
describe('sandboxing', function() {
|
||||
describe('Function constructor', function() {
|
||||
it('should NOT allow access to Function constructor in getter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor("alert(1)")');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor("alert(1)")');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('[].toString.constructor.foo');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: [].toString.constructor.foo');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString["constructor"]');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString["constructor"]');
|
||||
expect(function() {
|
||||
scope.$eval('{}["toString"]["constructor"]');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}["toString"]["constructor"]');
|
||||
|
||||
scope.a = [];
|
||||
expect(function() {
|
||||
scope.$eval('a.toString.constructor', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: a.toString.constructor');
|
||||
expect(function() {
|
||||
scope.$eval('a.toString["constructor"]', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: a.toString["constructor"]');
|
||||
});
|
||||
|
||||
it('should NOT allow access to Function constructor in setter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor = 1');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor.a = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'$parse', 'isecfn','Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor.a = 1');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString["constructor"]["constructor"] = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString["constructor"]["constructor"] = 1');
|
||||
|
||||
|
||||
scope.key1 = "const";
|
||||
scope.key2 = "ructor";
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString[key1 + key2].foo = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString[key1 + key2].foo = 1');
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString[key1 + key2].foo = 1');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString["constructor"]["a"] = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString["constructor"]["a"] = 1');
|
||||
|
||||
scope.a = [];
|
||||
expect(function() {
|
||||
scope.$eval('a.toString.constructor = 1', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: a.toString.constructor = 1');
|
||||
});
|
||||
|
||||
@@ -730,21 +692,84 @@ describe('parser', function() {
|
||||
expect(function() {
|
||||
scope.$eval('foo["bar"]');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: foo["bar"]');
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should NOT allow access to Function constructor in getter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Function prototype functions', function () {
|
||||
it('should NOT allow invocation to Function.call', function() {
|
||||
scope.fn = Function.prototype.call;
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('$eval.call()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: $eval.call()');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('fn()')
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: fn()');
|
||||
});
|
||||
|
||||
it('should NOT allow invocation to Function.apply', function() {
|
||||
scope.apply = Function.prototype.apply;
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('$eval.apply()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: $eval.apply()');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('apply()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: apply()');
|
||||
});
|
||||
|
||||
|
||||
// IE8 doesn't have Function.prototype.bind
|
||||
if (!msie || msie > 8) {
|
||||
it('should NOT allow invocation to Function.bind', function () {
|
||||
scope.bind = Function.prototype.bind;
|
||||
|
||||
expect(function () {
|
||||
scope.$eval('$eval.bind()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: $eval.bind()');
|
||||
|
||||
expect(function () {
|
||||
scope.$eval('bind()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: bind()');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Object constructor', function() {
|
||||
|
||||
it('should NOT allow access to Object constructor that has been aliased', function() {
|
||||
scope.foo = { "bar": Object };
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('foo.bar.keys(foo)');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.bar.keys(foo)');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('foo["bar"]["keys"](foo)');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
|
||||
'Expression: foo["bar"]["keys"](foo)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Window and $element/node', function() {
|
||||
it('should NOT allow access to the Window or DOM when indexing', inject(function($window, $document) {
|
||||
@@ -753,15 +778,16 @@ describe('parser', function() {
|
||||
expect(function() {
|
||||
scope.$eval('wrap["w"]', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'disallowed! Expression: wrap["w"]');
|
||||
expect(function() {
|
||||
scope.$eval('wrap["d"]', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'disallowed! Expression: wrap["d"]');
|
||||
}));
|
||||
|
||||
|
||||
it('should NOT allow access to the Window or DOM returned from a function', inject(function($window, $document) {
|
||||
scope.getWin = valueFn($window);
|
||||
scope.getDoc = valueFn($document);
|
||||
@@ -769,12 +795,12 @@ describe('parser', function() {
|
||||
expect(function() {
|
||||
scope.$eval('getWin()', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'disallowed! Expression: getWin()');
|
||||
expect(function() {
|
||||
scope.$eval('getDoc()', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'disallowed! Expression: getDoc()');
|
||||
}));
|
||||
|
||||
@@ -783,12 +809,12 @@ describe('parser', function() {
|
||||
expect(function() {
|
||||
scope.$eval('a.b.win.alert(1)', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'disallowed! Expression: a.b.win.alert(1)');
|
||||
expect(function() {
|
||||
scope.$eval('a.b.doc.on("click")', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'disallowed! Expression: a.b.doc.on("click")');
|
||||
}));
|
||||
|
||||
@@ -814,39 +840,145 @@ describe('parser', function() {
|
||||
expect(function() { scope.$eval('array'); }).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('overriding constructor', function() {
|
||||
it('should evaluate grouped expressions', function() {
|
||||
scope.foo = function foo() {
|
||||
return "foo";
|
||||
};
|
||||
// When not overridden, access should be restricted both by the dot operator and by the
|
||||
// index operator.
|
||||
expect(function() {
|
||||
scope.$eval('foo.constructor()', scope)
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.constructor()');
|
||||
expect(function() {
|
||||
scope.$eval('foo["constructor"]()', scope)
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: foo["constructor"]()');
|
||||
describe('Disallowed fields', function() {
|
||||
it('should NOT allow access or invocation of __defineGetter__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__defineGetter__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__defineGetter__("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
// User defined value assigned to constructor.
|
||||
scope.foo.constructor = function constructor() {
|
||||
return "custom constructor";
|
||||
};
|
||||
// Dot operator should still block it.
|
||||
expect(function() {
|
||||
scope.$eval('foo.constructor()', scope)
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.constructor()');
|
||||
// However, the index operator should allow it.
|
||||
expect(scope.$eval('foo["constructor"]()', scope)).toBe('custom constructor');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__defineGetter__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__defineGetter__"]("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = "__define";
|
||||
scope.b = "Getter__";
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
|
||||
it('should NOT allow access or invocation of __defineSetter__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__defineSetter__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__defineSetter__("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}["__defineSetter__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__defineSetter__"]("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = "__define";
|
||||
scope.b = "Setter__";
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
|
||||
it('should NOT allow access or invocation of __lookupGetter__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__lookupGetter__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__lookupGetter__("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}["__lookupGetter__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__lookupGetter__"]("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = "__lookup";
|
||||
scope.b = "Getter__";
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
|
||||
it('should NOT allow access or invocation of __lookupSetter__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__lookupSetter__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__lookupSetter__("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}["__lookupSetter__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__lookupSetter__"]("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = "__lookup";
|
||||
scope.b = "Setter__";
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
|
||||
it('should NOT allow access to __proto__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__proto__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__proto__.foo = 1');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}["__proto__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__proto__"].foo = 1');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = "__pro";
|
||||
scope.b = "to__";
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b].foo = 1');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
});
|
||||
|
||||
it('should prevent the exploit', function() {
|
||||
expect(function() {
|
||||
scope.$eval('' +
|
||||
' "".sub.call.call(' +
|
||||
'({})["constructor"].getOwnPropertyDescriptor("".sub.__proto__, "constructor").value,' +
|
||||
'null,' +
|
||||
'"alert(1)"' +
|
||||
')()' +
|
||||
'')
|
||||
}).toThrow();
|
||||
})
|
||||
});
|
||||
|
||||
it('should call the function from the received instance and not from a new one', function() {
|
||||
@@ -1005,6 +1137,7 @@ describe('parser', function() {
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('constant', function() {
|
||||
it('should mark scalar value expressions as constant', inject(function($parse) {
|
||||
expect($parse('12.3').constant).toBe(true);
|
||||
|
||||
Vendored
+17
@@ -805,6 +805,23 @@ describe('ngMock', function() {
|
||||
expect(example).toEqual('win');
|
||||
});
|
||||
});
|
||||
|
||||
describe('module cleanup', function() {
|
||||
function testFn() {
|
||||
|
||||
}
|
||||
|
||||
it('should add hashKey to module function', function() {
|
||||
module(testFn);
|
||||
inject(function () {
|
||||
expect(testFn.$$hashKey).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should cleanup hashKey after previous test', function() {
|
||||
expect(testFn.$$hashKey).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('in DSL', function() {
|
||||
|
||||
@@ -1044,6 +1044,27 @@ describe("resource", function() {
|
||||
$httpBackend.flush();
|
||||
expect(user).toEqualData([ {id: 1, name: 'user1'} ]);
|
||||
});
|
||||
|
||||
it('should not convert string literals in array into Resource objects', function() {
|
||||
$httpBackend.expect('GET', '/names.json').respond(["mary", "jane"]);
|
||||
var strings = $resource('/names.json').query();
|
||||
$httpBackend.flush();
|
||||
expect(strings).toEqualData(["mary", "jane"]);
|
||||
});
|
||||
|
||||
it('should not convert number literals in array into Resource objects', function() {
|
||||
$httpBackend.expect('GET', '/names.json').respond([213, 456]);
|
||||
var numbers = $resource('/names.json').query();
|
||||
$httpBackend.flush();
|
||||
expect(numbers).toEqualData([213, 456]);
|
||||
});
|
||||
|
||||
it('should not convert boolean literals in array into Resource objects', function() {
|
||||
$httpBackend.expect('GET', '/names.json').respond([true, false]);
|
||||
var bools = $resource('/names.json').query();
|
||||
$httpBackend.flush();
|
||||
expect(bools).toEqualData([true, false]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', function(){
|
||||
|
||||
Reference in New Issue
Block a user