Compare commits

...

45 Commits

Author SHA1 Message Date
Martin Staffa 996c901ff9 docs(changelog, guide/migration): mention rare BC for ngInclude
See https://github.com/angular/angular.js/issues/13555#issuecomment-165118890
for detailed explanation.
2016-06-07 10:44:56 +02:00
Georgios Kalpakas 6234cda41c docs(tutorial): add deprecation notice to v1.4.x tutorial and point to the correct branch
- Add a deprecation notice on the 'index' page.
- Hide the "Live Demo" buttons (since we don't have a live demo).
- Update the GitHub diff links to point to the `1.4-snapshot` angular-phonecat branch.
- Modify all git commangs to use the appropriate branch and tags (e.g. `1.4-step-*`).

Related to #14416.

Closes #14675
2016-05-31 12:48:16 +03:00
Martin Staffa f8103a82bf docs(changelog): update with changes for 1.4.11 2016-05-27 12:42:41 +02:00
Martin Staffa 0d764b581d fix(ngAnimate): safe-guard against missing document
In tests, the $document service might be mocked out without providing a real
document, which can lead to errors when the animator is attempting to read properties from it.

This commit provides an object {hidden: true}, if the $document service doesn't have 
a document. This will prevent the animation process from trying to run any animations.

This commit also changes the check for document.hidden slightly. It
should be accessed independently of the current animationsEnabled state.
Since animations are only enabled after two digests, it's possible that
some tests never reach the animationsEnabled = true state and therefore
aren't actually checking the document.hidden state, which means that
the previous fix only works if no more than two digests happen in the test.

(#14633)
2016-05-24 16:44:11 +02:00
Igor Minar 57a37fcc20 fix(ngAnimate): guard $document[0].hidden access in case it was mocked out
Some tests mock out  and now that we always access the hidden property, existing
tests can get broken. This change keeps the existing tests working.
2016-05-17 12:52:01 -07:00
Sander Boom 7f6ba5534f docs(guide/$location): fix typo
Closes #14607
2016-05-14 01:55:18 +03:00
Martin Staffa b24bfae585 Revert: ngAnimate changes for listening on visibilitychange
Reverts d3e123b, bf1acf7 and aa28e48.
Backporting the changes is complicated because we don't destroy the rootScope
after each test in 1.4.x
(#14574)
2016-05-09 12:35:07 -07:00
Martin Staffa d3e123b0a6 test($$isDocumentHidden): really fix jasmine syntax 2016-05-06 17:12:17 +02:00
Martin Staffa bf1acf7b21 test($): fix jasmine syntax 2016-05-06 16:30:30 +02:00
Maksim Ryzhikov fdaf4d5e27 fix(ngAnimate): properly handle empty jqLite collections
Previously `stripCommentsFromElement()` would return an empty Array (instead of a jqLite collection)
which would cause an exception to be thrown: "element.parent not a function".
This commit fixes it, by ensuring that the returned value is always a jqLite collection.

Closes #14558

Closes #14559
2016-05-06 08:14:32 -06:00
FGasper 1c47abc462 docs($location): clarify return value for path method
docs for return of path() inaccurately describe the function’s return when a value is passed in.

Closes #14544
2016-05-06 15:56:24 +02:00
Stephen Barker 5222703444 docs(guide/decorators): add decorator guide
+ explain decorators and how they are implemented in angular
+ explain how different types of services can be selected
+ explain `$delegate` objects and how they differ between services
+ warn of the risks/caveats of `$delegate` modification
+ note the exposure of `decorator` through the module api
+ show an example of decorating a core service
+ show an example of decorating a core directive
+ show an example of decorating a core filter

Closes #12163
Closes #14372
2016-05-06 15:56:15 +02:00
Michael Warner 4b38b44c91 docs(ngRequired): add 'restrict' info
The Angular Docs do not show the restrictions

Closes #14541
2016-05-06 15:56:01 +02:00
Martin Staffa aa28e48e17 perf(ngAnimate): listen for document visibility changes
Accessing the document for the hidden state is costly for
platforms like Electron. Instead, listen for visibilitychange
and store the state.

Closes #14066
2016-05-06 15:54:49 +02:00
Martin Probst f31586db41 fix(ng-bind-html): watch the unwrapped value using $sce.valueOf() (instead of toString())
Custom `$sce` implementations might not provide a `toString()` method on the wrapped object, or it
might be compiled away in non-debug mode. Watching the unwrapped value (retrieved using
`$sce.valueOf()`) fixes the problem.

The performance of this should be equivalent - `toString()` on objects usually touches all fields,
plus we will also avoid the (potentially big) string allocation.

Fixes #14526
Closes #14527
2016-04-28 20:26:03 +03:00
Weijing Jay Lin 8337b9b2d4 docs(identity): add example
Closes #14528
2016-04-28 11:07:30 +03:00
Perry Hooker 85a53ea9cd fix($compile): properly handle setting srcset to undefined
Previously, calling `Attributes#$set('srcset', value)` on an `<img>` element would throw if `value`
were undefined, as it assumed `value` is always a string.
This commit fixes the issue, by skipping the unnecessary string manipulation when `value` is not
defined.

Closes #14470

Closes #14493
2016-04-26 09:20:54 +03:00
andrea 2d87ef8f23 docs(guide/index): fixed formatting with subtitles
The "Books" and "Videos" subtitles had no space between text and the '#' so it didn't render as a
subtitle.

Closes #14514
2016-04-26 08:34:11 +03:00
Tim van Dalen 3df8b637f3 docs($anchorScroll): fix link to HTML5 spec
Closes #14364
2016-04-26 00:31:52 +03:00
Georgios Kalpakas 28d3126fe5 test($templateRequest): fix code for jasmine 1.3 2016-04-25 22:14:41 +03:00
Raphael Jamet ad21f8feaf fix($templateRequest): trust empty templates in $templateCache as well
Implicitly trust empty templates added to `$templateCache` as is the case for all other templates.

Fixes #14479

Closes #14496
2016-04-25 20:22:54 +03:00
Georgios Kalpakas 7f2df141cd docs($interpolate): add known issue about end-symbol in expression
This has been discussed in #8642.

Closes #14494
2016-04-22 15:42:51 +03:00
Georgios Kalpakas 4e735e5363 fix(filters): always call splice() with 2 arguments or more
When calling `.splice()` without a 2nd argument (`deleteCount`), most browsers will splice to the
end of the array. As it turns out, this is the behavior specified in [ES2015][es6]. In [ES5][es5],
the spec seems to imply that nothing should happen.

To avoid inconsistent behavior among browsers implementing different versions of the EcmaScript
standart or when ES5 shims are included (e.g. https://github.com/es-shims/es5-shim/), this commit
ensures that all calls to `.splice()` provide at least two arguments.

[es5]: http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.12
[es6]: http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.splice

Fixes #14467

Closes #14489
2016-04-22 12:42:01 +03:00
cloverharvest a9db6073c9 docs($http): fix a typo (his --> this)
Closes #14430
2016-04-15 01:25:40 +03:00
Martin Staffa e48e27aa2b test(ngAnimate): fix jasmine syntax 2016-04-13 04:00:26 -07:00
Jason Bedard 832b383cbc perf($compile): use createMap() for directive bindings to allow fast forEach
Closes #12529
2016-04-13 12:06:55 +03:00
Martin Staffa c4bff290e5 fix(ngAnimate): fire callbacks when document is hidden
Since commit a3a7afd3aa, animations are not run
when the document is hidden (only their structural or class change effects are executed).
However, some libraries rely on the $animate.on() callbacks to be called even when no actual animation
runs.
This commit restores the behavior for the ngAnimate.$animate functions.
Note that callbacks still won't be called if animations are disabled, because this would be be a potential
breaking change, as some applications might rely on this implementation.

Fixes #14120
2016-04-12 20:42:30 +02:00
Martin Staffa 55b00148dd test(ngAnimate): test calling callbacks for various constellations 2016-04-12 20:42:30 +02:00
Martin Staffa 23550b5e27 fix(ngAnimate): fire callbacks in the correct order for certain skipped animations 2016-04-12 20:42:29 +02:00
Georgii Dolzhykov 97d2a08c5a docs(Module): fix parameter names for .decorator()
Closes #14413
2016-04-12 14:52:48 +03:00
aortyl 7f6efb2822 docs(guide/scope): add comma for readability
Closes #14411
2016-04-11 22:23:03 +03:00
David Rodenas Pico b50867001b fix(ngClass): fix watching of an array expression containing an object
Closes #14405
2016-04-11 12:41:29 +03:00
Gene McCulley 8b9ce885d3 docs(numberFilter): fix the description of the returned value
Closes #14408
2016-04-11 12:28:56 +03:00
Jurko Gospodnetić 56dae6fa1b fix(ngMock): fix collecting stack trace in inject() on IE10+, PhantomJS
Add support for collecting current stack trace information in browsers
(e.g. IE10+, PhantomJS) that do not automatically store the current stack trace
information in a newly created `Error` object's `stack` property, but
only add it there once the `Error` gets thrown.

The original implementation works fine in Firefox & Chrome, but fails on IE10+
and PhantomJS where it, for example, breaks Karma's error reporting in cases
when an exception is thrown in a test like the following:

```
it('the holy crusade', inject(function() {
  var x = {};
  x.holyGrail();
}));
```

In this case, the ngMock `inject()` implementation would incorrectly add the
word `undefined` at the end of the collected error stack trace information,
thus causing the main error description to be reported back to Karma as
`undefined`.

The added test makes sure this functionality:

- works as expected in browsers supporting JavaScript stack trace
  collection, e.g. Chrome, Firefox, IE10+, Opera & PhantomJS
- does not add any bogus stack track information in browsers that do
  not support JavaScript stack trace collection, e.g. IE9

Fixes #13591
Closes #13592

Closes #13593
2016-04-09 20:56:16 +03:00
cscport 611dcbc035 docs(angular.bootstrap): fix capitalization in error message
Closes #14325
2016-03-27 15:45:11 +03:00
Martin Staffa 2c9066e012 test(ngMessages): fix most recent call syntax 2016-03-24 11:10:32 +01:00
Martin Staffa 8ecc9357ef test(ngMessages): fix callThrough syntax 2016-03-24 01:08:28 +01:00
Martin Staffa 71dca7c4c2 fix(ngMessages): don't crash when nested messages are removed
Under specific circumstances, ngMessages would go into an infinite loop and crash the
browser / page:
- At least two ngMessage elements are wrapped inside another element (e.g. ngTransclude)
- The first message is currently visible
- The first message is removed (e.g. when the whole ngMessages element is removed by an ngIf)

When a message is removed, it looks for a previous message - in this specific case it would misidentify
the second message for a previous message, which would then cause the first message to be marked as the
second message's next message, resulting in an infinite loop, and crash.

This fix ensures that when searching for previous messages, ngMessage walks the DOM in a way so
that messages that come after the current message are never identified as previous messages.

This commit also detaches and destroys all child ngMessage elements when the ngMessages element is
destroyed, which should improve performance slightly.

Fixes #14183
Closes #14242
2016-03-24 00:15:35 +01:00
Steve Mao ce77c25b06 docs($q): mention ES2015 (as a "synonym" for ES6) and remove "harmony"
Closes #14294
2016-03-22 12:09:31 +02:00
Owen Craig a1188721e1 fix(formatNumber): handle small numbers correctly when gSize !== lgSize
By using `>=` when comparing the number length to `lgSize`, we'll provide the correct value, when
formatting numbers with different `lgSize` than `gSize`.

Fixes #14289

Closes #14290
2016-03-22 00:12:18 +02:00
surya prakash singh 1917ff86c4 docs(input[time]): fix a typo in the example
Closes #14220
2016-03-21 01:26:54 +02:00
Georgios Kalpakas c60e1960c6 docs(CHANGELOG.md): add notes for v1.4.10 2016-03-20 22:49:46 +02:00
Peter Bacon Darwin 268e71eb8b chore(travis): update node and browser versions 2016-03-16 14:37:16 +00:00
Peter Bacon Darwin 03e6fb3df5 chore(jenkins): fix node version chooser in build scripts
The `set-node-version.sh` script was being run in its own shell and so
was not actually changing the current version of node.
2016-03-16 11:41:30 +00:00
Peter Bacon Darwin 4fc9cf6289 chore(jenkins): update node version to 4.4 2016-03-16 11:41:27 +00:00
41 changed files with 1367 additions and 75 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
language: node_js
sudo: false
node_js:
- '4.2'
- '4.4'
cache:
directories:
+176
View File
@@ -1,3 +1,141 @@
<a name="1.4.11"></a>
# 1.4.11 relentless-syncomania (2016-05-27)
## Bug Fixes
- **$compile:** properly handle setting `srcset` to undefined
([85a53ea9](https://github.com/angular/angular.js/commit/85a53ea9cd08476496ef680d20f64ae6a7ae7499),
[#14470](https://github.com/angular/angular.js/issues/14470), [#14493](https://github.com/angular/angular.js/issues/14493))
- **$templateRequest:** trust empty templates in `$templateCache` as well
([ad21f8fe](https://github.com/angular/angular.js/commit/ad21f8feaf8659158e8f2092c90eb42c4f13c141),
[#14479](https://github.com/angular/angular.js/issues/14479), [#14496](https://github.com/angular/angular.js/issues/14496))
- **filters:** always call `splice()` with 2 arguments or more
([4e735e53](https://github.com/angular/angular.js/commit/4e735e53638a1c831f5c0455cd123a4127a11505),
[#14467](https://github.com/angular/angular.js/issues/14467), [#14489](https://github.com/angular/angular.js/issues/14489))
- **formatNumber:** handle small numbers correctly when `gSize` !== `lgSize`
([a1188721](https://github.com/angular/angular.js/commit/a1188721e1ac24e1c34e61b4f622f397416ccc7c),
[#14289](https://github.com/angular/angular.js/issues/14289), [#14290](https://github.com/angular/angular.js/issues/14290))
- **ng-bind-html:** watch the unwrapped value using `$sce.valueOf()` (instead of `toString()`)
([f31586db](https://github.com/angular/angular.js/commit/f31586db4115baabaa854c095278f367850a11c5),
[#14526](https://github.com/angular/angular.js/issues/14526), [#14527](https://github.com/angular/angular.js/issues/14527))
- **ngAnimate:**
- safe-guard against missing document
([0d764b58](https://github.com/angular/angular.js/commit/0d764b581d8b494190fa280f8870eee8fd039933))
- properly handle empty jqLite collections
([fdaf4d5e](https://github.com/angular/angular.js/commit/fdaf4d5e27a2ddebf2999db4d35bef7915d32c86),
[#14558](https://github.com/angular/angular.js/issues/14558), [#14559](https://github.com/angular/angular.js/issues/14559))
- fire callbacks when document is hidden
([c4bff290](https://github.com/angular/angular.js/commit/c4bff290e5009e8ae5aa93839ad80a677a9e878f),
[#14120](https://github.com/angular/angular.js/issues/14120))
- fire callbacks in the correct order for certain skipped animations
([23550b5e](https://github.com/angular/angular.js/commit/23550b5e278b4fb3cf1300f1399d8ad6d6771725))
- **ngClass:** fix watching of an array expression containing an object
([b5086700](https://github.com/angular/angular.js/commit/b50867001bf71998a2f7e381095cb9001166e806),
[#14405](https://github.com/angular/angular.js/issues/14405))
- **ngMessages:** don't crash when nested messages are removed
([71dca7c4](https://github.com/angular/angular.js/commit/71dca7c4c23df95c6c0e5974f0758a3ca34ce819),
[#14183](https://github.com/angular/angular.js/issues/14183), [#14242](https://github.com/angular/angular.js/issues/14242))
- **ngMock:** fix collecting stack trace in `inject()` on IE10+, PhantomJS
([56dae6fa](https://github.com/angular/angular.js/commit/56dae6fa1bee88eaffae766a6cdcb8b6c4b89958),
[#13591](https://github.com/angular/angular.js/issues/13591), [#13592](https://github.com/angular/angular.js/issues/13592), [#13593](https://github.com/angular/angular.js/issues/13593))
## Performance Improvements
- **$compile:** use createMap() for directive bindings to allow fast forEach
([832b383c](https://github.com/angular/angular.js/commit/832b383cbc63c157e62df523d574a75171a8c11c),
[#12529](https://github.com/angular/angular.js/issues/12529))
<a name="1.4.10"></a>
# 1.4.10 benignant-oscillation (2016-03-16)
## Bug Fixes
- **core:** only call `console.log` when `window.console` exists
([beb00e44](https://github.com/angular/angular.js/commit/beb00e44de947981dbe35d5cf7a116e10ea8dc67),
[#14006](https://github.com/angular/angular.js/issues/14006), [#14007](https://github.com/angular/angular.js/issues/14007), [#14047](https://github.com/angular/angular.js/issues/14047))
- **$animateCss:** cancel fallback timeout when animation ends normally
([a60bbc12](https://github.com/angular/angular.js/commit/a60bbc12e8c5170e70d95f1b2c3e309b3b95cb84),
[#13787](https://github.com/angular/angular.js/issues/13787))
- **$compile:**
- allow directives to have decorators
([77cdc37c](https://github.com/angular/angular.js/commit/77cdc37c65491b551fcf01a18ab848a693c293d7))
- properly denormalize templates when only one of the start/end symbols is different
([2d44a681](https://github.com/angular/angular.js/commit/2d44a681eb912a81a8bc8e16a278c45dae91fa24),
[#13848](https://github.com/angular/angular.js/issues/13848))
- handle boolean attributes in `@` bindings
([2ffbfb0a](https://github.com/angular/angular.js/commit/2ffbfb0ad0647d103ff339ee4b772b62d4823bf3),
[#13767](https://github.com/angular/angular.js/issues/13767), [#13769](https://github.com/angular/angular.js/issues/13769))
- **$parse:**
- prevent assignment on constructor properties
([f47e2180](https://github.com/angular/angular.js/commit/f47e218006029f39b4785d820b430de3a0eebcb0),
[#13417](https://github.com/angular/angular.js/issues/13417))
- preserve expensive checks when runnning `$eval` inside an expression
([96d62cc0](https://github.com/angular/angular.js/commit/96d62cc0fc77248d7e3ec4aa458bac0d3e072629))
- copy `inputs` for expressions with expensive checks
([0b7fff30](https://github.com/angular/angular.js/commit/0b7fff303f46202bbae1ff3ca9d0e5fa76e0fc9a))
- **$rootScope:** set no context when calling helper functions for `$watch`
([ab5c7698](https://github.com/angular/angular.js/commit/ab5c7698bb106669ca31b5f79a95afa54d65c5f1))
- **$route:** allow preventing a route reload
([4bc30314](https://github.com/angular/angular.js/commit/4bc3031497447ad527356f12bd0ceee1d7d09db5),
[#9824](https://github.com/angular/angular.js/issues/9824), [#13894](https://github.com/angular/angular.js/issues/13894))
- **$routeProvider:** properly handle optional eager path named groups
([6a4403a1](https://github.com/angular/angular.js/commit/6a4403a11845173d6a96232f77d73aa544b182af),
[#14011](https://github.com/angular/angular.js/issues/14011))
- **copy:** add support for copying `Blob` objects
([863a4232](https://github.com/angular/angular.js/commit/863a4232a6faa92428df45cd54d5a519be2434de),
[#9669](https://github.com/angular/angular.js/issues/9669), [#14064](https://github.com/angular/angular.js/issues/14064))
- **dateFilter:** follow the CLDR on pattern escape sequences
([f476060d](https://github.com/angular/angular.js/commit/f476060de6cc016380c0343490a184543f853652),
[#12839](https://github.com/angular/angular.js/issues/12839))
- **dateFilter, input:** fix Date parsing in IE/Edge when timezone offset contains `:`
([571afd65](https://github.com/angular/angular.js/commit/571afd6558786d7b99e2aebd307b4a94c9f2bb87),
[#13880](https://github.com/angular/angular.js/issues/13880), [#13887](https://github.com/angular/angular.js/issues/13887))
- **input:** re-validate when partially editing date-family inputs
([02929f82](https://github.com/angular/angular.js/commit/02929f82f30449301ff18fea84a6396a017683b1),
[#12207](https://github.com/angular/angular.js/issues/12207), [#13886](https://github.com/angular/angular.js/issues/13886))
- **select:** handle corner case of adding options via a custom directive
([df6e7315](https://github.com/angular/angular.js/commit/df6e731506831a3dc7f44c9a90abe17515450b3e),
[#13874](https://github.com/angular/angular.js/issues/13874), [#13878](https://github.com/angular/angular.js/issues/13878))
- **ngOptions:** always set the 'selected' attribute for selected options
([f87e8288](https://github.com/angular/angular.js/commit/f87e8288fb69526fd240a66a046f5de52ed204de),
[#14115](https://github.com/angular/angular.js/issues/14115))
- **ngAnimate:** properly cancel previously running class-based animations
([3b27dd37](https://github.com/angular/angular.js/commit/3b27dd37a2cc8a52992784ece6b371023dadf792),
[#10156](https://github.com/angular/angular.js/issues/10156), [#13822](https://github.com/angular/angular.js/issues/13822))
- **ngAnimateChildren:** make it compatible with `ngIf`
([dc158e7e](https://github.com/angular/angular.js/commit/dc158e7e40624ef94c66560386522ef7e991a9ce),
[#13865](https://github.com/angular/angular.js/issues/13865), [#13876](https://github.com/angular/angular.js/issues/13876))
- **ngMockE2E:** pass `responseType` to `$delegate` when using `passThrough`
([947cb4d1](https://github.com/angular/angular.js/commit/947cb4d1451afa4f5090a693df5b1968dd0df70c),
[#5415](https://github.com/angular/angular.js/issues/5415), [#5783](https://github.com/angular/angular.js/issues/5783))
## Features
- **$locale:** Include original locale ID in $locale
([e69f3550](https://github.com/angular/angular.js/commit/e69f35507e10c994708ce4f1efba7573951d1acd),
[#13390](https://github.com/angular/angular.js/issues/13390))
- **ngAnimate:** provide ng-[event]-prepare class for structural animations
([796f7ab4](https://github.com/angular/angular.js/commit/796f7ab41487e124b5b0c02dbf0a03bd581bf073))
## Performance Improvements
- **$compile:** avoid needless overhead when wrapping text nodes
([946d9ae9](https://github.com/angular/angular.js/commit/946d9ae90bb31fe911ebbe1b80cd4c8af5a665c6))
- **ngRepeat:** avoid duplicate jqLite wrappers
([d04c38c4](https://github.com/angular/angular.js/commit/d04c38c48968db777c3ea6a177ce2ff0116df7b4))
- **ngAnimate:**
- avoid jqLite/jQuery for upward DOM traversal
([ab95ba65](https://github.com/angular/angular.js/commit/ab95ba65c08b38cace83de6717b7681079182b45))
- avoid `$.fn.data` overhead with jQuery
([86416bcb](https://github.com/angular/angular.js/commit/86416bcbee2192fa31c017163c5d856763182ade))
<a name="1.4.9"></a>
# 1.4.9 implicit-superannuation (2016-01-21)
@@ -818,6 +956,44 @@ describe('$q.when', function() {
[#11580](https://github.com/angular/angular.js/issues/11580), [#12234](https://github.com/angular/angular.js/issues/12234))
## Breaking Changes
- **ngInclude:** due to [3c6e8ce044446735eb2e70d0061db8c6db050289](https://github.com/angular/angular.js/commit/3c6e8ce044446735eb2e70d0061db8c6db050289), the `src` attribute of ngInclude no longer accepts an
expression that returns the result of `$sce.trustAsResourceUrl`. This will now cause an infinite digest:
Before:
```html
<div ng-include="findTemplate('https://example.com/myTemplate.html')"></div>
```
```js
$scope.findTemplate = function(templateName) {
return $sce.trustAsResourceUrl(templateName);
};
```
To migrate, either cache the result of `trustAsResourceUrl()`, or put the template url in the resource
whitelist in the `config()` function:
After:
```js
var templateCache = {};
$scope.findTemplate = function(templateName) {
if (!templateCache[templateName]) {
templateCache[templateName] = $sce.trustAsResourceUrl(templateName);
}
return templateCache[templateName];
};
// Alternatively, use `$sceDelegateProvider.resourceUrlWhitelist()`:
angular.module('myApp', []).config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/**'])
});
```
<a name="1.3.17"></a>
# 1.3.17 tsktskskly-euouae (2015-07-06)
+5 -7
View File
@@ -11,8 +11,7 @@ angular.module('tutorials', [])
scope: {},
template:
'<a ng-href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-step-backward"></i> Previous</li></a>\n' +
'<a ng-href="http://angular.github.io/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="glyphicon glyphicon-play"></i> Live Demo</li></a>\n' +
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
'<a ng-href="https://github.com/angular/angular-phonecat/compare/1.4-step-{{diffLo}}...1.4-step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
'<a ng-href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="glyphicon glyphicon-step-forward"></i></li></a>',
link: function(scope, element, attrs) {
var seq = 1 * attrs.docTutorialNav;
@@ -38,12 +37,11 @@ angular.module('tutorials', [])
'<p><button class="btn" ng-click="show=!show">Workspace Reset Instructions ➤</button></p>\n' +
'<div class="alert alert-info" ng-show="show">\n' +
' <p>Reset the workspace to step {{step}}.</p>' +
' <p><pre>git checkout -f step-{{step}}</pre></p>\n' +
' <p>Refresh your browser or check out this step online: '+
'<a href="http://angular.github.io/angular-phonecat/step-{{step}}/app">Step {{step}} Live Demo</a>.</p>\n' +
' <p><pre>git checkout -f 1.4-step-{{step}}</pre></p>\n' +
' <p>Refresh your browser to see the changes.</p>\n' +
'</div>\n' +
'<p>The most important changes are listed below. You can see the full diff on ' +
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{step ? (step - 1): \'0~1\'}}...step-{{step}}" title="See diff on Github">GitHub</a>\n' +
'<a ng-href="https://github.com/angular/angular-phonecat/compare/1.4-step-{{step ? (step - 1) : \'0~1\'}}...1.4-step-{{step}}" title="See diff on Github">GitHub</a>\n' +
'</p>'
};
});
});
+1 -1
View File
@@ -350,7 +350,7 @@ base and the path that should be handled by the application.
### Base href constraints
The `$location` service is not able to function properly if the current URL is outside the URL given
as the base href. This can have subtle confusing consequencies...
as the base href. This can have subtle confusing consequences...
Consider a base href set as follows: `<base href="/base/">` (i.e. the application exists in the "folder"
called `/base`). The URL `/base` is actually outside the application (it refers to the `base` file found
+486
View File
@@ -0,0 +1,486 @@
@ngdoc overview
@name Decorators
@sortOrder 345
@description
# Decorators in AngularJS
<div class="alert alert-warning">
**NOTE:** This guide is targeted towards developers who are already familiar with AngularJS basics.
If you're just getting started, we recommend the {@link tutorial/ tutorial} first.
</div>
## What are decorators?
Decorators are a design pattern that is used to separate modification or *decoration* of a class without modifying the
original source code. In Angular, decorators are functions that allow a service, directive or filter to be modified
prior to its usage.
## How to use decorators
There are two ways to register decorators
- `$provide.decorator`, and
- `module.decorator`
Each provide access to a `$delegate`, which is the instantiated service/directive/filter, prior to being passed to the
service that required it.
### $provide.decorator
The {@link api/auto/service/$provide#decorator decorator function} allows access to a $delegate of the service once it
has been instantiated. For example:
```js
angular.module('myApp', [])
.config([ '$provide', function($provide) {
$provide.decorator('$log', [
'$delegate',
function $logDecorator($delegate) {
var originalWarn = $delegate.warn;
$delegate.warn = function decoratedWarn(msg) {
msg = 'Decorated Warn: ' + msg;
originalWarn.apply($delegate, arguments);
};
return $delegate;
}
]);
}]);
```
After the `$log` service has been instantiated the decorator is fired. The decorator function has a `$delegate` object
injected to provide access to the service that matches the selector in the decorator. This `$delegate` will be the
service you are decorating. The return value of the function *provided to the decorator* will take place of the service,
directive, or filter being decorated.
<hr>
The `$delegate` may be either modified or completely replaced. Given a service `myService` with a method `someFn`, the
following could all be viable solutions:
#### Completely Replace the $delegate
```js
angular.module('myApp', [])
.config([ '$provide', function($provide) {
$provide.decorator('myService', [
'$delegate',
function myServiceDecorator($delegate) {
var myDecoratedService = {
// new service object to replace myService
};
return myDecoratedService;
}
]);
}]);
```
#### Patch the $delegate
```js
angular.module('myApp', [])
.config([ '$provide', function($provide) {
$provide.decorator('myService', [
'$delegate',
function myServiceDecorator($delegate) {
var someFn = $delegate.someFn;
function aNewFn() {
// new service function
someFn.apply($delegate, arguments);
}
$delegate.someFn = aNewFn;
return $delegate;
}
]);
}]);
```
#### Augment the $delegate
```js
angular.module('myApp', [])
.config([ '$provide', function($provide) {
$provide.decorator('myService', [
'$delegate',
function myServiceDecorator($delegate) {
function helperFn() {
// an additional fn to add to the service
}
$delegate.aHelpfulAddition = helperFn;
return $delegate;
}
]);
}]);
```
<div class="alert alert-info">
Note that whatever is returned by the decorator function will replace that which is being decorated. For example, a
missing return statement will wipe out the entire object being decorated.
</div>
<hr>
Decorators have different rules for different services. This is because services are registered in different ways.
Services are selected by name, however filters and directives are selected by appending `"Filter"` or `"Directive"` to
the end of the name. The `$delegate` provided is dictated by the type of service.
| Service Type | Selector | $delegate |
|--------------|-------------------------------|-----------------------------------------------------------------------|
| Service | `serviceName` | The `object` or `function` returned by the service |
| Directive | `directiveName + 'Directive'` | An `Array.<DirectiveObject>`<sub>{@link guide/decorators#drtvArray 1}</sub> |
| Filter | `filterName + 'Filter'` | The `function` returned by the filter |
<small id="drtvArray">1. Multiple directives may be registered to the same selector/name</small>
<div class="alert alert-warning">
**NOTE:** Developers should take care in how and why they are modifying the `$delegate` for the service. Not only
should expectations for the consumer be kept, but some functionality (such as directive registration) does not take
place after decoration, but during creation/registration of the original service. This means, for example, that
an action such as pushing a directive object to a directive `$delegate` will likely result in unexpected behavior.
Furthermore, great care should be taken when decorating core services, directives, or filters as this may unexpectedly
or adversely affect the functionality of the framework.
</div>
### module.decorator
This {@link api/ng/type/angular.Module#decorator function} is the same as the `$provide.decorator` function except it is
exposed through the module API. This allows you to separate your decorator patterns from your module config blocks. The
main caveat here is that you will need to take note the order in which you create your decorators.
Unlike in the module config block (which allows configuration of services prior to their creation), the service must be
registered prior to the decorator (see {@link guide/providers#provider-recipe Provider Recipe}). For example, the
following would not work because you are attempting to decorate outside of the configuration phase and the service
hasn't been created yet:
```js
// will cause an error since 'someService' hasn't been registered
angular.module('myApp').decorator('someService', ...);
angular.module('myApp').factory('someService', ...);
```
## Example Applications
The following sections provide examples each of a service decorator, a directive decorator, and a filter decorator.
### Service Decorator Example
This example shows how we can replace the $log service with our own to display log messages.
<example module="myServiceDecorator" name="service-decorator">
<file name="script.js">
angular.module('myServiceDecorator', []).
controller('Ctrl', [
'$scope',
'$log',
'$timeout',
function($scope, $log, $timeout) {
var types = ['error', 'warn', 'log', 'info' ,'debug'], i;
for (i = 0; i < types.length; i++) {
$log[types[i]](types[i] + ': message ' + (i + 1));
}
$timeout(function() {
$log.info('info: message logged in timeout');
});
}
]).
directive('myLog', [
'$log',
function($log) {
return {
restrict: 'E',
template: '<ul id="myLog"><li ng-repeat="l in myLog" class="{{l.type}}">{{l.message}}</li></ul>',
scope: {},
compile: function() {
return function(scope) {
scope.myLog = $log.stack;
};
}
};
}
]).
config([
'$provide',
function($provide) {
$provide.decorator('$log', [
'$delegate',
function logDecorator($delegate) {
var myLog = {
warn: function(msg) {
log(msg, 'warn');
},
error: function(msg) {
log(msg, 'error');
},
info: function(msg) {
log(msg, 'info');
},
debug: function(msg) {
log(msg, 'debug');
},
log: function(msg) {
log(msg, 'log');
},
stack: []
};
function log(msg, type) {
myLog.stack.push({ type: type, message: msg.toString() });
if (console && console[type]) console[type](msg);
}
return myLog;
}
]);
}
]);
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<h1>Logs</h1>
<my-log></my-log>
</div>
</file>
<file name="style.css">
li.warn { color: yellow; }
li.error { color: red; }
li.info { color: blue }
li.log { color: black }
li.debug { color: green }
</file>
<file name="protractor.js" type="protractor">
it('should display log messages in dom', function() {
element.all(by.repeater('l in myLog')).count().then(function(count) {
expect(count).toEqual(6);
});
});
</file>
</example>
### Directive Decorator Example
Failed interpolated expressions in `ng-href` attributes can easily go unnoticed. We can decorate `ngHref` to warn us of
those conditions.
<example module="urlDecorator" name="directive-decorator">
<file name="script.js">
angular.module('urlDecorator', []).
controller('Ctrl', ['$scope', function ($scope) {
$scope.id = 3;
$scope.warnCount = 0; // for testing
}]).
config(['$provide', function($provide) {
// matchExpressions looks for interpolation markup in the directive attribute, extracts the expressions
// from that markup (if they exist) and returns an array of those expressions
function matchExpressions(str) {
var exps = str.match(/{{([^}]+)}}/g);
// if there isn't any, get out of here
if (exps === null) return;
exps = exps.map(function(exp) {
var prop = exp.match(/[^{}]+/);
return prop === null ? null : prop[0];
});
return exps;
}
// remember: directives must be selected by appending 'Directive' to the directive selector
$provide.decorator('ngHrefDirective', [
'$delegate',
'$log',
'$parse',
function($delegate, $log, $parse) {
// store the original link fn
var originalLinkFn = $delegate[0].link;
// replace the compile fn
$delegate[0].compile = function(tElem, tAttr) {
// store the original exp in the directive attribute for our warning message
var originalExp = tAttr.ngHref;
// get the interpolated expressions
var exps = matchExpressions(originalExp);
// create and store the getters using $parse
var getters = exps.map(function(el) {
if (el) return $parse(el);
});
return function newLinkFn(scope, elem, attr) {
// fire the originalLinkFn
originalLinkFn.apply($delegate[0], arguments);
// observe the directive attr and check the expressions
attr.$observe('ngHref', function(val) {
// if we have getters and getters is an array...
if (getters && angular.isArray(getters)) {
// loop through the getters and process them
angular.forEach(getters, function(g, idx) {
// if val is truthy, then the warning won't log
var val = angular.isFunction(g) ? g(scope) : true;
if (!val) {
$log.warn('NgHref Warning: "' + exps[idx] + '" in the expression "' + originalExp +
'" is falsy!');
scope.warnCount++; // for testing
}
});
}
});
};
};
// get rid of the old link function since we return a link function in compile
delete $delegate[0].link;
// return the $delegate
return $delegate;
}
]);
}]);
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<a ng-href="/products/{{ id }}/view" id="id3">View Product {{ id }}</a>
- <strong>id == 3</strong>, so no warning<br>
<a ng-href="/products/{{ id + 5 }}/view" id="id8">View Product {{ id + 5 }}</a>
- <strong>id + 5 == 8</strong>, so no warning<br>
<a ng-href="/products/{{ someOtherId }}/view" id="someOtherId">View Product {{ someOtherId }}</a>
- <strong style="background-color: #ffff00;">someOtherId == undefined</strong>, so warn<br>
<a ng-href="/products/{{ someOtherId + 5 }}/view" id="someOtherId5">View Product {{ someOtherId + 5 }}</a>
- <strong>someOtherId + 5 == 5</strong>, so no warning<br>
<div>Warn Count: {{ warnCount }}</div>
</div>
</file>
<file name="protractor.js" type="protractor">
it('should warn when an expression in the interpolated value is falsy', function() {
var id3 = element(by.id('id3'));
var id8 = element(by.id('id8'));
var someOther = element(by.id('someOtherId'));
var someOther5 = element(by.id('someOtherId5'));
expect(id3.getText()).toEqual('View Product 3');
expect(id3.getAttribute('href')).toContain('/products/3/view');
expect(id8.getText()).toEqual('View Product 8');
expect(id8.getAttribute('href')).toContain('/products/8/view');
expect(someOther.getText()).toEqual('View Product');
expect(someOther.getAttribute('href')).toContain('/products//view');
expect(someOther5.getText()).toEqual('View Product 5');
expect(someOther5.getAttribute('href')).toContain('/products/5/view');
expect(element(by.binding('warnCount')).getText()).toEqual('Warn Count: 1');
});
</file>
</example>
### Filter Decorator Example
Let's say we have created an app that uses the default format for many of our `Date` filters. Suddenly requirements have
changed (that never happens) and we need all of our default dates to be `'shortDate'` instead of `'mediumDate'`.
<example module="filterDecorator" name="filter-decorator">
<file name="script.js">
angular.module('filterDecorator', []).
controller('Ctrl', ['$scope', function ($scope) {
$scope.genesis = new Date(2010, 0, 5);
$scope.ngConf = new Date(2016, 4, 4);
}]).
config(['$provide', function($provide) {
$provide.decorator('dateFilter', [
'$delegate',
function dateDecorator($delegate) {
// store the original filter
var originalFilter = $delegate;
// return our filter
return shortDateDefault;
// shortDateDefault sets the format to shortDate if it is falsy
function shortDateDefault(date, format, timezone) {
if (!format) format = 'shortDate';
// return the result of the original filter
return originalFilter(date, format, timezone);
}
}
]);
}]);
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<div id="genesis">Initial Commit default to short date: {{ genesis | date }}</div>
<div>ng-conf 2016 default short date: {{ ngConf | date }}</div>
<div id="ngConf">ng-conf 2016 with full date format: {{ ngConf | date:'fullDate' }}</div>
</div>
</file>
<file name="protractor.js" type="protractor">
it('should default date filter to short date format', function() {
expect(element(by.id('genesis')).getText())
.toMatch(/Initial Commit default to short date: \d{1,2}\/\d{1,2}\/\d{2}/);
});
it('should still allow dates to be formatted', function() {
expect(element(by.id('ngConf')).getText())
.toMatch(/ng-conf 2016 with full date format\: [A-Za-z]+, [A-Za-z]+ \d{1,2}, \d{4}/);
});
</file>
</example>
+2 -2
View File
@@ -101,7 +101,7 @@ This is a short list of libraries with specific support and documentation for wo
## Learning Resources
###Books
### Books
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
@@ -113,7 +113,7 @@ This is a short list of libraries with specific support and documentation for wo
* [Responsive Web Design with AngularJS](http://www.amazon.com/Responsive-Design-AngularJS-Sandeep-Kumar/dp/178439842X) by Sandeep Kumar Patel
* [Professional AngularJS](http://www.amazon.com/Professional-AngularJS-Valeri-Karpov/dp/1118832078/)
###Videos:
### Videos:
* [egghead.io](http://egghead.io/)
* [Angular on YouTube](http://youtube.com/angularjs)
* [Firebase Foundations for AngularJS](http://blog.watchandcode.com/firebase-foundations/)
+40 -7
View File
@@ -284,7 +284,7 @@ angular.module('myApp').directive('form', function() {
});
```
## Templating (`ngRepeat`, `$compile`)
## Templating (`ngRepeat`, `$compile`, `ngInclude`)
### ngRepeat
@@ -316,6 +316,45 @@ returning an object from a controller constructor function will now override the
controllerAs method will no longer get the this reference, but the returned object.
### ngInclude:
Due to [3c6e8ce044446735eb2e70d0061db8c6db050289](https://github.com/angular/angular.js/commit/3c6e8ce044446735eb2e70d0061db8c6db050289), the `src` attribute of ngInclude no longer accepts an
expression that returns the result of `$sce.trustAsResourceUrl`. This will now cause an infinite digest:
Before:
```html
<div ng-include="findTemplate('https://example.com/templates/myTemplate.html')"></div>
```
```js
$scope.findTemplate = function(templateName) {
return $sce.trustAsResourceUrl(templateName);
};
```
To migrate, either cache the result of `trustAsResourceUrl()`, or put the template url in the resource
whitelist in the `config()` function:
After:
```js
var templateCache = {};
$scope.findTemplate = function(templateName) {
if (!templateCache[templateName]) {
templateCache[templateName] = $sce.trustAsResourceUrl(templateName);
}
return templateCache[templateName];
};
// Alternatively, use `$sceDelegateProvider.resourceUrlWhitelist()`, which means you don't
// have to use `$sce.trustAsResourceUrl()` at all:
angular.module('myApp', []).config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/templates/**'])
});
```
## Cookies (`ngCookies`)
Due to [38fbe3ee](https://github.com/angular/angular.js/commit/38fbe3ee8370fc449b82d80df07b5c2ed2cd5fbe),
@@ -353,8 +392,6 @@ has been moved to `$cookies`, to which `$cookieStore` now simply
delegates calls.
## Server Requests (`$http`)
Due to [5da1256](https://github.com/angular/angular.js/commit/5da1256fc2812d5b28fb0af0de81256054856369),
@@ -386,8 +423,6 @@ $http.get(url, {
```
## Filters (`filter`, `limitTo`)
### `filter` filter
@@ -405,8 +440,6 @@ Now, instead of returning empty object/array, it returns unchanged input.
# Migrating from 1.2 to 1.3
## Controllers
+1 -1
View File
@@ -257,7 +257,7 @@ the `$digest` phase. This delay is desirable, since it coalesces multiple model
2. **Watcher registration**
During template linking directives register {@link
During template linking, directives register {@link
ng.$rootScope.Scope#$watch watches} on the scope. These watches will be
used to propagate model values to the DOM.
+22 -1
View File
@@ -5,6 +5,22 @@
# PhoneCat Tutorial App
<div class="alert alert-danger">
<p>
This version of the tutorial is deprecated and no longer maintained. You can find the most
recent version of the tutorial at https://docs.angularjs.org/tutorial/.
</p>
<p>
It is recommended to use the latest tutorial version for the following reasons:
<ul>
<li>It showcases features introduced in AngularJS v1.5+</li>
<li>It follows modern best practices in terms of architecture and code organization</li>
<li>It has more up-to-date dependencies and tools</li>
<li>It is actively maintained</li>
</ul>
</p>
</div>
A great way to get introduced to AngularJS is to work through this tutorial, which walks you through
the construction of an AngularJS web app. The app you will build is a catalog that displays a list
of Android devices, lets you filter the list to see only devices that interest you, and then view
@@ -73,7 +89,7 @@ Clone the [angular-phonecat repository][angular-phonecat] located at GitHub by r
command:
```
git clone --depth=14 https://github.com/angular/angular-phonecat.git
git clone --depth=14 --branch=1.4-snapshot https://github.com/angular/angular-phonecat.git
```
This command creates the `angular-phonecat` directory in your current directory.
@@ -82,6 +98,11 @@ This command creates the `angular-phonecat` directory in your current directory.
download much smaller and faster.
</div>
<div class="alert alert-info">
The `--branch=1.4-snapshot` option tells Git to pull down the `1.4-snapshot` branch.
The code for this older version of the tutorial is on that branch.
</div>
Change your current directory to `angular-phonecat`.
```
+1 -1
View File
@@ -16,7 +16,7 @@ dependencies, as described in {@link index#get-started Get Started}.
In the `angular-phonecat` directory, run this command:
```
git checkout -f step-0
git checkout -f 1.4-step-0
```
+6 -6
View File
@@ -9,7 +9,7 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
capabilitiesForBrowserStack({
browserName: 'chrome',
platform: 'MAC',
version: '34'
version: '49'
}),
capabilitiesForBrowserStack({
browserName: 'firefox',
@@ -18,7 +18,7 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
capabilitiesForBrowserStack({
browserName: 'safari',
platform: 'MAC',
version: '7'
version: '9'
})
];
} else {
@@ -28,8 +28,8 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
config.multiCapabilities = [
capabilitiesForSauceLabs({
browserName: 'chrome',
platform: 'OS X 10.9',
version: '34'
platform: 'OS X 10.11',
version: '48'
}),
capabilitiesForSauceLabs({
browserName: 'firefox',
@@ -37,8 +37,8 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
}),
capabilitiesForSauceLabs({
browserName: 'safari',
platform: 'OS X 10.9',
version: '7'
platform: 'OS X 10.11',
version: '9'
})
];
}
+2 -2
View File
@@ -4,11 +4,11 @@ echo "#################################"
echo "#### Jenkins Build ############"
echo "#################################"
source scripts/jenkins/set-node-version.sh
# Enable tracing and exit on first failure
set -xe
scripts/jenkins/set-node-version.sh
# This is the default set of browsers to use on the CI server unless overridden via env variable
if [[ -z "$BROWSERS" ]]
then
+1 -1
View File
@@ -35,7 +35,7 @@ function init {
}
function build {
./set-node-version.sh
source ./set-node-version.sh
cd ../..
npm install -g grunt-cli
+1 -1
View File
@@ -4,4 +4,4 @@
source ~/.nvm/nvm.sh
# Use node.js at 4.2.x
nvm install 4.2
nvm install 4.4
+16 -6
View File
@@ -466,12 +466,22 @@ noop.$inject = [];
* functional style.
*
```js
function transformer(transformationFn, value) {
return (transformationFn || angular.identity)(value);
};
function transformer(transformationFn, value) {
return (transformationFn || angular.identity)(value);
};
// E.g.
function getResult(fn, input) {
return (fn || angular.identity)(input);
};
getResult(function(n) { return n * 2; }, 21); // returns 42
getResult(null, 21); // returns 21
getResult(undefined, 21); // returns 21
```
* @param {*} value to be returned.
* @returns {*} the value passed in.
*
* @param {*} value to be returned.
* @returns {*} the value passed in.
*/
function identity($) {return $;}
identity.$inject = [];
@@ -1594,7 +1604,7 @@ function bootstrap(element, modules, config) {
//Encode angle brackets to prevent input from being sanitized to empty string #8683
throw ngMinErr(
'btstrpd',
"App Already Bootstrapped with this Element '{0}'",
"App already bootstrapped with this element '{0}'",
tag.replace(/</,'&lt;').replace(/>/,'&gt;'));
}
+3 -3
View File
@@ -197,9 +197,9 @@ function setupModuleLoader(window) {
* @ngdoc method
* @name angular.Module#decorator
* @module ng
* @param {string} The name of the service to decorate.
* @param {Function} This function will be invoked when the service needs to be
* instantiated and should return the decorated service instance.
* @param {string} name The name of the service to decorate.
* @param {Function} decorFn This function will be invoked when the service needs to be
* instantiated and should return the decorated service instance.
* @description
* See {@link auto.$provide#decorator $provide.decorator()}.
*/
+1 -1
View File
@@ -41,7 +41,7 @@ function $AnchorScrollProvider() {
* When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
* current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
* in the
* [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document).
* [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document).
*
* It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
* match any anchor whenever it changes. This can be disabled by calling
+2 -2
View File
@@ -782,7 +782,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function parseIsolateBindings(scope, directiveName, isController) {
var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
var bindings = {};
var bindings = createMap();
forEach(scope, function(definition, scopeName) {
if (definition in bindingCache) {
@@ -1153,7 +1153,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
(nodeName === 'img' && key === 'src')) {
// sanitize a[href] and img[src] values
this[key] = value = $$sanitizeUri(value, key === 'src');
} else if (nodeName === 'img' && key === 'srcset') {
} else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) {
// sanitize img[srcset] values
var result = "";
+1 -1
View File
@@ -393,7 +393,7 @@ var inputType = {
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
<label for="exampleInput">Pick a between 8am and 5pm:</label>
<label for="exampleInput">Pick a time between 8am and 5pm:</label>
<input type="time" id="exampleInput" name="input" ng-model="example.value"
placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
<div role="alert">
+6 -5
View File
@@ -188,8 +188,9 @@ var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse,
restrict: 'A',
compile: function ngBindHtmlCompile(tElement, tAttrs) {
var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
return (value || '').toString();
var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function sceValueOf(val) {
// Unwrap the value to compare the actual inner safe value, not the wrapper object.
return $sce.valueOf(val);
});
$compile.$$addBindingClass(tElement);
@@ -197,9 +198,9 @@ var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse,
$compile.$$addBindingInfo(element, attr.ngBindHtml);
scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
// we re-evaluate the expr because we want a TrustedValueHolderType
// for $sce, not a string
element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
// The watched value is the unwrapped value. To avoid re-escaping, use the direct getter.
var value = ngBindHtmlGetter(scope);
element.html($sce.getTrustedHtml(value) || '');
});
};
}
+5 -1
View File
@@ -78,7 +78,11 @@ function classDirective(name, selector) {
updateClasses(oldClasses, newClasses);
}
}
oldVal = shallowCopy(newVal);
if (isArray(newVal)) {
oldVal = newVal.map(function(v) { return shallowCopy(v); });
} else {
oldVal = shallowCopy(newVal);
}
}
}
};
+1
View File
@@ -2,6 +2,7 @@
/**
* @ngdoc directive
* @name ngRequired
* @restrict A
*
* @description
*
+7 -5
View File
@@ -93,7 +93,9 @@ function currencyFilter($locale) {
* @param {(number|string)=} fractionSize Number of decimal places to round the number to.
* If this is not provided then the fraction size is computed from the current locale's number
* formatting pattern. In the case of the default locale, it will be 3.
* @returns {string} Number rounded to fractionSize and places a “,” after each third digit.
* @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current
* locale (e.g., in the en_US locale it will have "." as the decimal separator and
* include "," group separators after each third digit).
*
* @example
<example module="numberFilterExample">
@@ -296,7 +298,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
// extract decimals digits
if (integerLen > 0) {
decimals = digits.splice(integerLen);
decimals = digits.splice(integerLen, digits.length);
} else {
decimals = digits;
digits = [0];
@@ -304,11 +306,11 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
// format the integer digits with grouping separators
var groups = [];
if (digits.length > pattern.lgSize) {
groups.unshift(digits.splice(-pattern.lgSize).join(''));
if (digits.length >= pattern.lgSize) {
groups.unshift(digits.splice(-pattern.lgSize, digits.length).join(''));
}
while (digits.length > pattern.gSize) {
groups.unshift(digits.splice(-pattern.gSize).join(''));
groups.unshift(digits.splice(-pattern.gSize, digits.length).join(''));
}
if (digits.length) {
groups.unshift(digits.join(''));
+1 -1
View File
@@ -555,7 +555,7 @@ function $HttpProvider() {
* That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference).
* For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest
* function will be reflected on the scope and in any templates where the object is data-bound.
* To prevent his, transform functions should have no side-effects.
* To prevent this, transform functions should have no side-effects.
* If you need to modify properties, it is recommended to make a copy of the data, or create new object to return.
* </div>
*
+5
View File
@@ -199,6 +199,11 @@ function $InterpolateProvider() {
* </file>
* </example>
*
* @knownIssue
* It is currently not possible for an interpolated expression to contain the interpolation end
* symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e.
* an interpolated expression consisting of a single-quote (`'`) and the `' }}` string.
*
* @param {string} text The text with markup to interpolate.
* @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
* embedded expression in order to return an interpolation function. Strings with no
+1 -1
View File
@@ -482,7 +482,7 @@ var locationPrototype = {
* ```
*
* @param {(string|number)=} path New path
* @return {string} path
* @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter
*/
path: locationGetterSetter('$$path', function(path) {
path = path !== null ? path.toString() : '';
+3 -3
View File
@@ -13,15 +13,15 @@
* [Kris Kowal's Q](https://github.com/kriskowal/q).
*
* $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
* implementations, and the other which resembles ES6 promises to some degree.
* implementations, and the other which resembles ES6 (ES2015) promises to some degree.
*
* # $q constructor
*
* The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
* function as the first argument. This is similar to the native Promise implementation from ES6 Harmony,
* function as the first argument. This is similar to the native Promise implementation from ES6,
* see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
*
* While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are
* While the constructor-style use is supported, not all of the supporting methods from ES6 promises are
* available yet.
*
* It can be used like so:
+1 -1
View File
@@ -31,7 +31,7 @@ function $TemplateRequestProvider() {
// are included in there. This also makes Angular accept any script
// directive, no matter its name. However, we still need to unwrap trusted
// types.
if (!isString(tpl) || !$templateCache.get(tpl)) {
if (!isString(tpl) || isUndefined($templateCache.get(tpl))) {
tpl = $sce.getTrustedResourceUrl(tpl);
}
+15 -7
View File
@@ -103,6 +103,9 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
var activeAnimationsLookup = new $$HashMap();
var disabledElementsLookup = new $$HashMap();
var animationsEnabled = null;
// $document might be mocked out in tests and won't include a real document.
// Providing an empty object with hidden = true will prevent animations from running
var rawDocument = $document[0] || {hidden: true};
function postDigestTaskFactory() {
var postDigestCalled = false;
@@ -331,12 +334,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
var documentHidden = rawDocument.hidden;
// this is a hard disable of all animations for the application or on
// the element itself, therefore there is no need to continue further
// past this point if not enabled
// Animations are also disabled if the document is currently hidden (page is not visible
// to the user), because browsers slow down or do not flush calls to requestAnimationFrame
var skipAnimations = !animationsEnabled || $document[0].hidden || disabledElementsLookup.get(node);
var skipAnimations = !animationsEnabled || documentHidden || disabledElementsLookup.get(node);
var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
var hasExistingAnimation = !!existingAnimation.state;
@@ -347,7 +352,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
if (skipAnimations) {
// Callbacks should fire even if the document is hidden (regression fix for issue #14120)
if (documentHidden) notifyProgress(runner, event, 'start');
close();
if (documentHidden) notifyProgress(runner, event, 'close');
return runner;
}
@@ -497,6 +505,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
markElementAnimationState(element, RUNNING_STATE);
var realRunner = $$animation(element, event, animationDetails.options);
// this will update the runner's flow-control events based on
// the `realRunner` object.
runner.setHost(realRunner);
notifyProgress(runner, event, 'start', {});
realRunner.done(function(status) {
close(!status);
var animationDetails = activeAnimationsLookup.get(node);
@@ -505,11 +518,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
notifyProgress(runner, event, 'close', {});
});
// this will update the runner's flow-control events based on
// the `realRunner` object.
runner.setHost(realRunner);
notifyProgress(runner, event, 'start', {});
});
return runner;
@@ -578,7 +586,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
* d) the element is not a child of the $rootElement
*/
function areAnimationsAllowed(element, parentElement, event) {
var bodyElement = jqLite($document[0].body);
var bodyElement = jqLite(rawDocument.body);
var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
var rootElementDetected = isMatchingElement(element, $rootElement);
var parentAnimationDetected = false;
+1 -1
View File
@@ -127,7 +127,7 @@ function stripCommentsFromElement(element) {
if (element instanceof jqLite) {
switch (element.length) {
case 0:
return [];
return element;
break;
case 1:
+14 -3
View File
@@ -410,6 +410,13 @@ angular.module('ngMessages', [])
$scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
// If the element is destroyed, proactively destroy all the currently visible messages
$element.on('$destroy', function() {
forEach(messages, function(item) {
item.message.detach();
});
});
this.reRender = function() {
if (!renderLater) {
renderLater = true;
@@ -444,6 +451,7 @@ angular.module('ngMessages', [])
function findPreviousMessage(parent, comment) {
var prevNode = comment;
var parentLookup = [];
while (prevNode && prevNode !== parent) {
var prevKey = prevNode.$$ngMessageNode;
if (prevKey && prevKey.length) {
@@ -455,8 +463,11 @@ angular.module('ngMessages', [])
if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) == -1) {
parentLookup.push(prevNode);
prevNode = prevNode.childNodes[prevNode.childNodes.length - 1];
} else if (prevNode.previousSibling) {
prevNode = prevNode.previousSibling;
} else {
prevNode = prevNode.previousSibling || prevNode.parentNode;
prevNode = prevNode.parentNode;
parentLookup.push(prevNode);
}
}
}
@@ -664,8 +675,8 @@ function ngMessageDirectiveFactory(restrict) {
// when we are destroying the node later.
var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId();
// in the event that the parent element is destroyed
// by any other structural directive then it's time
// in the event that the element or a parent element is destroyed
// by another structural directive then it's time
// to deregister the message from the controller
currentElement.on('$destroy', function() {
if (currentElement && currentElement.$$attachId === $$attachId) {
+6
View File
@@ -2480,6 +2480,12 @@ if (window.jasmine || window.mocha) {
window.inject = angular.mock.inject = function() {
var blockFns = Array.prototype.slice.call(arguments, 0);
var errorForStack = new Error('Declaration Location');
// IE10+ and PhanthomJS do not set stack trace information, until the error is thrown
if (!errorForStack.stack) {
try {
throw errorForStack;
} catch (e) {}
}
return isSpecRunning() ? workFn.call(currentSpec) : workFn;
/////////////////////
function workFn() {
+21
View File
@@ -7602,6 +7602,27 @@ describe('$compile', function() {
describe('img[srcset] sanitization', function() {
it('should not error if undefined', function() {
var linked = false;
module(function() {
directive('setter', valueFn(function(scope, elem, attrs) {
attrs.$set('srcset', 'http://example.com/');
expect(attrs.srcset).toBe('http://example.com/');
attrs.$set('srcset', undefined);
expect(attrs.srcset).toBeUndefined();
linked = true;
}));
});
inject(function($compile, $rootScope) {
element = $compile('<img setter></img>')($rootScope);
expect(linked).toBe(true);
expect(element.attr('srcset')).toBeUndefined();
});
});
it('should NOT require trusted values for img srcset', inject(function($rootScope, $compile, $sce) {
element = $compile('<img srcset="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = 'http://example.com/image.png';
+49 -1
View File
@@ -142,6 +142,16 @@ describe('ngBind*', function() {
expect(angular.lowercase(element.html())).toEqual('<div onclick="">hello</div>');
}));
it('should update html', inject(function($rootScope, $compile, $sce) {
element = $compile('<div ng-bind-html="html"></div>')($rootScope);
$rootScope.html = 'hello';
$rootScope.$digest();
expect(angular.lowercase(element.html())).toEqual('hello');
$rootScope.html = 'goodbye';
$rootScope.$digest();
expect(angular.lowercase(element.html())).toEqual('goodbye');
}));
it('should one-time bind if the expression starts with two colons', inject(function($rootScope, $compile) {
element = $compile('<div ng-bind-html="::html"></div>')($rootScope);
$rootScope.html = '<div onclick="">hello</div>';
@@ -176,7 +186,18 @@ describe('ngBind*', function() {
expect(angular.lowercase(element.html())).toEqual('<div onclick="">hello</div>');
}));
it('should watch the string value to avoid infinite recursion', inject(function($rootScope, $compile, $sce) {
it('should update html', inject(function($rootScope, $compile, $sce) {
element = $compile('<div ng-bind-html="html"></div>')($rootScope);
$rootScope.html = $sce.trustAsHtml('hello');
$rootScope.$digest();
expect(angular.lowercase(element.html())).toEqual('hello');
$rootScope.html = $sce.trustAsHtml('goodbye');
$rootScope.$digest();
expect(angular.lowercase(element.html())).toEqual('goodbye');
}));
it('should not cause infinite recursion for trustAsHtml object watches',
inject(function($rootScope, $compile, $sce) {
// Ref: https://github.com/angular/angular.js/issues/3932
// If the binding is a function that creates a new value on every call via trustAs, we'll
// trigger an infinite digest if we don't take care of it.
@@ -188,6 +209,33 @@ describe('ngBind*', function() {
expect(angular.lowercase(element.html())).toEqual('<div onclick="">hello</div>');
}));
it('should handle custom $sce objects', function() {
function MySafeHtml(val) { this.val = val; }
module(function($provide) {
$provide.decorator('$sce', function($delegate) {
$delegate.trustAsHtml = function(html) { return new MySafeHtml(html); };
$delegate.getTrustedHtml = function(mySafeHtml) { return mySafeHtml.val; };
$delegate.valueOf = function(v) { return v instanceof MySafeHtml ? v.val : v; };
return $delegate;
});
});
inject(function($rootScope, $compile, $sce) {
// Ref: https://github.com/angular/angular.js/issues/14526
// Previous code used toString for change detection, which fails for custom objects
// that don't override toString.
element = $compile('<div ng-bind-html="getHtml()"></div>')($rootScope);
var html = 'hello';
$rootScope.getHtml = function() { return $sce.trustAsHtml(html); };
$rootScope.$digest();
expect(angular.lowercase(element.html())).toEqual('hello');
html = 'goodbye';
$rootScope.$digest();
expect(angular.lowercase(element.html())).toEqual('goodbye');
});
});
describe('when $sanitize is available', function() {
beforeEach(function() { module('ngSanitize'); });
+16
View File
@@ -409,6 +409,22 @@ describe('ngClass', function() {
expect(e2.hasClass('even')).toBeTruthy();
expect(e2.hasClass('odd')).toBeFalsy();
}));
it('should support mixed array/object variable with a mutating object',
inject(function($rootScope, $compile) {
element = $compile('<div ng-class="classVar"></div>')($rootScope);
$rootScope.classVar = [{orange: true}];
$rootScope.$digest();
expect(element).toHaveClass('orange');
$rootScope.classVar[0].orange = false;
$rootScope.$digest();
expect(element).not.toHaveClass('orange');
})
);
});
describe('ngClass animations', function() {
+5
View File
@@ -28,5 +28,10 @@ describe('ngSrcset', function() {
$rootScope.$digest();
expect(element.attr('srcset')).toBe('http://example.com/image1.png 1x,unsafe:javascript:doEvilStuff() 2x');
}));
it('should not throw an error if undefined', inject(function($rootScope, $compile) {
element = $compile('<img ng-attr-srcset="{{undefined}}">')($rootScope);
$rootScope.$digest();
}));
});
+5 -1
View File
@@ -35,7 +35,11 @@ describe('filters', function() {
it('should format according to different patterns', function() {
pattern.gSize = 2;
var num = formatNumber(1234567.89, pattern, ',', '.');
var num = formatNumber(99, pattern, ',', '.');
expect(num).toBe('99');
num = formatNumber(888, pattern, ',', '.');
expect(num).toBe('888');
num = formatNumber(1234567.89, pattern, ',', '.');
expect(num).toBe('12,34,567.89');
num = formatNumber(1234.56, pattern, ',', '.');
expect(num).toBe('1,234.56');
+33
View File
@@ -81,6 +81,39 @@ describe('$templateRequest', function() {
}).not.toThrow();
}));
it('should accept empty templates and refuse null or undefined templates in cache',
inject(function($rootScope, $templateRequest, $templateCache, $sce) {
// Will throw on any template not in cache.
spyOn($sce, 'getTrustedResourceUrl').andReturn(false);
expect(function() {
$templateRequest('tpl.html'); // should go through $sce
$rootScope.$digest();
}).toThrow();
$templateCache.put('tpl.html'); // is a no-op, so $sce check as well.
expect(function() {
$templateRequest('tpl.html');
$rootScope.$digest();
}).toThrow();
$templateCache.removeAll();
$templateCache.put('tpl.html', null); // makes no sense, but it's been added, so trust it.
expect(function() {
$templateRequest('tpl.html');
$rootScope.$digest();
}).not.toThrow();
$templateCache.removeAll();
$templateCache.put('tpl.html', ''); // should work (empty template)
expect(function() {
$templateRequest('tpl.html');
$rootScope.$digest();
}).not.toThrow();
$templateCache.removeAll();
}));
it('should keep track of how many requests are going on',
inject(function($rootScope, $templateRequest, $httpBackend) {
+208 -1
View File
@@ -395,6 +395,19 @@ describe("animations", function() {
expect(capturedAnimation).toBeFalsy();
}));
it('should not attempt to perform an animation on an empty jqLite collection',
inject(function($rootScope, $animate) {
element.html('');
var emptyNode = jqLite(element[0].firstChild);
$animate.addClass(emptyNode, 'some-class');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
})
);
it('should perform the leave domOperation if a text node is used',
inject(function($rootScope, $animate) {
@@ -1256,7 +1269,7 @@ describe("animations", function() {
expect(element).not.toHaveClass('green');
}));
it('should automatically cancel out class-based animations if the element already contains or doesn\' contain the applied classes',
it('should automatically cancel out class-based animations if the element already contains or doesn\'t contain the applied classes',
inject(function($animate, $rootScope) {
parent.append(element);
@@ -1391,6 +1404,36 @@ describe("animations", function() {
});
});
it('should not run animations if the document is unavailable', function() {
var capturedAnimation;
module(function($provide) {
$provide.value('$document', {});
$provide.factory('$$animation', function($$AnimateRunner) {
return function(element, method, options) {
capturedAnimation = arguments;
return new $$AnimateRunner();
};
});
});
inject(function($animate, $rootScope, $rootElement, $document) {
$animate.enabled(true);
var spy = jasmine.createSpy();
element = jqLite('<div></div>');
var runner = $animate.enter(element, $rootElement);
$rootScope.$digest();
$animate.flush();
expect(capturedAnimation).toBeUndefined();
});
});
describe('[ng-animate-children]', function() {
var parent, element, child, capturedAnimation, captureLog;
beforeEach(module(function($provide) {
@@ -2137,5 +2180,169 @@ describe("animations", function() {
});
});
describe('when animations are skipped, disabled, or invalid', function() {
var overriddenAnimationRunner;
var capturedAnimation;
var capturedAnimationHistory;
var defaultFakeAnimationRunner;
var parent;
var parent2;
beforeEach(module(function($provide) {
overriddenAnimationRunner = null;
capturedAnimation = null;
capturedAnimationHistory = [];
$provide.value('$$animation', function() {
capturedAnimationHistory.push(capturedAnimation = arguments);
return overriddenAnimationRunner || defaultFakeAnimationRunner;
});
return function($rootElement, $q, $animate, $$AnimateRunner, $document) {
defaultFakeAnimationRunner = new $$AnimateRunner();
$animate.enabled(true);
element = jqLite('<div class="element">element</div>');
parent = jqLite('<div class="parent1">parent</div>');
parent2 = jqLite('<div class="parent2">parent</div>');
$rootElement.append(parent);
$rootElement.append(parent2);
jqLite($document[0].body).append($rootElement);
};
}));
it('should trigger all callbacks if a follow-up structural animation takes over a running animation',
inject(function($animate, $rootScope) {
parent.append(element);
var moveSpy = jasmine.createSpy();
var leaveSpy = jasmine.createSpy();
$animate.on('move', parent2, moveSpy);
$animate.on('leave', parent2, leaveSpy);
$animate.move(element, parent2);
$rootScope.$digest();
$animate.flush();
expect(moveSpy.calls.length).toBe(1);
expect(moveSpy.mostRecentCall.args[1]).toBe('start');
$animate.leave(element);
$rootScope.$digest();
$animate.flush();
expect(moveSpy.calls.length).toBe(2);
expect(moveSpy.mostRecentCall.args[1]).toBe('close');
expect(leaveSpy.calls.length).toBe(2);
expect(leaveSpy.calls[0].args[1]).toBe('start');
expect(leaveSpy.calls[1].args[1]).toBe('close');
}));
it('should not trigger callbacks for the previous structural animation if a follow-up structural animation takes over before the postDigest',
inject(function($animate, $rootScope) {
var enterDone = jasmine.createSpy('enter animation done');
var enterSpy = jasmine.createSpy();
var leaveSpy = jasmine.createSpy();
$animate.on('enter', parent, enterSpy);
$animate.on('leave', parent, leaveSpy);
$animate.enter(element, parent).done(enterDone);
expect(enterDone).not.toHaveBeenCalled();
var runner = $animate.leave(element);
$animate.flush();
expect(enterDone).toHaveBeenCalled();
expect(enterSpy).not.toHaveBeenCalled();
expect(leaveSpy.calls.length).toBe(1);
expect(leaveSpy.mostRecentCall.args[1]).toBe('start');
leaveSpy.reset();
runner.end();
$animate.flush();
expect(enterSpy).not.toHaveBeenCalled();
expect(leaveSpy.calls.length).toBe(1);
expect(leaveSpy.mostRecentCall.args[1]).toBe('close');
}));
it('should not trigger the callback if animations are disabled on the element',
inject(function($animate, $rootScope, $rootElement, $document) {
var callbackTriggered = false;
var spy = jasmine.createSpy('enter');
$animate.on('enter', jqLite($document[0].body), spy);
element = jqLite('<div></div>');
$animate.enabled(element, false);
var runner = $animate.enter(element, $rootElement);
$rootScope.$digest();
$animate.flush(); // Flushes the animation frames for the callbacks
expect(spy).not.toHaveBeenCalled();
}));
it('should not trigger the callbacks if the animation is skipped because there are no class-based animations and no structural animation',
inject(function($animate, $rootScope) {
parent.append(element);
var classSpy = jasmine.createSpy('classChange');
$animate.on('addClass', element, classSpy);
$animate.on('removeClass', element, classSpy);
element.addClass('one three');
$animate.addClass(element, 'one');
$animate.removeClass(element, 'four');
$rootScope.$digest();
$animate.flush();
expect(classSpy).not.toHaveBeenCalled();
}));
describe('because the document is hidden', function() {
beforeEach(module(function($provide) {
var doc = jqLite({
body: document.body,
hidden: true
});
$provide.value('$document', doc);
}));
it('should trigger callbacks for an enter animation',
inject(function($animate, $rootScope, $rootElement, $document) {
var callbackTriggered = false;
var spy = jasmine.createSpy();
$animate.on('enter', jqLite($document[0].body), spy);
element = jqLite('<div></div>');
var runner = $animate.enter(element, $rootElement);
$rootScope.$digest();
$animate.flush(); // Flushes the animation frames for the callbacks
expect(spy.calls.length).toBe(2);
expect(spy.calls[0].args[1]).toBe('start');
expect(spy.calls[1].args[1]).toBe('close');
}));
});
});
});
});
+151
View File
@@ -445,6 +445,126 @@ describe('ngMessages', function() {
});
});
describe('ngMessage nested nested inside elements', function() {
it('should not crash or leak memory when the messages are transcluded, the first message is ' +
'visible, and ngMessages is removed by ngIf', function() {
module(function($compileProvider) {
$compileProvider.directive('messageWrap', function() {
return {
transclude: true,
scope: {
col: '=col'
},
template: '<div ng-messages="col"><ng-transclude></ng-transclude></div>'
};
});
});
inject(function($rootScope, $compile) {
element = $compile('<div><div ng-if="show"><div message-wrap col="col">' +
' <div ng-message="a">A</div>' +
' <div ng-message="b">B</div>' +
'</div></div></div>')($rootScope);
$rootScope.$apply(function() {
$rootScope.show = true;
$rootScope.col = {
a: true,
b: true
};
});
expect(messageChildren(element).length).toBe(1);
expect(trim(element.text())).toEqual('A');
$rootScope.$apply('show = false');
expect(messageChildren(element).length).toBe(0);
});
});
it('should not crash when the first of two nested messages is removed', function() {
inject(function($rootScope, $compile) {
element = $compile(
'<div ng-messages="col">' +
'<div class="wrapper">' +
'<div remove-me ng-message="a">A</div>' +
'<div ng-message="b">B</div>' +
'</div>' +
'</div>'
)($rootScope);
$rootScope.$apply(function() {
$rootScope.col = {
a: true,
b: false
};
});
expect(messageChildren(element).length).toBe(1);
expect(trim(element.text())).toEqual('A');
var ctrl = element.controller('ngMessages');
var deregisterSpy = spyOn(ctrl, 'deregister').andCallThrough();
var nodeA = element[0].querySelector('[ng-message="a"]');
jqLite(nodeA).remove();
$rootScope.$digest(); // The next digest triggers the error
// Make sure removing the element triggers the deregistration in ngMessages
expect(trim(deregisterSpy.mostRecentCall.args[0].nodeValue)).toBe('ngMessage: a');
expect(messageChildren(element).length).toBe(0);
});
});
it('should not crash, but show deeply nested messages correctly after a message ' +
'has been removed', function() {
inject(function($rootScope, $compile) {
element = $compile(
'<div ng-messages="col" ng-messages-multiple>' +
'<div class="another-wrapper">' +
'<div ng-message="a">A</div>' +
'<div class="wrapper">' +
'<div ng-message="b">B</div>' +
'<div ng-message="c">C</div>' +
'</div>' +
'<div ng-message="d">D</div>' +
'</div>' +
'</div>'
)($rootScope);
$rootScope.$apply(function() {
$rootScope.col = {
a: true,
b: true
};
});
expect(messageChildren(element).length).toBe(2);
expect(trim(element.text())).toEqual('AB');
var ctrl = element.controller('ngMessages');
var deregisterSpy = spyOn(ctrl, 'deregister').andCallThrough();
var nodeB = element[0].querySelector('[ng-message="b"]');
jqLite(nodeB).remove();
$rootScope.$digest(); // The next digest triggers the error
// Make sure removing the element triggers the deregistration in ngMessages
expect(trim(deregisterSpy.mostRecentCall.args[0].nodeValue)).toBe('ngMessage: b');
expect(messageChildren(element).length).toBe(1);
expect(trim(element.text())).toEqual('A');
});
});
});
describe('when including templates', function() {
they('should work with a dynamic collection model which is managed by ngRepeat',
{'<div ng-messages-include="...">': '<div ng-messages="item">' +
@@ -651,6 +771,37 @@ describe('ngMessages', function() {
expect(trim(element.text())).toEqual("C");
}));
it('should properly detect a previous message, even if it was registered later',
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('include.html', '<div ng-message="a">A</div>');
var html =
'<div ng-messages="items">' +
'<div ng-include="\'include.html\'"></div>' +
'<div ng-message="b">B</div>' +
'<div ng-message="c">C</div>' +
'</div>';
element = $compile(html)($rootScope);
$rootScope.$apply('items = {b: true, c: true}');
expect(element.text()).toBe('B');
var ctrl = element.controller('ngMessages');
var deregisterSpy = spyOn(ctrl, 'deregister').andCallThrough();
var nodeB = element[0].querySelector('[ng-message="b"]');
jqLite(nodeB).remove();
// Make sure removing the element triggers the deregistration in ngMessages
expect(trim(deregisterSpy.mostRecentCall.args[0].nodeValue)).toBe('ngMessage: b');
$rootScope.$apply('items.a = true');
expect(element.text()).toBe('A');
})
);
});
describe('when multiple', function() {
+45
View File
@@ -919,6 +919,51 @@ describe('ngMock', function() {
});
}).toThrow('test message');
}));
describe('error stack trace when called outside of spec context', function() {
// - Chrome, Firefox, Edge, Opera give us the stack trace as soon as an Error is created
// - IE10+, PhantomJS give us the stack trace only once the error is thrown
// - IE9 does not provide stack traces
var stackTraceSupported = (function() {
var error = new Error();
if (!error.stack) {
try {
throw error;
} catch (e) {}
}
return !!error.stack;
})();
function testCaller() {
return inject(function() {
throw new Error();
});
}
var throwErrorFromInjectCallback = testCaller();
if (stackTraceSupported) {
describe('on browsers supporting stack traces', function() {
it('should update thrown Error stack trace with inject call location', function() {
try {
throwErrorFromInjectCallback();
} catch (e) {
expect(e.stack).toMatch('testCaller');
}
});
});
} else {
describe('on browsers not supporting stack traces', function() {
it('should not add stack trace information to thrown Error', function() {
try {
throwErrorFromInjectCallback();
} catch (e) {
expect(e.stack).toBeUndefined();
}
});
});
}
});
});
});