Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d396f42537 | |||
| 5ec900a125 | |||
| 66503a6e18 | |||
| 2d8749e8c9 | |||
| b39e1d47b9 | |||
| 5061d2c97c | |||
| a961291aa2 | |||
| 3068d8e52a | |||
| d2dd3581a9 | |||
| 53feb272be | |||
| 408508ad29 | |||
| ece7d19115 | |||
| 992101da10 | |||
| 47f42ecf45 | |||
| fd995abc9a | |||
| 5f9a9747d2 | |||
| 94b0f2d35d | |||
| c12e8d4665 | |||
| c65796d496 | |||
| 34b43eab5f | |||
| 9d9cdfb575 | |||
| 68a09ba74d | |||
| 239d0b1f49 | |||
| 4e04c73cd3 | |||
| 6c863e5bba | |||
| e0cf7c5bf2 | |||
| 466320f691 | |||
| 7e02fa07eb | |||
| 9ed9777317 | |||
| e661bc9f15 | |||
| 7af210f1de | |||
| b48203f9f2 | |||
| 789f0f1809 | |||
| 8366d545c5 | |||
| 1f5b4c95eb | |||
| 910d652ce5 | |||
| e5e62a5479 | |||
| 8f14b726b0 | |||
| 88b6a9a288 | |||
| d4fc40c282 | |||
| 653700df5b | |||
| 2712c2f197 | |||
| 36e6de1d91 | |||
| 9bf964f1f3 | |||
| 54f0bc0fe0 | |||
| 2ece4d0347 | |||
| 1812af58c2 | |||
| 19cb2e3d12 | |||
| c92ce4511b | |||
| d7548fdf1c | |||
| 7e239f9485 | |||
| fa5daa7693 |
+1
-2
@@ -9,8 +9,7 @@ performance/temp*.html
|
||||
*.swp
|
||||
angular.js.tmproj
|
||||
/node_modules/
|
||||
/components/
|
||||
/bower_components/
|
||||
bower_components/
|
||||
angular.xcodeproj
|
||||
.idea
|
||||
.agignore
|
||||
|
||||
+553
@@ -1,3 +1,549 @@
|
||||
# NOTICE: Pending Breaking Change
|
||||
|
||||
The next 1.3.0 release candidate (1.3.0-rc.2) will contain a perf-related change that is likely to
|
||||
introduce breakages in some applications. The change will affect filters and function call
|
||||
expressions, and will not call the function if the variables passed to the function are primitive
|
||||
values and have not changed since the last digest loop.
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
//date filter would only be called if the 'timeCreated' property has changed
|
||||
<span ng-bind="timeCreated|date"></span>
|
||||
|
||||
//custom filter would break if depends on data changed by user other than 'cost'
|
||||
<span ng-bind="cost|i18nLocalizer">
|
||||
```
|
||||
|
||||
|
||||
<a name="1.3.0-rc.1"></a>
|
||||
# 1.3.0-rc.1 backyard-atomicity (2014-09-09)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:**
|
||||
- don't call toString on null values
|
||||
([c3a58a9f](https://github.com/angular/angular.js/commit/c3a58a9f34919f121587540e03ecbd51b25198d4))
|
||||
- remove an unused parameter of $location.url
|
||||
([99d95f16](https://github.com/angular/angular.js/commit/99d95f1639b64c39231448d77209676b54e6f0be))
|
||||
- allow numeric location setter arguments
|
||||
([adb5c6d6](https://github.com/angular/angular.js/commit/adb5c6d6cc76b928436743707727ab0974d6810b),
|
||||
[#7054](https://github.com/angular/angular.js/issues/7054))
|
||||
- set `baseHref` in mock browser to `/`
|
||||
([fc706d13](https://github.com/angular/angular.js/commit/fc706d13d80bb40eb3dade58ea4b92dca33ce4e7),
|
||||
[#8866](https://github.com/angular/angular.js/issues/8866), [#8889](https://github.com/angular/angular.js/issues/8889))
|
||||
- **$parse:** disallow passing Function to Array.sort
|
||||
([bd8ad0fb](https://github.com/angular/angular.js/commit/bd8ad0fbe81f6c280baa26a596d78e58fc7842e6))
|
||||
- **input:** check `scope.$$phase` only on `$rootScope`
|
||||
([bf59d727](https://github.com/angular/angular.js/commit/bf59d7274f4a667c5b19e6d4ba5ed2730ca2fe42))
|
||||
- **ngAnimate:** support removing classes from SVG elements when using jQuery
|
||||
([b3b67213](https://github.com/angular/angular.js/commit/b3b672130d4d1c6f13bdf7e58be76b2aafea2497),
|
||||
[#8872](https://github.com/angular/angular.js/issues/8872), [#8893](https://github.com/angular/angular.js/issues/8893))
|
||||
- **ngEventDirs:** check `scope.$$phase` only on `$rootScope`
|
||||
([203ea10f](https://github.com/angular/angular.js/commit/203ea10f9ea49d7e29569a4232d3b2a666307cd8),
|
||||
[#8891](https://github.com/angular/angular.js/issues/8891))
|
||||
- **ngForm:** don't clear validity of whole form when removing control
|
||||
([953ee22f](https://github.com/angular/angular.js/commit/953ee22f76f8c1137949ed07f36fafc5bbfeb7fe),
|
||||
[#8863](https://github.com/angular/angular.js/issues/8863))
|
||||
- **ngInclude:** correctly add svg-namespaced template content
|
||||
([6639ca9d](https://github.com/angular/angular.js/commit/6639ca9d6bc00a6e3a31e54c50474361ae3561c6),
|
||||
[#7538](https://github.com/angular/angular.js/issues/7538), [#8981](https://github.com/angular/angular.js/issues/8981), [#8997](https://github.com/angular/angular.js/issues/8997))
|
||||
- **ngModel:**
|
||||
- update model value with async validators correctly
|
||||
([64c3b745](https://github.com/angular/angular.js/commit/64c3b745fba0792166f30e057f9251f263d80dac))
|
||||
- render immediately also with async validators
|
||||
([f94d5515](https://github.com/angular/angular.js/commit/f94d551529b7c970c38b29e3073cec4e7f6b0e00))
|
||||
- properly parse min/max date values as strings for date inputs
|
||||
([088545c1](https://github.com/angular/angular.js/commit/088545c1856ce1c3ec3416965dff65077a6e0523),
|
||||
[#6755](https://github.com/angular/angular.js/issues/6755))
|
||||
- revalidate the model when min/max expression values change for date inputs
|
||||
([b3502835](https://github.com/angular/angular.js/commit/b3502835039178296b730b7526e5666b66ba9156),
|
||||
[#6755](https://github.com/angular/angular.js/issues/6755))
|
||||
- consider ngMin/ngMax values when validating number input types
|
||||
([25541c1f](https://github.com/angular/angular.js/commit/25541c1f876a16c892d71faae11727bec7bba98c))
|
||||
- revalidate the model when min/max expression values change for number inputs
|
||||
([7b273a2c](https://github.com/angular/angular.js/commit/7b273a2c978d5f5ef374f5335afab0ca7d8cfd4d),
|
||||
[#2404](https://github.com/angular/angular.js/issues/2404))
|
||||
- **ngModelOptions:** do not trigger digest on `setViewValue` if debouncing
|
||||
([e322cd9b](https://github.com/angular/angular.js/commit/e322cd9b3b8b47b95c9de3edf631bb46f919c492),
|
||||
[#8814](https://github.com/angular/angular.js/issues/8814), [#8850](https://github.com/angular/angular.js/issues/8850), [#8911](https://github.com/angular/angular.js/issues/8911))
|
||||
- **ngRepeat:** preserve original position of elements that are being animated away
|
||||
([ed637330](https://github.com/angular/angular.js/commit/ed6373300028deda9a0878b3975699d183c1f75c),
|
||||
[#8918](https://github.com/angular/angular.js/issues/8918), [#8994](https://github.com/angular/angular.js/issues/8994))
|
||||
- **ngSwitch:** ensure correct iterator is passed to async function
|
||||
([712299c2](https://github.com/angular/angular.js/commit/712299c2a24390e74cd5c20f51cb1d78f0233b6f),
|
||||
[#8833](https://github.com/angular/angular.js/issues/8833))
|
||||
- **numberFilter:** format numbers that round to zero as nonnegative
|
||||
([ae952fbf](https://github.com/angular/angular.js/commit/ae952fbf0be925a48743d1c925ffe4e31a42c280),
|
||||
[#8489](https://github.com/angular/angular.js/issues/8489))
|
||||
- **orderBy:** allow arrayLike objects to be ordered
|
||||
([cbdaabfb](https://github.com/angular/angular.js/commit/cbdaabfb59bf3348588d5b581f2754e0f9f034a4),
|
||||
[#8944](https://github.com/angular/angular.js/issues/8944))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **angular.forEach:** add the array/object as the 3rd param like the native array forEach
|
||||
([df9e60c8](https://github.com/angular/angular.js/commit/df9e60c8e7453cdca2cb5a4fa48f3981ecc23a7d),
|
||||
[#7902](https://github.com/angular/angular.js/issues/7902))
|
||||
- **ngModelOptions:** add allowInvalid option
|
||||
([3c538c1d](https://github.com/angular/angular.js/commit/3c538c1d21c43422c7b4cd9b69cb67981bce2b87),
|
||||
[#8290](https://github.com/angular/angular.js/issues/8290), [#8313](https://github.com/angular/angular.js/issues/8313))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$parse:**
|
||||
- remove getterFn wrapper for internal use
|
||||
([b3b476db](https://github.com/angular/angular.js/commit/b3b476db7d34bc2f8b099ab5b993b1e899b9cffd),
|
||||
[#8901](https://github.com/angular/angular.js/issues/8901))
|
||||
- removing references to Parser/Lexer from parsed expressions
|
||||
([43c67ccd](https://github.com/angular/angular.js/commit/43c67ccd167aecc3549e1b7f7d100956204e3ed4))
|
||||
- calculate array lengths once at start of loop
|
||||
([907b8c16](https://github.com/angular/angular.js/commit/907b8c1675865ac38dd055f3f304272e68b233d0))
|
||||
- **extend:** remove use of forEach to remove calls/closures/passing arguments
|
||||
([9bedeb33](https://github.com/angular/angular.js/commit/9bedeb3353969fba631ad9164edea3c38059fbda),
|
||||
[#8898](https://github.com/angular/angular.js/issues/8898))
|
||||
- **jQuery:** only trigger $destroy if a handler exists
|
||||
([f6aa1c55](https://github.com/angular/angular.js/commit/f6aa1c55616b34215f562e0445e436210860ef04),
|
||||
[#8859](https://github.com/angular/angular.js/issues/8859))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **ngModelController,formController:** due to [6046e14b](https://github.com/angular/angular.js/commit/6046e14bd22491168116e61ffdf5fd3fed5f135c),
|
||||
|
||||
- `ctrl.$error` no longer contains entries for validators that were
|
||||
successful.
|
||||
- `ctrl.$setValidity` now differentiates between `true`, `false`,
|
||||
`undefined` and `null`, instead of previously only truthy vs falsy.
|
||||
|
||||
Closes #8941- **ngSwitch:** due to [0f806d96](https://github.com/angular/angular.js/commit/0f806d9659b5b89a4bd9493364bc36398677e939),
|
||||
|
||||
|
||||
Ever since 0df93fd, tagged in v1.0.0rc1, the ngSwitch directive has had an undocumented `change`
|
||||
attribute, used for evaluating a scope expression when the switch value changes.
|
||||
|
||||
While it's unlikely, applications which may be using this feature should work around the removal
|
||||
by adding a custom directive which will perform the eval instead. Directive controllers are
|
||||
re-instantiated when being transcluded, so by putting the attribute on each item that you want
|
||||
to be notified of a change to, you can more or less emulate the old behaviour.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
angular.module("switchChangeWorkaround", []).
|
||||
directive("onSwitchChanged", function() {
|
||||
return {
|
||||
linke: function($scope, $attrs) {
|
||||
$scope.$parent.$eval($attrs.change);
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
```html
|
||||
<div ng-switch="switcher">
|
||||
<div ng-switch-when="a" on-switch-changed="doSomethingInParentScope()"></div>
|
||||
<div ng-switch-when="b" on-switch-changed="doSomethingInParentScope()"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Closes #8858
|
||||
Closes #8822
|
||||
|
||||
|
||||
<a name="1.2.24"></a>
|
||||
# 1.2.24 static-levitation (2014-09-09)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$browser:** detect changes to the browser url that happened in sync
|
||||
([2ece4d03](https://github.com/angular/angular.js/commit/2ece4d0347a8a18d4d35993bb882ed6b5b24266c),
|
||||
[#6976](https://github.com/angular/angular.js/issues/6976))
|
||||
- **$compile:**
|
||||
- render nested transclusion at the root of a template
|
||||
([9d9cdfb5](https://github.com/angular/angular.js/commit/9d9cdfb575b89e96ae957c986734a49995e2b511),
|
||||
[#8914](https://github.com/angular/angular.js/issues/8914), [#8925](https://github.com/angular/angular.js/issues/8925))
|
||||
- render nested transclusion at the root of a template
|
||||
([466320f6](https://github.com/angular/angular.js/commit/466320f6911698048bae5406e341d25af7efafa0),
|
||||
[#8914](https://github.com/angular/angular.js/issues/8914), [#8925](https://github.com/angular/angular.js/issues/8925))
|
||||
- **$location:**
|
||||
- don't call toString on null values
|
||||
([c12e8d46](https://github.com/angular/angular.js/commit/c12e8d4665b635ba6b09d12802efb88d38b7ad5c))
|
||||
- remove an unused parameter of $location.url
|
||||
([c65796d4](https://github.com/angular/angular.js/commit/c65796d496038554861e70da8012f9d0e2521e6d))
|
||||
- allow numeric location setter arguments
|
||||
([68a09ba7](https://github.com/angular/angular.js/commit/68a09ba74d10a1490feca1d248f85b0023aa399b),
|
||||
[#7054](https://github.com/angular/angular.js/issues/7054))
|
||||
- **$parse:** disallow passing Function to Array.sort
|
||||
([b39e1d47](https://github.com/angular/angular.js/commit/b39e1d47b9a1b39a9fe34c847a81f589fba522f8))
|
||||
- **form:** ensure concurrent animations use setClass
|
||||
([d7548fdf](https://github.com/angular/angular.js/commit/d7548fdf1ce6f543bf55d330985a83ef09d0cb83),
|
||||
[#8166](https://github.com/angular/angular.js/issues/8166))
|
||||
- **input:** check `scope.$$phase` only on `$rootScope`
|
||||
([36e6de1d](https://github.com/angular/angular.js/commit/36e6de1d91937d73e900ac115ae366fbefcdf6da))
|
||||
- **ngEventDirs:**
|
||||
- check `scope.$$phase` only on `$rootScope`
|
||||
([2712c2f1](https://github.com/angular/angular.js/commit/2712c2f1979db23eeb53be8a519b9f79bd75e217),
|
||||
[#8891](https://github.com/angular/angular.js/issues/8891))
|
||||
- execute `blur` and `focus` expression using `scope.$evalAsync`
|
||||
([54f0bc0f](https://github.com/angular/angular.js/commit/54f0bc0fe0c6b6d974d23f2c5ef07359dd93eb99),
|
||||
[#4979](https://github.com/angular/angular.js/issues/4979), [#5945](https://github.com/angular/angular.js/issues/5945), [#8803](https://github.com/angular/angular.js/issues/8803), [#6910](https://github.com/angular/angular.js/issues/6910), [#5402](https://github.com/angular/angular.js/issues/5402))
|
||||
- **ngRepeat:** improve errors for duplicate items
|
||||
([1812af58](https://github.com/angular/angular.js/commit/1812af58c2d470d586c2a543c9a7db3f0baca04f))
|
||||
- **numberFilter:** format numbers that round to zero as nonnegative
|
||||
([7e02fa07](https://github.com/angular/angular.js/commit/7e02fa07eb5b02e75b1db0058d638af3d1074942),
|
||||
[#8489](https://github.com/angular/angular.js/issues/8489))
|
||||
- **orderBy:** allow arrayLike objects to be ordered
|
||||
([94b0f2d3](https://github.com/angular/angular.js/commit/94b0f2d35de601ded3d93ea4fa78a4d9b139c0a0),
|
||||
[#8944](https://github.com/angular/angular.js/issues/8944))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **ngEventDirs:** due to [54f0bc0f](https://github.com/angular/angular.js/commit/54f0bc0fe0c6b6d974d23f2c5ef07359dd93eb99),
|
||||
|
||||
The `blur` and `focus` event fire synchronously, also during DOM operations
|
||||
that remove elements. This lead to errors as the Angular model was not
|
||||
in a consistent state. See this [fiddle](http://jsfiddle.net/fq1dq5yb/) for a demo.
|
||||
|
||||
This change executes the expression of those events using
|
||||
`scope.$evalAsync` if an `$apply` is in progress, otherwise
|
||||
keeps the old behavior.
|
||||
|
||||
Fixes #4979
|
||||
Fixes #5945
|
||||
Closes #8803
|
||||
Closes #6910
|
||||
Closes #5402
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.3.0-RC.0"></a>
|
||||
# 1.3.0-RC.0 sonic-boltification (2014-08-29)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:**
|
||||
- wait two until two digests are over until enabling animations
|
||||
([92576743](https://github.com/angular/angular.js/commit/92576743eec0cef5ffdd701b83f72a61e6489c3b),
|
||||
[#8844](https://github.com/angular/angular.js/issues/8844))
|
||||
- ensure guarded animations consider AJAX requests upon bootstrap
|
||||
([4bca4c44](https://github.com/angular/angular.js/commit/4bca4c44b95a7435722605a750804043f2960160),
|
||||
[#8275](https://github.com/angular/angular.js/issues/8275), [#5262](https://github.com/angular/angular.js/issues/5262))
|
||||
- use $timeout to handle the delay within staggering animations
|
||||
([23da6140](https://github.com/angular/angular.js/commit/23da614043fe5dcf0be132b86466eecb11c766a2),
|
||||
[#7228](https://github.com/angular/angular.js/issues/7228), [#7547](https://github.com/angular/angular.js/issues/7547), [#8297](https://github.com/angular/angular.js/issues/8297), [#8547](https://github.com/angular/angular.js/issues/8547))
|
||||
- **$browser:** detect changes to the browser url that happened in sync
|
||||
([3be00df4](https://github.com/angular/angular.js/commit/3be00df495f6eed3b3d9587ebab1fdd633e94e08),
|
||||
[#6976](https://github.com/angular/angular.js/issues/6976))
|
||||
- **$compile:** use the correct namespace for transcluded svg elements
|
||||
([cb73a37c](https://github.com/angular/angular.js/commit/cb73a37c7cae5cdebadf7b3ddd44c5a452495e4e),
|
||||
[#8808](https://github.com/angular/angular.js/issues/8808), [#8816](https://github.com/angular/angular.js/issues/8816))
|
||||
- **$location:** always resolve relative links in html5mode to `<base>` url
|
||||
([22948807](https://github.com/angular/angular.js/commit/22948807e324eb0b182b15b31045dc306a9f3231),
|
||||
[#8492](https://github.com/angular/angular.js/issues/8492), [#8172](https://github.com/angular/angular.js/issues/8172))
|
||||
- **$parse:** properly handle dots at the end of identifiers
|
||||
([8ac90357](https://github.com/angular/angular.js/commit/8ac90357a66ae0c62dbfe6db2c6eaf1d600ecc65),
|
||||
[#4613](https://github.com/angular/angular.js/issues/4613), [#4912](https://github.com/angular/angular.js/issues/4912), [#8559](https://github.com/angular/angular.js/issues/8559))
|
||||
- **Angular:** remove duplicate nodeName_ references
|
||||
([a4520a74](https://github.com/angular/angular.js/commit/a4520a745d917c77f1d12cdbce48272c643f7255))
|
||||
- **currencyFilter:** pass through null and undefined values
|
||||
([c2aaddbe](https://github.com/angular/angular.js/commit/c2aaddbe4b21348aab8c13a78cdd6aaee846ae4e),
|
||||
[#8605](https://github.com/angular/angular.js/issues/8605))
|
||||
- **docs:** don't throw exception on the 404 page
|
||||
([550ba01b](https://github.com/angular/angular.js/commit/550ba01b325fc29460030fc9c24fa00269dec2a9),
|
||||
[#8518](https://github.com/angular/angular.js/issues/8518))
|
||||
- **input:**
|
||||
- validate minlength/maxlength for non-string values
|
||||
([77ce5b89](https://github.com/angular/angular.js/commit/77ce5b89f97aa83c3eb1fe2e19375ef00a822015),
|
||||
[#7967](https://github.com/angular/angular.js/issues/7967), [#8811](https://github.com/angular/angular.js/issues/8811))
|
||||
- allow to use seconds in `input[time]` and `input[datetime-local]`
|
||||
([5f90340a](https://github.com/angular/angular.js/commit/5f90340abb78aa08dde4876328bcc00e46232e46))
|
||||
- use year 1970 instead of 1900 for `input[time]`
|
||||
([29f0b568](https://github.com/angular/angular.js/commit/29f0b568debab7810752969d363d337099e96cdc))
|
||||
- **ngBindHtml:** throw error if interpolation is used in expression
|
||||
([cd21602d](https://github.com/angular/angular.js/commit/cd21602d5b1650d8be373618cb7320d697e32c4d),
|
||||
[#8824](https://github.com/angular/angular.js/issues/8824))
|
||||
- **ngEventDirs:** execute `blur` and `focus` expression using `scope.$evalAsync`
|
||||
([719c747c](https://github.com/angular/angular.js/commit/719c747cd892ee933e7e414a7dc97e657b88317d),
|
||||
[#4979](https://github.com/angular/angular.js/issues/4979), [#5945](https://github.com/angular/angular.js/issues/5945), [#8803](https://github.com/angular/angular.js/issues/8803), [#6910](https://github.com/angular/angular.js/issues/6910), [#5402](https://github.com/angular/angular.js/issues/5402))
|
||||
- **ngModel:**
|
||||
- always format the viewValue as a string for text, url and email types
|
||||
([1eda1836](https://github.com/angular/angular.js/commit/1eda18365a348c9597aafba9d195d345e4f13d1e))
|
||||
- allow non-assignable binding when getterSetter is used
|
||||
([ab878a6c](https://github.com/angular/angular.js/commit/ab878a6c038f47b95f3a7e85a4fdb599e0c73e63),
|
||||
[#8704](https://github.com/angular/angular.js/issues/8704))
|
||||
- treat undefined parse responses as parse errors
|
||||
([db044c40](https://github.com/angular/angular.js/commit/db044c408a7f8082758b96ab739348810c36e15a))
|
||||
- **ngRepeat:** improve errors for duplicate items
|
||||
([0604bb7b](https://github.com/angular/angular.js/commit/0604bb7b7a6156e33679396e805e327662d9a178))
|
||||
- **ngSwitch:** avoid removing DOM nodes twice within watch operation
|
||||
([c9b0bfec](https://github.com/angular/angular.js/commit/c9b0bfecc99837af1c97792b3ca3408ba182b0bb),
|
||||
[#8662](https://github.com/angular/angular.js/issues/8662))
|
||||
- **numberFilter:** pass through null and undefined values
|
||||
([2ae10f67](https://github.com/angular/angular.js/commit/2ae10f67fcde3e172f695956301ef796b68a50c2),
|
||||
[#8605](https://github.com/angular/angular.js/issues/8605), [#8842](https://github.com/angular/angular.js/issues/8842))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **core:**
|
||||
- add angular.reloadWithDebugInfo()
|
||||
([41c1b88](https://github.com/angular/angular.js/commit/41c1b8858f02c7310bfabdd545ebb28e90eb4258))
|
||||
- **$animate:**
|
||||
- use promises instead of callbacks for animations
|
||||
([bf0f5502](https://github.com/angular/angular.js/commit/bf0f5502b1bbfddc5cdd2f138efd9188b8c652a9))
|
||||
- coalesce concurrent class-based animations within a digest loop
|
||||
([2f4437b3](https://github.com/angular/angular.js/commit/2f4437b3a149eafb899f25933bd6c713b167d10e))
|
||||
- **$compile:**
|
||||
- bind isolate scope properties to controller
|
||||
([5f3f25a1](https://github.com/angular/angular.js/commit/5f3f25a1a6f9d4f2a66e2700df3b9c5606f1c255),
|
||||
[#7635](https://github.com/angular/angular.js/issues/7635), [#7645](https://github.com/angular/angular.js/issues/7645))
|
||||
- allow disabling scope info
|
||||
([a1e5cd5f](https://github.com/angular/angular.js/commit/a1e5cd5fe3906ebee8c400247a1f793d3e2239fb))
|
||||
- **$compile/ngBind:** allow disabling binding info
|
||||
([3660fd09](https://github.com/angular/angular.js/commit/3660fd0912d3ccf6def8c9f02d8d4c0621c8d91f))
|
||||
- **$http:** implement mechanism for coalescing calls to $apply in $http
|
||||
([ea6fc6e6](https://github.com/angular/angular.js/commit/ea6fc6e69c2a2aa213c71ed4e917a0d54d064e4c),
|
||||
[#8736](https://github.com/angular/angular.js/issues/8736), [#7634](https://github.com/angular/angular.js/issues/7634), [#5297](https://github.com/angular/angular.js/issues/5297))
|
||||
- **$rootScope:** implement $applyAsync to support combining calls to $apply into a single digest.
|
||||
([e94d454b](https://github.com/angular/angular.js/commit/e94d454b840f6cc55a440741382b407836ad245b))
|
||||
- **$templateRequest:** introduce the $templateRequest service
|
||||
([a70e2833](https://github.com/angular/angular.js/commit/a70e2833ea276107b11aafea96ef4a6724ad4d83))
|
||||
- **filter:** allow to define the timezone for formatting dates
|
||||
([4739b1d9](https://github.com/angular/angular.js/commit/4739b1d9daebfd094b6181c5f2cb52ff71e31c61))
|
||||
- **filterFilter:** pass index to function predicate
|
||||
([46343c60](https://github.com/angular/angular.js/commit/46343c603db6192daf5303b92eb664749326c7e6),
|
||||
[#654](https://github.com/angular/angular.js/issues/654))
|
||||
- **input:** allow to define the timezone for parsing dates
|
||||
([cc6fc199](https://github.com/angular/angular.js/commit/cc6fc199f5abaacdf781aa03634337d776eb0fc9),
|
||||
[#8447](https://github.com/angular/angular.js/issues/8447))
|
||||
- **minErr:** allow specifying ErrorConstructor in minErr constructor
|
||||
([a6bd4bc8](https://github.com/angular/angular.js/commit/a6bd4bc866a18f860c7548fa1b3f6d4c2a953416))
|
||||
- **ngModel:** provide validation API functions for sync and async validations
|
||||
([2ae4f40b](https://github.com/angular/angular.js/commit/2ae4f40be1803d999ca2a8cc30ec17ff19ea6d86))
|
||||
- **ngRoute:** alias string as redirectTo property in .otherwise()
|
||||
([3b5d75c0](https://github.com/angular/angular.js/commit/3b5d75c021e21fa6ec4dc6c47b8eafa55680ea63),
|
||||
[#7794](https://github.com/angular/angular.js/issues/7794))
|
||||
- **testability:** add $$testability service
|
||||
([85880a64](https://github.com/angular/angular.js/commit/85880a64900fa22a61feb926bf52de0965332ca5))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:**
|
||||
- add debug classes in compile phase
|
||||
([e0489abd](https://github.com/angular/angular.js/commit/e0489abd8d9e4971ae23cc38805a92d227d1f3a1))
|
||||
- only iterate over elements with link functions
|
||||
([fdf9989f](https://github.com/angular/angular.js/commit/fdf9989f7cf1ed81982a788b75a338ac33334571),
|
||||
[#8741](https://github.com/angular/angular.js/issues/8741))
|
||||
- **nodeName_:** simplify the code and reduce the number of DOM calls
|
||||
([5a1a0c96](https://github.com/angular/angular.js/commit/5a1a0c96220101b5e040f0755e5eb401e2c73f65))
|
||||
- **select:** execute render after $digest cycle
|
||||
([6f7018d5](https://github.com/angular/angular.js/commit/6f7018d52fa4f9f9c7fa8e3035317d1239efb20f),
|
||||
[#8825](https://github.com/angular/angular.js/issues/8825))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$location**: due to [22948807](https://github.com/angular/angular.js/commit/22948807e324eb0b182b15b31045dc306a9f3231)
|
||||
|
||||
#### since 1.2.0 and 1.3.0-beta.1
|
||||
|
||||
Angular now requires a `<base>` tag when html5 mode of `$location` is enabled. Reasoning:
|
||||
Using html5 mode without a `<base href="...">` tag makes relative links for images, links, ...
|
||||
relative to the current url if the browser supports
|
||||
the history API. However, if the browser does not support the history API Angular falls back to using the `#`,
|
||||
and then all those relative links would be broken.
|
||||
|
||||
The `<base>` tag is also needed when a deep url is loaded from the server, e.g. `http://server/some/page/url`.
|
||||
In that case, Angular needs to decide which part of the url is the base of the application, and which part
|
||||
is path inside of the application.
|
||||
|
||||
To summarize: Now all relative links are always relative to the `<base>` tag.
|
||||
|
||||
Exception (also a breaking change):
|
||||
Link tags whose `href` attribute starts with a `#` will only change the hash of the url, but nothing else
|
||||
(e.g. `<a href="#someAnchor">`). This is to make it easy to scroll to anchors inside a document.
|
||||
|
||||
Related to #6162
|
||||
Closes #8492
|
||||
|
||||
#### since 1.2.17 and 1.3.0-beta.10
|
||||
|
||||
In html5 mode without a `<base>` tag on older browser that don't support the history API
|
||||
relative paths were adding up. E.g. clicking on `<a href="page1">` and then on `<a href="page2">`
|
||||
would produce `$location.path()==='/page1/page2'`. The code that introduced this behavior was removed
|
||||
and Angular now also requires a `<base>` tag to be present when using html5 mode.
|
||||
|
||||
Closes #8172, #8233
|
||||
|
||||
|
||||
- **ngInclude, ngMessage, ngView and directives that load templates**: due to [a70e2833](https://github.com/angular/angular.js/commit/a70e2833ea276107b11aafea96ef4a6724ad4d83)
|
||||
|
||||
Angular will now throw a $compile minErr each a template fails to download
|
||||
for ngView, directives and ngMessage template requests. This changes the former
|
||||
behavior of silently ignoring failed HTTP requests--or when the template itself
|
||||
is empty. Please ensure that all directive, ngView and ngMessage code now properly
|
||||
addresses this scenario. NgInclude is uneffected from this change.
|
||||
|
||||
|
||||
- **$animate**: due to [23da6140](https://github.com/angular/angular.js/commit/23da614043fe5dcf0be132b86466eecb11c766a2)
|
||||
|
||||
If any stagger code consisted of having BOTH transition staggers and delay staggers
|
||||
together then that will not work the same way. Angular will now instead choose
|
||||
the highest stagger delay value and set the timeout to wait for that before
|
||||
applying the active CSS class.
|
||||
|
||||
|
||||
- **$animate**: due to [bf0f5502](https://github.com/angular/angular.js/commit/bf0f5502b1bbfddc5cdd2f138efd9188b8c652a9)
|
||||
|
||||
Both the API for the cancallation method and the done callback for
|
||||
$animate animations is different. Instead of using a callback function
|
||||
for each of the $animate animation methods, a promise is used instead.
|
||||
|
||||
```js
|
||||
//before
|
||||
$animate.enter(element, container, null, callbackFn);
|
||||
|
||||
//after
|
||||
$animate.enter(element, container).then(callbackFn);
|
||||
```
|
||||
|
||||
The animation can now be cancelled via `$animate.cancel(promise)`.
|
||||
|
||||
```js
|
||||
//before
|
||||
var cancelFn = $animate.enter(element, container);
|
||||
cancelFn(); //cancels the animation
|
||||
|
||||
//after
|
||||
var promise = $animate.enter(element, container);
|
||||
$animate.cancel(promise); //cancels the animation
|
||||
```
|
||||
|
||||
keep in mind that you will still need to run $scope.$apply inside of the `then` callback
|
||||
to trigger a digest.
|
||||
|
||||
|
||||
- **$animate**: due to [2f4437b3](https://github.com/angular/angular.js/commit/2f4437b3a149eafb899f25933bd6c713b167d10e)
|
||||
|
||||
$animate.addClass, $animate.removeClass and $animate.setClass will no longer start the animation
|
||||
right after being called in the directive code. The animation will only commence once a digest
|
||||
has passed. This means that all animation-related testing code requires an extra digest to kick
|
||||
off the animation.
|
||||
|
||||
```js
|
||||
//before this fix
|
||||
$animate.addClass(element, 'super');
|
||||
expect(element).toHaveClass('super');
|
||||
|
||||
//now
|
||||
$animate.addClass(element, 'super');
|
||||
$rootScope.$digest();
|
||||
expect(element).toHaveClass('super');
|
||||
```
|
||||
|
||||
$animate will also tally the amount of times classes are added and removed and only animate
|
||||
the left over classes once the digest kicks in. This means that for any directive code that
|
||||
adds and removes the same CSS class on the same element then this may result in no animation
|
||||
being triggered at all.
|
||||
|
||||
```js
|
||||
$animate.addClass(element, 'klass');
|
||||
$animate.removeClass(element, 'klass');
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
//nothing happens...
|
||||
```
|
||||
|
||||
|
||||
- **$compile/ngBind:** due to [3660fd09](https://github.com/angular/angular.js/commit/3660fd0912d3ccf6def8c9f02d8d4c0621c8d91f),
|
||||
|
||||
The value of `$binding` data property on an element is always an array now
|
||||
and the expressions do not include the curly braces `{{ ... }}`.
|
||||
|
||||
|
||||
- **currencyFilter:** due to [c2aaddbe](https://github.com/angular/angular.js/commit/c2aaddbe4b21348aab8c13a78cdd6aaee846ae4e),
|
||||
previously the currency filter would convert null and undefined values into empty string, after this change
|
||||
these values will be passed through.
|
||||
|
||||
Only cases when the currency filter is chained with another filter that doesn't expect null/undefined will be affected. This
|
||||
should be very rare.
|
||||
|
||||
This change will not change the visual output of the filter because the interpolation will convert the null/undefined to
|
||||
an empty string.
|
||||
|
||||
Closes #8605
|
||||
|
||||
|
||||
- **numberFilter:** due to [2ae10f67](https://github.com/angular/angular.js/commit/2ae10f67fcde3e172f695956301ef796b68a50c2),
|
||||
previously the number filter would convert null and undefined values into empty string, after this change
|
||||
these values will be passed through.
|
||||
|
||||
Only cases when the number filter is chained with another filter that doesn't expect null/undefined will be affected. This
|
||||
should be very rare.
|
||||
|
||||
This change will not change the visual output of the filter because the interpolation will convert the null/undefined to
|
||||
an empty string.
|
||||
|
||||
Closes #8605
|
||||
Closes #8842
|
||||
|
||||
|
||||
- **input:**
|
||||
- due to [77ce5b89](https://github.com/angular/angular.js/commit/77ce5b89f97aa83c3eb1fe2e19375ef00a822015),
|
||||
|
||||
NgModel.viewValue will always be used when rendering validations for `minlength` and `maxlength`.
|
||||
|
||||
Closes #7967
|
||||
Closes #8811
|
||||
|
||||
- **input:**
|
||||
- due to [29f0b568](https://github.com/angular/angular.js/commit/29f0b568debab7810752969d363d337099e96cdc),
|
||||
|
||||
|
||||
According to the HTML5 spec `input[time]` should create dates
|
||||
based on the year 1970 (used to be based on the year 1900).
|
||||
|
||||
Related to #8447.
|
||||
|
||||
|
||||
- **ngModel**: due to [db044c40](https://github.com/angular/angular.js/commit/db044c408a7f8082758b96ab739348810c36e15a)
|
||||
|
||||
Any parser code from before that returned an `undefined` value
|
||||
(or nothing at all) will now cause a parser failure. When this occurs
|
||||
none of the validators present in `$validators` will run until the parser
|
||||
error is gone. The error will be stored on `ngModel.$error`.
|
||||
|
||||
|
||||
|
||||
|
||||
- **ngEventDirs:** due to [719c747c](https://github.com/angular/angular.js/commit/719c747cd892ee933e7e414a7dc97e657b88317d),
|
||||
|
||||
The `blur` and `focus` event fire synchronously, also during DOM operations
|
||||
that remove elements. This lead to errors as the Angular model was not
|
||||
in a consistent state. See this [fiddle](http://jsfiddle.net/fq1dq5yb/) for a demo.
|
||||
|
||||
This change executes the expression of those events using
|
||||
`scope.$evalAsync` if an `$apply` is in progress, otherwise
|
||||
keeps the old behavior.
|
||||
|
||||
Fixes #4979
|
||||
Fixes #5945
|
||||
Closes #8803
|
||||
Closes #6910
|
||||
Closes #5402
|
||||
|
||||
|
||||
|
||||
<a name="1.2.23"></a>
|
||||
# 1.2.23 superficial-malady (2014-08-22)
|
||||
|
||||
@@ -1668,6 +2214,13 @@ jQuery. We don't expect that app code actually depends on this accidental featur
|
||||
application if 100s of elements are being inserted into the page. Therefore after this
|
||||
change callbacks are only fired if registered on the element being animated.
|
||||
|
||||
- **input:**
|
||||
- due to [a9fcb0d0](https://github.com/angular/angular.js/commit/a9fcb0d0fc6456f80501b8820d02b04d7c15b6d6),
|
||||
input[type=file] will no longer support ngModel. Due to browser support being spotty among target browsers,
|
||||
file inputs cannot be cleanly supported, and even features which technically do work (such as ng-change)
|
||||
work in an inconsistent way depending on the attributes of the form control.
|
||||
|
||||
As a workaround, one can manually listen for change events on file inputs and handle them manually.
|
||||
|
||||
<a name="1.2.12"></a>
|
||||
# 1.2.12 cauliflower-eradication (2014-02-07)
|
||||
|
||||
@@ -15,7 +15,7 @@ it makes development fun!
|
||||
* Tutorial: http://docs.angularjs.org/tutorial
|
||||
* API Docs: http://docs.angularjs.org/api
|
||||
* Developer Guide: http://docs.angularjs.org/guide
|
||||
* Contribution guidelines: http://docs.angularjs.org/misc/contribute
|
||||
* Contribution guidelines: [CONTRIBUTING.md](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md)
|
||||
* Dashboard: http://dashboard.angularjs.org
|
||||
|
||||
Building AngularJS
|
||||
@@ -37,7 +37,7 @@ To execute end-to-end (e2e) tests, use:
|
||||
grunt test:e2e
|
||||
|
||||
To learn more about the grunt tasks, run `grunt --help` and also read our
|
||||
[contribution guidelines](http://docs.angularjs.org/misc/contribute).
|
||||
[contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).
|
||||
|
||||
|
||||
[](https://github.com/igrigorik/ga-beacon)
|
||||
|
||||
+1
-5
@@ -2,11 +2,7 @@
|
||||
"name": "AngularJS",
|
||||
"devDependencies": {
|
||||
"jquery": "1.10.2",
|
||||
"lunr.js": "0.4.3",
|
||||
"open-sans-fontface": "1.0.4",
|
||||
"google-code-prettify": "1.0.1",
|
||||
"closure-compiler": "https://closure-compiler.googlecode.com/files/compiler-20130603.zip",
|
||||
"ng-closure-runner": "https://raw.github.com/angular/ng-closure-runner/v0.2.3/assets/ng-closure-runner.zip",
|
||||
"bootstrap": "3.1.1"
|
||||
"ng-closure-runner": "https://raw.github.com/angular/ng-closure-runner/v0.2.3/assets/ng-closure-runner.zip"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,12 +635,14 @@ ul.events > li {
|
||||
display:inline-block;
|
||||
padding:3px 0;
|
||||
}
|
||||
.nav-index-group .nav-index-listing:not(.nav-index-section) + .nav-index-listing:not(.nav-index-section):after {
|
||||
padding-right:5px;
|
||||
content:", ";
|
||||
.nav-index-group .nav-index-listing:not(.nav-index-section):after {
|
||||
padding-right:5px;
|
||||
margin-left:-3px;
|
||||
content:", ";
|
||||
}
|
||||
.nav-index-group .nav-index-listing:last-child {
|
||||
.nav-index-group .nav-index-listing:last-child:after {
|
||||
content:"";
|
||||
display:inline-block;
|
||||
}
|
||||
.nav-index-group .nav-index-section {
|
||||
display:block;
|
||||
|
||||
@@ -74,6 +74,7 @@ angular.module('search', [])
|
||||
var index = lunrSearch(function() {
|
||||
this.ref('id');
|
||||
this.field('title', {boost: 50});
|
||||
this.field('members', { boost: 40});
|
||||
this.field('keywords', { boost : 20 });
|
||||
});
|
||||
|
||||
@@ -82,7 +83,8 @@ angular.module('search', [])
|
||||
index.store({
|
||||
id : key,
|
||||
title : page.searchTerms.titleWords,
|
||||
keywords : page.searchTerms.keywords
|
||||
keywords : page.searchTerms.keywords,
|
||||
members : page.searchTerms.members
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "AngularJS-docs-app",
|
||||
"dependencies": {
|
||||
"jquery": "2.1.1",
|
||||
"lunr.js": "0.4.3",
|
||||
"open-sans-fontface": "1.0.4",
|
||||
"google-code-prettify": "1.0.1",
|
||||
"bootstrap": "3.1.1"
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,8 @@ module.exports = function(config) {
|
||||
]);
|
||||
|
||||
config.append('processing.tagDefinitions', [
|
||||
require('./tag-defs/tutorial-step')
|
||||
require('./tag-defs/tutorial-step'),
|
||||
require('./tag-defs/sortOrder')
|
||||
]);
|
||||
|
||||
config.append('processing.defaultTagTransforms', [
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
"use strict";
|
||||
var _ = require('lodash');
|
||||
var log = require('winston');
|
||||
var fs = require('fs');
|
||||
@@ -5,7 +6,7 @@ var path = require('canonical-path');
|
||||
|
||||
module.exports = {
|
||||
name: 'keywords',
|
||||
runAfter: ['docs-processed'],
|
||||
runAfter: ['docs-processed', 'api-docs'],
|
||||
runBefore: ['adding-extra-docs'],
|
||||
description: 'This processor extracts all the keywords from the document',
|
||||
process: function(docs, config) {
|
||||
@@ -52,7 +53,7 @@ module.exports = {
|
||||
_.forEach(tokens, function(token){
|
||||
var match = token.match(KEYWORD_REGEX);
|
||||
if (match){
|
||||
key = match[1];
|
||||
var key = match[1];
|
||||
if ( !keywordMap[key]) {
|
||||
keywordMap[key] = true;
|
||||
words.push(key);
|
||||
@@ -69,17 +70,28 @@ module.exports = {
|
||||
|
||||
var words = [];
|
||||
var keywordMap = _.clone(ignoreWordsMap);
|
||||
var members = [];
|
||||
var membersMap = {};
|
||||
|
||||
// Search each top level property of the document for search terms
|
||||
_.forEach(doc, function(value, key) {
|
||||
|
||||
if ( _.isString(value) && !propertiesToIgnore[key] ) {
|
||||
extractWords(value, words, keywordMap);
|
||||
}
|
||||
|
||||
if ( key === 'methods' || key === 'properties' || key === 'events' ) {
|
||||
_.forEach(value, function(member) {
|
||||
extractWords(member.name, members, membersMap);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
doc.searchTerms = {
|
||||
titleWords: extractTitleWords(doc.name),
|
||||
keywords: _.sortBy(words).join(' ')
|
||||
keywords: _.sortBy(words).join(' '),
|
||||
members: _.sortBy(members).join(' ')
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -119,13 +119,20 @@ var navGroupMappers = {
|
||||
})];
|
||||
},
|
||||
pages: function(pages, area) {
|
||||
return [getNavGroup(pages, area, 'path', function(page) {
|
||||
return {
|
||||
name: page.name,
|
||||
href: page.path,
|
||||
type: 'page'
|
||||
};
|
||||
})];
|
||||
return [getNavGroup(
|
||||
pages,
|
||||
area,
|
||||
function(page) {
|
||||
return page.sortOrder || page.path;
|
||||
},
|
||||
function(page) {
|
||||
return {
|
||||
name: page.name,
|
||||
href: page.path,
|
||||
type: 'page'
|
||||
};
|
||||
}
|
||||
)];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
name: 'sortOrder',
|
||||
transforms: function(doc, tag, value) {
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
};
|
||||
@@ -10,7 +10,7 @@ Angular's {@link ng.$sce Strict Contextual Escaping (SCE)} mode
|
||||
contexts to result in a value that is trusted as safe for use in such a context. (e.g. loading an
|
||||
Angular template from a URL requires that the URL is one considered safe for loading resources.)
|
||||
|
||||
This helps prevent XSS and other security issues. Read more at {@link
|
||||
api/ng.$sce Strict Contextual Escaping (SCE)}
|
||||
This helps prevent XSS and other security issues. Read more at
|
||||
{@link ng.$sce Strict Contextual Escaping (SCE)}
|
||||
|
||||
You may want to include the ngSanitize module to use the automatic sanitizing.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Using $location
|
||||
@sortOrder 500
|
||||
@description
|
||||
|
||||
# What does it do?
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Animations
|
||||
@sortOrder 310
|
||||
@description
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Bootstrap
|
||||
@sortOrder 350
|
||||
@description
|
||||
|
||||
# Bootstrap
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name HTML Compiler
|
||||
@sortOrder 330
|
||||
@description
|
||||
|
||||
<div class="alert alert-warning">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Conceptual Overview
|
||||
@sortOrder 200
|
||||
@description
|
||||
|
||||
# Conceptual Overview
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Controllers
|
||||
@sortOrder 220
|
||||
@description
|
||||
|
||||
# Understanding Controllers
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Working With CSS
|
||||
@sortOrder 510
|
||||
@description
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Data Binding
|
||||
@sortOrder 210
|
||||
@description
|
||||
|
||||
Data-binding in Angular apps is the automatic synchronization of data between the model and view
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Dependency Injection
|
||||
@sortOrder 250
|
||||
@description
|
||||
|
||||
# Dependency Injection
|
||||
@@ -109,7 +110,7 @@ asks the injector to create an instance of the controller and its dependencies.
|
||||
injector.instantiate(MyController);
|
||||
```
|
||||
|
||||
This is all done behinds the scenes. Notice that by having the `ng-controller` ask the injector to
|
||||
This is all done behind the scenes. Notice that by having the `ng-controller` ask the injector to
|
||||
instantiate the class, it can satisfy all of the dependencies of `MyController` without the
|
||||
controller ever knowing about the injector.
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Directives
|
||||
@sortOrder 300
|
||||
@description
|
||||
|
||||
# Creating Custom Directives
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name E2E Testing
|
||||
@sortOrder 420
|
||||
@description
|
||||
|
||||
# E2E Testing
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Expressions
|
||||
@sortOrder 270
|
||||
@description
|
||||
|
||||
# Angular Expressions
|
||||
@@ -38,7 +39,9 @@ the method from your view. If you want to `eval()` an Angular expression yoursel
|
||||
## Example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
1+2={{1+2}}
|
||||
<span>
|
||||
1+2={{1+2}}
|
||||
</span>
|
||||
</file>
|
||||
|
||||
<file name="protractor.js" type="protractor">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Filters
|
||||
@sortOrder 280
|
||||
@description
|
||||
|
||||
A filter formats the value of an expression for display to the user. They can be used in view templates,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Forms
|
||||
@sortOrder 290
|
||||
@description
|
||||
|
||||
Controls (`input`, `select`, `textarea`) are ways for a user to enter data.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name i18n and l10n
|
||||
@sortOrder 520
|
||||
@description
|
||||
|
||||
# i18n and l10n
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Internet Explorer Compatibility
|
||||
@sortOrder 530
|
||||
@description
|
||||
|
||||
# Internet Explorer Compatibility
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Introduction
|
||||
@sortOrder 100
|
||||
@description
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Migrating from 1.0 to 1.2
|
||||
@sortOrder 550
|
||||
@description
|
||||
|
||||
# Migrating from 1.0 to 1.2
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Modules
|
||||
@sortOrder 320
|
||||
@description
|
||||
|
||||
# What is a Module?
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Providers
|
||||
@sortOrder 340
|
||||
@description
|
||||
|
||||
# Providers
|
||||
@@ -97,9 +98,8 @@ created by this recipe.
|
||||
Note: All services in Angular are singletons. That means that the injector uses each recipe at most
|
||||
once to create the object. The injector then caches the reference for all future needs.
|
||||
|
||||
Since Factory is more a powerful version of the Value recipe, you can construct the same service with it.
|
||||
Using our previous `clientId` Value recipe example, we can rewrite it as a Factory recipe like
|
||||
this:
|
||||
Since a Factory is a more powerful version of the Value recipe, the same service can be constructed with it.
|
||||
Using our previous `clientId` Value recipe example, we can rewrite it as a Factory recipe like this:
|
||||
|
||||
```javascript
|
||||
myApp.factory('clientId', function clientIdFactory() {
|
||||
@@ -134,11 +134,11 @@ token.
|
||||
|
||||
<div class="alert alert-success">
|
||||
**Best Practice:** name the factory functions as `<serviceId>Factory`
|
||||
(e.g. apiTokenFactory). While this naming convention is not required, it helps when navigating the code base
|
||||
(e.g., apiTokenFactory). While this naming convention is not required, it helps when navigating the codebase
|
||||
or looking at stack traces in the debugger.
|
||||
</div>
|
||||
|
||||
Just like with Value recipe, Factory recipe can create a service of any type, whether it be a
|
||||
Just like with the Value recipe, the Factory recipe can create a service of any type, whether it be a
|
||||
primitive, object literal, function, or even an instance of a custom type.
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ function UnicornLauncher(apiToken) {
|
||||
|
||||
this.launchedCount = 0;
|
||||
this.launch = function() {
|
||||
// make a request to the remote api and include the apiToken
|
||||
// Make a request to the remote API and include the apiToken
|
||||
...
|
||||
this.launchedCount++;
|
||||
}
|
||||
@@ -170,7 +170,7 @@ myApp.factory('unicornLauncher', ["apiToken", function(apiToken) {
|
||||
```
|
||||
|
||||
|
||||
This is, however, exactly the use-case that Service recipe is the most suitable for.
|
||||
This is, however, exactly the use-case that the Service recipe is the most suitable for.
|
||||
|
||||
The Service recipe produces a service just like the Value or Factory recipes, but it does so by
|
||||
*invoking a constructor with the `new` operator*. The constructor can take zero or more arguments,
|
||||
@@ -189,7 +189,7 @@ myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);
|
||||
Much simpler!
|
||||
|
||||
Note: Yes, we have called one of our service recipes 'Service'. We regret this and know that we'll
|
||||
be somehow punished for our mis-deed. It's like we named one of our offspring 'Child'. Boy,
|
||||
be somehow punished for our misdeed. It's like we named one of our offspring 'Child'. Boy,
|
||||
that would mess with the teachers.
|
||||
|
||||
|
||||
@@ -199,8 +199,8 @@ As already mentioned in the intro, the Provider recipe is the core recipe type a
|
||||
all the other recipe types are just syntactic sugar on top of it. It is the most verbose recipe
|
||||
with the most abilities, but for most services it's overkill.
|
||||
|
||||
Provider recipe is syntactically defined as a custom type that implements a `$get` method. This
|
||||
method is a factory function just like the one we use in Factory recipe. In fact, if you define
|
||||
The Provider recipe is syntactically defined as a custom type that implements a `$get` method. This
|
||||
method is a factory function just like the one we use in the Factory recipe. In fact, if you define
|
||||
a Factory recipe, an empty Provider type with the `$get` method set to your factory function is
|
||||
automatically created under the hood.
|
||||
|
||||
@@ -248,7 +248,7 @@ and wires (injects) all provider instances only.
|
||||
|
||||
During application bootstrap, before Angular goes off creating all services, it configures and
|
||||
instantiates all providers. We call this the configuration phase of the application life-cycle.
|
||||
During this phase services aren't accessible because they haven't been created yet.
|
||||
During this phase, services aren't accessible because they haven't been created yet.
|
||||
|
||||
Once the configuration phase is over, interaction with providers is disallowed and the process of
|
||||
creating services starts. We call this part of the application life-cycle the run phase.
|
||||
@@ -259,9 +259,9 @@ creating services starts. We call this part of the application life-cycle the ru
|
||||
We've just learned how Angular splits the life-cycle into configuration phase and run phase and how
|
||||
you can provide configuration to your application via the config function. Since the config
|
||||
function runs in the configuration phase when no services are available, it doesn't have access
|
||||
even to simple value objects created via Value recipe.
|
||||
even to simple value objects created via the Value recipe.
|
||||
|
||||
Since simple values, like url prefix, don't have dependencies or configuration, it is often handy
|
||||
Since simple values, like URL prefixes, don't have dependencies or configuration, it's often handy
|
||||
to make them available in both the configuration and run phases. This is what the Constant recipe
|
||||
is for.
|
||||
|
||||
@@ -317,7 +317,7 @@ Let's take a look at how we would create a very simple component via the directi
|
||||
on the `planetName` constant we've just defined and displays the planet name, in our case:
|
||||
"Planet Name: Greasy Giant".
|
||||
|
||||
Since the directives are registered via Factory recipe, we can use the same syntax as with factories.
|
||||
Since the directives are registered via the Factory recipe, we can use the same syntax as with factories.
|
||||
|
||||
```javascript
|
||||
myApp.directive('myPlanet', ['planetName', function myPlanetDirectiveFactory(planetName) {
|
||||
@@ -340,7 +340,7 @@ We can then use the component like this:
|
||||
</html>
|
||||
```
|
||||
|
||||
Using Factory recipes you can also define Angular's filters and animations, but the controllers
|
||||
Using Factory recipes, you can also define Angular's filters and animations, but the controllers
|
||||
are a bit special. You create a controller as a custom type that declares its dependencies as
|
||||
arguments for its constructor function. This constructor is then registered with a module. Let's
|
||||
take a look at the `DemoController`, created in one of the early examples:
|
||||
@@ -351,7 +351,7 @@ myApp.controller('DemoController', ['clientId', function DemoController(clientId
|
||||
}]);
|
||||
```
|
||||
|
||||
The DemoController is instantiated via its constructor every time the app needs an instance of
|
||||
The DemoController is instantiated via its constructor, every time the app needs an instance of
|
||||
DemoController (in our simple app it's just once). So unlike services, controllers are not
|
||||
singletons. The constructor is called with all the requested services, in our case the `clientId`
|
||||
service.
|
||||
@@ -365,12 +365,12 @@ To wrap it up, let's summarize the most important points:
|
||||
- There are five recipe types that define how to create objects: Value, Factory, Service, Provider
|
||||
and Constant.
|
||||
- Factory and Service are the most commonly used recipes. The only difference between them is that
|
||||
Service recipe works better for objects of custom type, while Factory can produce JavaScript
|
||||
the Service recipe works better for objects of a custom type, while the Factory can produce JavaScript
|
||||
primitives and functions.
|
||||
- The Provider recipe is the core recipe type and all the other ones are just syntactic sugar on it.
|
||||
- Provider is the most complex recipe type. You don't need it unless you are building a reusable
|
||||
piece of code that needs global configuration.
|
||||
- All special purpose objects except for Controller are defined via Factory recipes.
|
||||
- All special purpose objects except for the Controller are defined via Factory recipes.
|
||||
|
||||
<table class="table table-bordered code-table">
|
||||
<thead>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Scopes
|
||||
@sortOrder 240
|
||||
@description
|
||||
|
||||
# What are Scopes?
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Services
|
||||
@sortOrder 230
|
||||
@description
|
||||
|
||||
# Services
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Templates
|
||||
@sortOrder 260
|
||||
@description
|
||||
|
||||
In Angular, templates are written with HTML that contains Angular-specific elements and attributes.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@ngdoc overview
|
||||
@name Unit Testing
|
||||
@sortOrder 410
|
||||
@description
|
||||
|
||||
JavaScript is a dynamically typed language which comes with great power of expression, but it also
|
||||
|
||||
@@ -250,6 +250,8 @@ browser is limited, which results in your karma tests running extremely slow.
|
||||
<tr><th>row number</th></tr>
|
||||
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
|
||||
</table>
|
||||
|
||||
Extra points: try and make an 8x8 table using an additional ng-repeat.
|
||||
|
||||
* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use `toBe(4)`.
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ Because we started using dependency injection and our controller has dependencie
|
||||
controller in our tests is a bit more complicated. We could use the `new` operator and provide the
|
||||
constructor with some kind of fake `$http` implementation. However, Angular provides a mock `$http`
|
||||
service that we can use in unit tests. We configure "fake" responses to server requests by calling
|
||||
methods on a service called $httpBackend:
|
||||
methods on a service called `$httpBackend`:
|
||||
|
||||
```js
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
+5
-2
@@ -1,3 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var path = require('canonical-path');
|
||||
var versionInfo = require('../lib/versions/version-info');
|
||||
var basePath = __dirname;
|
||||
@@ -9,9 +11,10 @@ module.exports = function(config) {
|
||||
var cdnUrl = "//ajax.googleapis.com/ajax/libs/angularjs/" + versionInfo.cdnVersion;
|
||||
|
||||
var getVersion = function(component, sourceFolder, packageFile) {
|
||||
sourceFolder = sourceFolder || '../bower_components';
|
||||
sourceFolder = sourceFolder || './bower_components';
|
||||
packageFile = packageFile || 'bower.json';
|
||||
return require(path.join(sourceFolder,component,packageFile)).version;
|
||||
|
||||
return require(path.resolve(sourceFolder,component,packageFile)).version;
|
||||
};
|
||||
|
||||
|
||||
|
||||
+13
-3
@@ -1,4 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
var gulp = require('gulp');
|
||||
var log = require('gulp-util').log;
|
||||
var concat = require('gulp-concat');
|
||||
var jshint = require('gulp-jshint');
|
||||
var bower = require('bower');
|
||||
@@ -12,7 +15,7 @@ var path = require('canonical-path');
|
||||
// See clean and bower for async tasks, and see assets and doc-gen for dependent tasks below
|
||||
|
||||
var outputFolder = '../build/docs';
|
||||
var bowerFolder = '../bower_components';
|
||||
var bowerFolder = 'bower_components';
|
||||
|
||||
|
||||
var copyComponent = function(component, pattern, sourceFolder, packageFile) {
|
||||
@@ -26,7 +29,14 @@ var copyComponent = function(component, pattern, sourceFolder, packageFile) {
|
||||
};
|
||||
|
||||
gulp.task('bower', function() {
|
||||
return bower.commands.install();
|
||||
var bowerTask = bower.commands.install();
|
||||
bowerTask.on('log', function (result) {
|
||||
log('bower:', result.id, result.data.endpoint.name);
|
||||
});
|
||||
bowerTask.on('error', function(error) {
|
||||
log(error);
|
||||
});
|
||||
return bowerTask;
|
||||
});
|
||||
|
||||
gulp.task('build-app', function() {
|
||||
@@ -48,7 +58,7 @@ gulp.task('assets', ['bower'], function() {
|
||||
});
|
||||
|
||||
|
||||
gulp.task('doc-gen', function() {
|
||||
gulp.task('doc-gen', ['bower'], function() {
|
||||
var generateDocs = dgeni.generator('docs.config.js');
|
||||
return generateDocs()
|
||||
.catch(function(error) {
|
||||
|
||||
Generated
+1770
-604
File diff suppressed because it is too large
Load Diff
+31
-25
@@ -6,11 +6,20 @@
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-benchpress": "0.x.x",
|
||||
"benchmark": "1.x.x",
|
||||
"bower": "~1.3.9",
|
||||
"browserstacktunnel-wrapper": "~1.1.1",
|
||||
"canonical-path": "0.0.2",
|
||||
"dgeni": "^0.3.0",
|
||||
"dgeni-packages": "^0.9.8",
|
||||
"es6-shim": "^0.14.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"grunt": "~0.4.2",
|
||||
"grunt-bump": "~0.0.13",
|
||||
"grunt-contrib-clean": "~0.5.0",
|
||||
"grunt-contrib-connect": "~0.5.0",
|
||||
"grunt-contrib-compress": "~0.5.2",
|
||||
"grunt-contrib-connect": "~0.5.0",
|
||||
"grunt-contrib-copy": "~0.4.1",
|
||||
"grunt-contrib-jshint": "~0.10.0",
|
||||
"grunt-ddescribe-iit": "~0.0.1",
|
||||
@@ -19,41 +28,38 @@
|
||||
"grunt-merge-conflict": "~0.0.1",
|
||||
"grunt-parallel": "~0.3.1",
|
||||
"grunt-shell": "~0.4.0",
|
||||
"load-grunt-tasks": "~0.3.0",
|
||||
"bower": "~1.2.2",
|
||||
"gulp": "~3.8.0",
|
||||
"gulp-concat": "~2.1.7",
|
||||
"gulp-jshint": "~1.4.2",
|
||||
"gulp-util": "^3.0.1",
|
||||
"jasmine-node": "~1.11.0",
|
||||
"q": "~1.0.0",
|
||||
"shelljs": "~0.2.6",
|
||||
"jasmine-reporters": "~0.2.1",
|
||||
"jshint-stylish": "~0.1.5",
|
||||
"karma": "^0.12.0",
|
||||
"karma-jasmine": "0.1.5",
|
||||
"karma-browserstack-launcher": "0.0.7",
|
||||
"karma-chrome-launcher": "0.1.2",
|
||||
"karma-firefox-launcher": "0.1.3",
|
||||
"karma-ng-scenario": "0.1.0",
|
||||
"karma-jasmine": "0.1.5",
|
||||
"karma-junit-reporter": "0.2.1",
|
||||
"karma-ng-scenario": "0.1.0",
|
||||
"karma-sauce-launcher": "0.2.0",
|
||||
"karma-script-launcher": "0.1.0",
|
||||
"karma-browserstack-launcher": "0.0.7",
|
||||
"protractor": "1.0.0",
|
||||
"yaml-js": "~0.0.8",
|
||||
"rewire": "1.1.3",
|
||||
"promises-aplus-tests": "~1.3.2",
|
||||
"semver": "~2.1.0",
|
||||
"load-grunt-tasks": "~0.3.0",
|
||||
"lodash": "~2.1.0",
|
||||
"browserstacktunnel-wrapper": "~1.1.1",
|
||||
"jasmine-reporters": "~0.2.1",
|
||||
"gulp": "~3.4.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"marked": "~0.3.0",
|
||||
"gulp-concat": "~2.1.7",
|
||||
"canonical-path": "0.0.2",
|
||||
"winston": "~0.7.2",
|
||||
"dgeni": "^0.3.0",
|
||||
"dgeni-packages": "^0.9.7",
|
||||
"gulp-jshint": "~1.4.2",
|
||||
"jshint-stylish": "~0.1.5",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"promises-aplus-tests": "~1.3.2",
|
||||
"protractor": "1.2.0",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
"rewire": "1.1.3",
|
||||
"semver": "~2.1.0",
|
||||
"shelljs": "~0.2.6",
|
||||
"sorted-object": "^1.0.0",
|
||||
"qq": "^0.3.5"
|
||||
"stringmap": "^0.2.2",
|
||||
"winston": "~0.7.2",
|
||||
"yaml-js": "~0.0.8"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
|
||||
@@ -23,9 +23,6 @@ function cleanModule(module, name) {
|
||||
if (name === 'chokidar') {
|
||||
if (module.version === '0.8.1') {
|
||||
delete module.dependencies;
|
||||
} else if ( module.version !== '0.8.2') {
|
||||
throw new Error("Unfamiliar chokidar version (v" + module.version +
|
||||
") , please check status of https://github.com/paulmillr/chokidar/pull/106");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -234,6 +234,13 @@ function Browser(window, document, $log, $sniffer) {
|
||||
return callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the url has changed outside of Angular.
|
||||
* Needs to be exported to be able to check for changes that have been done in sync,
|
||||
* as hashchange/popstate events fire in async.
|
||||
*/
|
||||
self.$$checkUrlChange = fireUrlChange;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Misc API
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -70,8 +70,9 @@ function FormController(element, attrs, $scope, $animate) {
|
||||
// convenience method for easy toggling of classes
|
||||
function toggleValidCss(isValid, validationErrorKey) {
|
||||
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
|
||||
$animate.removeClass(element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey);
|
||||
$animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
|
||||
$animate.setClass(element,
|
||||
(isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey,
|
||||
(isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,8 +287,6 @@ function FormController(element, attrs, $scope, $animate) {
|
||||
* hitting enter in any of the input fields will trigger the click handler on the *first* button or
|
||||
* input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
|
||||
*
|
||||
* @param {string=} name Name of the form. If specified, the form controller will be published into
|
||||
* related scope, under this name.
|
||||
*
|
||||
* ## Animation Hooks
|
||||
*
|
||||
@@ -365,6 +364,8 @@ function FormController(element, attrs, $scope, $animate) {
|
||||
</file>
|
||||
</example>
|
||||
*
|
||||
* @param {string=} name Name of the form. If specified, the form controller will be published into
|
||||
* related scope, under this name.
|
||||
*/
|
||||
var formDirectiveFactory = function(isNgForm) {
|
||||
return ['$timeout', function($timeout) {
|
||||
|
||||
@@ -522,7 +522,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
// a row.
|
||||
var revalidate = validity && ctrl.$$hasNativeValidators;
|
||||
if (ctrl.$viewValue !== value || (value === '' && revalidate)) {
|
||||
if (scope.$$phase) {
|
||||
if (scope.$root.$$phase) {
|
||||
ctrl.$setViewValue(value);
|
||||
} else {
|
||||
scope.$apply(function() {
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
<button ng-click="count = count + 1" ng-init="count=0">
|
||||
Increment
|
||||
</button>
|
||||
count: {{count}}
|
||||
<span>
|
||||
count: {{count}}
|
||||
<span>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should check ng-click', function() {
|
||||
@@ -37,19 +39,33 @@
|
||||
* Events that are handled via these handler are always configured not to propagate further.
|
||||
*/
|
||||
var ngEventDirectives = {};
|
||||
|
||||
// For events that might fire synchronously during DOM manipulation
|
||||
// we need to execute their event handlers asynchronously using $evalAsync,
|
||||
// so that they are not executed in an inconsistent state.
|
||||
var forceAsyncEvents = {
|
||||
'blur': true,
|
||||
'focus': true
|
||||
};
|
||||
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) {
|
||||
ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
|
||||
return {
|
||||
compile: function($element, attr) {
|
||||
var fn = $parse(attr[directiveName]);
|
||||
return function ngEventHandler(scope, element) {
|
||||
element.on(lowercase(name), function(event) {
|
||||
scope.$apply(function() {
|
||||
var eventName = lowercase(name);
|
||||
element.on(eventName, function(event) {
|
||||
var callback = function() {
|
||||
fn(scope, {$event:event});
|
||||
});
|
||||
};
|
||||
if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
|
||||
scope.$evalAsync(callback);
|
||||
} else {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -366,6 +382,10 @@ forEach(
|
||||
* @description
|
||||
* Specify custom behavior on focus event.
|
||||
*
|
||||
* Note: As the `focus` event is executed synchronously when calling `input.focus()`
|
||||
* AngularJS executes the expression using `scope.$evalAsync` if the event is fired
|
||||
* during an `$apply` to ensure a consistent state.
|
||||
*
|
||||
* @element window, input, select, textarea, a
|
||||
* @priority 0
|
||||
* @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
|
||||
@@ -382,6 +402,14 @@ forEach(
|
||||
* @description
|
||||
* Specify custom behavior on blur event.
|
||||
*
|
||||
* A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
|
||||
* an element has lost focus.
|
||||
*
|
||||
* Note: As the `blur` event is executed synchronously also during DOM manipulations
|
||||
* (e.g. removing a focussed input),
|
||||
* AngularJS executes the expression using `scope.$evalAsync` if the event is fired
|
||||
* during an `$apply` to ensure a consistent state.
|
||||
*
|
||||
* @element window, input, select, textarea, a
|
||||
* @priority 0
|
||||
* @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
|
||||
|
||||
@@ -309,8 +309,9 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
if (block && block.scope) lastBlockMap[block.id] = block;
|
||||
});
|
||||
// This is a duplicate and we need to throw an error
|
||||
throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}",
|
||||
expression, trackById);
|
||||
throw ngRepeatMinErr('dupes',
|
||||
"Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
|
||||
expression, trackById, toJson(value));
|
||||
} else {
|
||||
// new never before seen block
|
||||
nextBlockOrder[index] = { id: trackById };
|
||||
|
||||
+1
-11
@@ -60,16 +60,6 @@
|
||||
* For more information about how angular filters work, and how to create your own filters, see
|
||||
* {@link guide/filter Filters} in the Angular Developer Guide.
|
||||
*/
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $filterProvider#register
|
||||
* @description
|
||||
* Register filter factory function.
|
||||
*
|
||||
* @param {String} name Name of the filter.
|
||||
* @param {Function} fn The filter factory function which is injectable.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
@@ -108,7 +98,7 @@ function $FilterProvider($provide) {
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $controllerProvider#register
|
||||
* @name $filterProvider#register
|
||||
* @param {string|Object} name Name of the filter function, or an object map of filters where
|
||||
* the keys are the filter names and the values are the filter factories.
|
||||
* @returns {Object} Registered filter instance, or if a map of filters was provided then a map
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
* which have property `name` containing "M" and property `phone` containing "1". A special
|
||||
* property name `$` can be used (as in `{$:"text"}`) to accept a match against any
|
||||
* property of the object. That's equivalent to the simple substring match with a `string`
|
||||
* as described above.
|
||||
* as described above. The predicate can be negated by prefixing the string with `!`.
|
||||
* For Example `{name: "!M"}` predicate will return an array of items which have property `name`
|
||||
* not containing "M".
|
||||
*
|
||||
* - `function(value)`: A predicate function can be used to write arbitrary filters. The function is
|
||||
* called for each element of `array`. The final result is an array of those elements that
|
||||
|
||||
@@ -153,6 +153,10 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
|
||||
number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
|
||||
|
||||
if (number === 0) {
|
||||
isNegative = false;
|
||||
}
|
||||
|
||||
var fraction = ('' + number).split(DECIMAL_SEP);
|
||||
var whole = fraction[0];
|
||||
fraction = fraction[1] || '';
|
||||
@@ -322,8 +326,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
|
||||
* * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm)
|
||||
* * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm)
|
||||
*
|
||||
* `format` string can contain literal values. These need to be quoted with single quotes (e.g.
|
||||
* `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence
|
||||
* `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
|
||||
* `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
|
||||
* (e.g. `"h 'o''clock'"`).
|
||||
*
|
||||
* @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
|
||||
@@ -343,6 +347,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
|
||||
<span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
|
||||
<span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
|
||||
<span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
|
||||
<span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
|
||||
<span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should format date', function() {
|
||||
@@ -352,6 +358,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
|
||||
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
|
||||
expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
|
||||
toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
|
||||
expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
|
||||
toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
orderByFilter.$inject = ['$parse'];
|
||||
function orderByFilter($parse){
|
||||
return function(array, sortPredicate, reverseOrder) {
|
||||
if (!isArray(array)) return array;
|
||||
if (!(isArrayLike(array))) return array;
|
||||
if (!sortPredicate) return array;
|
||||
sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
|
||||
sortPredicate = map(sortPredicate, function(predicate){
|
||||
|
||||
+1
-1
@@ -1048,7 +1048,7 @@ function $HttpProvider() {
|
||||
if (isObject(v)) {
|
||||
if (isDate(v)){
|
||||
v = v.toISOString();
|
||||
} else if (isObject(v)) {
|
||||
} else {
|
||||
v = toJson(v);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ function $InterpolateProvider() {
|
||||
* @description
|
||||
* Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
|
||||
*
|
||||
* Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change
|
||||
* Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
|
||||
* the symbol.
|
||||
*
|
||||
* @returns {string} start symbol.
|
||||
@@ -237,7 +237,7 @@ function $InterpolateProvider() {
|
||||
* @description
|
||||
* Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
|
||||
*
|
||||
* Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change
|
||||
* Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
|
||||
* the symbol.
|
||||
*
|
||||
* @returns {string} end symbol.
|
||||
|
||||
+11
-8
@@ -320,17 +320,16 @@ LocationHashbangInHtml5Url.prototype =
|
||||
* Change path, search and hash, when called with parameter and return `$location`.
|
||||
*
|
||||
* @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
|
||||
* @param {string=} replace The path that will be changed
|
||||
* @return {string} url
|
||||
*/
|
||||
url: function(url, replace) {
|
||||
url: function(url) {
|
||||
if (isUndefined(url))
|
||||
return this.$$url;
|
||||
|
||||
var match = PATH_MATCH.exec(url);
|
||||
if (match[1]) this.path(decodeURIComponent(match[1]));
|
||||
if (match[2] || match[1]) this.search(match[3] || '');
|
||||
this.hash(match[5] || '', replace);
|
||||
this.hash(match[5] || '');
|
||||
|
||||
return this;
|
||||
},
|
||||
@@ -388,10 +387,11 @@ LocationHashbangInHtml5Url.prototype =
|
||||
* Note: Path should always begin with forward slash (/), this method will add the forward slash
|
||||
* if it is missing.
|
||||
*
|
||||
* @param {string=} path New path
|
||||
* @param {(string|number)=} path New path
|
||||
* @return {string} path
|
||||
*/
|
||||
path: locationGetterSetter('$$path', function(path) {
|
||||
path = path ? path.toString() : '';
|
||||
return path.charAt(0) == '/' ? path : '/' + path;
|
||||
}),
|
||||
|
||||
@@ -427,7 +427,7 @@ LocationHashbangInHtml5Url.prototype =
|
||||
* If the argument is a hash object containing an array of values, these values will be encoded
|
||||
* as duplicate search parameters in the url.
|
||||
*
|
||||
* @param {(string|Array<string>|boolean)=} paramValue If `search` is a string, then `paramValue`
|
||||
* @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
|
||||
* will override only a single search property.
|
||||
*
|
||||
* If `paramValue` is an array, it will override the property of the `search` component of
|
||||
@@ -446,7 +446,8 @@ LocationHashbangInHtml5Url.prototype =
|
||||
case 0:
|
||||
return this.$$search;
|
||||
case 1:
|
||||
if (isString(search)) {
|
||||
if (isString(search) || isNumber(search)) {
|
||||
search = search.toString();
|
||||
this.$$search = parseKeyValue(search);
|
||||
} else if (isObject(search)) {
|
||||
// remove object undefined or null properties
|
||||
@@ -483,10 +484,12 @@ LocationHashbangInHtml5Url.prototype =
|
||||
*
|
||||
* Change hash fragment when called with parameter and return `$location`.
|
||||
*
|
||||
* @param {string=} hash New hash fragment
|
||||
* @param {(string|number)=} hash New hash fragment
|
||||
* @return {string} hash
|
||||
*/
|
||||
hash: locationGetterSetter('$$hash', identity),
|
||||
hash: locationGetterSetter('$$hash', function(hash) {
|
||||
return hash ? hash.toString() : '';
|
||||
}),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
|
||||
+4
-3
@@ -753,7 +753,7 @@ Parser.prototype = {
|
||||
var context = contextGetter ? contextGetter(scope, locals) : scope;
|
||||
|
||||
for (var i = 0; i < argsFn.length; i++) {
|
||||
args.push(argsFn[i](scope, locals));
|
||||
args.push(ensureSafeObject(argsFn[i](scope, locals), parser.text));
|
||||
}
|
||||
var fnPtr = fn(scope, locals, context) || noop;
|
||||
|
||||
@@ -841,13 +841,15 @@ Parser.prototype = {
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
function setter(obj, path, setValue, fullExp, options) {
|
||||
ensureSafeObject(obj, fullExp);
|
||||
|
||||
//needed?
|
||||
options = options || {};
|
||||
|
||||
var element = path.split('.'), key;
|
||||
for (var i = 0; element.length > 1; i++) {
|
||||
key = ensureSafeMemberName(element.shift(), fullExp);
|
||||
var propertyObj = obj[key];
|
||||
var propertyObj = ensureSafeObject(obj[key], fullExp);
|
||||
if (!propertyObj) {
|
||||
propertyObj = {};
|
||||
obj[key] = propertyObj;
|
||||
@@ -867,7 +869,6 @@ function setter(obj, path, setValue, fullExp, options) {
|
||||
}
|
||||
}
|
||||
key = ensureSafeMemberName(element.shift(), fullExp);
|
||||
ensureSafeObject(obj, fullExp);
|
||||
ensureSafeObject(obj[key], fullExp);
|
||||
obj[key] = setValue;
|
||||
return setValue;
|
||||
|
||||
+22
-5
@@ -139,10 +139,26 @@ function $RootScopeProvider(){
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name $rootScope.Scope#$id
|
||||
* @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for
|
||||
* debugging.
|
||||
*
|
||||
* @description
|
||||
* Unique scope ID (monotonically increasing) useful for debugging.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name $rootScope.Scope#$parent
|
||||
*
|
||||
* @description
|
||||
* Reference to the parent scope.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name $rootScope.Scope#$root
|
||||
*
|
||||
* @description
|
||||
* Reference to the root scope.
|
||||
*/
|
||||
|
||||
Scope.prototype = {
|
||||
constructor: Scope,
|
||||
@@ -154,9 +170,8 @@ function $RootScopeProvider(){
|
||||
* @description
|
||||
* Creates a new child {@link ng.$rootScope.Scope scope}.
|
||||
*
|
||||
* The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and
|
||||
* {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the
|
||||
* scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
|
||||
* The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
|
||||
* The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
|
||||
*
|
||||
* {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
|
||||
* desired for the scope and its child scopes to be permanently detached from the parent and
|
||||
@@ -609,6 +624,8 @@ function $RootScopeProvider(){
|
||||
logIdx, logMsg, asyncTask;
|
||||
|
||||
beginPhase('$digest');
|
||||
// Check for changes to browser url that happened in sync before the call to $digest
|
||||
$browser.$$checkUrlChange();
|
||||
|
||||
lastDirtyWatch = null;
|
||||
|
||||
|
||||
Vendored
+2
@@ -56,6 +56,8 @@ angular.mock.$Browser = function() {
|
||||
return listener;
|
||||
};
|
||||
|
||||
self.$$checkUrlChange = angular.noop;
|
||||
|
||||
self.cookieHash = {};
|
||||
self.lastCookieHash = {};
|
||||
self.deferredFns = [];
|
||||
|
||||
+32
-4
@@ -36,7 +36,7 @@ function MockWindow() {
|
||||
};
|
||||
|
||||
this.location = {
|
||||
href: 'http://server',
|
||||
href: 'http://server/',
|
||||
replace: noop
|
||||
};
|
||||
|
||||
@@ -414,7 +414,7 @@ describe('browser', function() {
|
||||
|
||||
expect(replaceState).not.toHaveBeenCalled();
|
||||
expect(locationReplace).not.toHaveBeenCalled();
|
||||
expect(fakeWindow.location.href).toEqual('http://server');
|
||||
expect(fakeWindow.location.href).toEqual('http://server/');
|
||||
});
|
||||
|
||||
it('should use history.replaceState when available', function() {
|
||||
@@ -426,7 +426,7 @@ describe('browser', function() {
|
||||
|
||||
expect(pushState).not.toHaveBeenCalled();
|
||||
expect(locationReplace).not.toHaveBeenCalled();
|
||||
expect(fakeWindow.location.href).toEqual('http://server');
|
||||
expect(fakeWindow.location.href).toEqual('http://server/');
|
||||
});
|
||||
|
||||
it('should set location.href when pushState not available', function() {
|
||||
@@ -448,7 +448,7 @@ describe('browser', function() {
|
||||
|
||||
expect(pushState).not.toHaveBeenCalled();
|
||||
expect(replaceState).not.toHaveBeenCalled();
|
||||
expect(fakeWindow.location.href).toEqual('http://server');
|
||||
expect(fakeWindow.location.href).toEqual('http://server/');
|
||||
});
|
||||
|
||||
it('should return $browser to allow chaining', function() {
|
||||
@@ -615,4 +615,32 @@ describe('browser', function() {
|
||||
expect(browser.baseHref()).toEqual('/base/path/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration tests with $location', function() {
|
||||
|
||||
beforeEach(module(function($provide, $locationProvider) {
|
||||
spyOn(fakeWindow.history, 'pushState').andCallFake(function(stateObj, title, newUrl) {
|
||||
fakeWindow.location.href = newUrl;
|
||||
});
|
||||
$provide.value('$browser', browser);
|
||||
browser.pollFns = [];
|
||||
|
||||
$locationProvider.html5Mode(true);
|
||||
}));
|
||||
|
||||
it('should update $location when it was changed outside of Angular in sync '+
|
||||
'before $digest was called', function() {
|
||||
inject(function($rootScope, $location) {
|
||||
fakeWindow.history.pushState(null, '', 'http://server/someTestHash');
|
||||
|
||||
// Verify that infinite digest reported in #6976 no longer occurs
|
||||
expect(function() {
|
||||
$rootScope.$digest();
|
||||
}).not.toThrow();
|
||||
|
||||
expect($location.path()).toBe('/someTestHash');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -621,10 +621,8 @@ describe('form animations', function() {
|
||||
it('should trigger an animation when invalid', inject(function($animate) {
|
||||
form.$setValidity('required', false);
|
||||
|
||||
assertValidAnimation($animate.queue[0], 'removeClass', 'ng-valid');
|
||||
assertValidAnimation($animate.queue[1], 'addClass', 'ng-invalid');
|
||||
assertValidAnimation($animate.queue[2], 'removeClass', 'ng-valid-required');
|
||||
assertValidAnimation($animate.queue[3], 'addClass', 'ng-invalid-required');
|
||||
assertValidAnimation($animate.queue[0], 'setClass', 'ng-invalid', 'ng-valid');
|
||||
assertValidAnimation($animate.queue[1], 'setClass', 'ng-invalid-required', 'ng-valid-required');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when valid', inject(function($animate) {
|
||||
@@ -634,10 +632,8 @@ describe('form animations', function() {
|
||||
|
||||
form.$setValidity('required', true);
|
||||
|
||||
assertValidAnimation($animate.queue[0], 'removeClass', 'ng-invalid');
|
||||
assertValidAnimation($animate.queue[1], 'addClass', 'ng-valid');
|
||||
assertValidAnimation($animate.queue[2], 'removeClass', 'ng-invalid-required');
|
||||
assertValidAnimation($animate.queue[3], 'addClass', 'ng-valid-required');
|
||||
assertValidAnimation($animate.queue[0], 'setClass', 'ng-valid', 'ng-invalid');
|
||||
assertValidAnimation($animate.queue[1], 'setClass', 'ng-valid-required', 'ng-invalid-required');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when dirty', inject(function($animate) {
|
||||
@@ -661,17 +657,13 @@ describe('form animations', function() {
|
||||
it('should trigger custom errors as addClass/removeClass when invalid/valid', inject(function($animate) {
|
||||
form.$setValidity('custom-error', false);
|
||||
|
||||
assertValidAnimation($animate.queue[0], 'removeClass', 'ng-valid');
|
||||
assertValidAnimation($animate.queue[1], 'addClass', 'ng-invalid');
|
||||
assertValidAnimation($animate.queue[2], 'removeClass', 'ng-valid-custom-error');
|
||||
assertValidAnimation($animate.queue[3], 'addClass', 'ng-invalid-custom-error');
|
||||
assertValidAnimation($animate.queue[0], 'setClass', 'ng-invalid', 'ng-valid');
|
||||
assertValidAnimation($animate.queue[1], 'setClass', 'ng-invalid-custom-error', 'ng-valid-custom-error');
|
||||
|
||||
$animate.queue = [];
|
||||
form.$setValidity('custom-error', true);
|
||||
|
||||
assertValidAnimation($animate.queue[0], 'removeClass', 'ng-invalid');
|
||||
assertValidAnimation($animate.queue[1], 'addClass', 'ng-valid');
|
||||
assertValidAnimation($animate.queue[2], 'removeClass', 'ng-invalid-custom-error');
|
||||
assertValidAnimation($animate.queue[3], 'addClass', 'ng-valid-custom-error');
|
||||
assertValidAnimation($animate.queue[0], 'setClass', 'ng-valid', 'ng-invalid');
|
||||
assertValidAnimation($animate.queue[1], 'setClass', 'ng-valid-custom-error', 'ng-invalid-custom-error');
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -580,19 +580,30 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
if (!_jqLiteMode) {
|
||||
it('should not cause the double $digest when triggering an event using jQuery', function() {
|
||||
$sniffer.hasEvent = function(eventName) {
|
||||
return eventName !== 'input';
|
||||
};
|
||||
describe('double $digest when triggering an event using jQuery', function() {
|
||||
function run() {
|
||||
$sniffer.hasEvent = function(eventName) {
|
||||
return eventName !== 'input';
|
||||
};
|
||||
|
||||
compileInput('<input type="text" ng-model="name" name="alias" ng-change="change()" />');
|
||||
compileInput('<input type="text" ng-model="name" name="alias" ng-change="change()" />');
|
||||
|
||||
scope.field = 'fake field';
|
||||
scope.$watch('field', function() {
|
||||
// We need to use _originalTrigger since trigger is modified by Angular Scenario.
|
||||
inputElm._originalTrigger('change');
|
||||
scope.field = 'fake field';
|
||||
scope.$watch('field', function() {
|
||||
// We need to use _originalTrigger since trigger is modified by Angular Scenario.
|
||||
inputElm._originalTrigger('change');
|
||||
});
|
||||
scope.$apply();
|
||||
}
|
||||
|
||||
it('should not cause the double $digest with non isolate scopes', function() {
|
||||
run();
|
||||
});
|
||||
|
||||
it('should not cause the double $digest with isolate scopes', function() {
|
||||
scope = scope.$new(true);
|
||||
run();
|
||||
});
|
||||
scope.$apply();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -39,4 +39,88 @@ describe('event directives', function() {
|
||||
expect($rootScope.formSubmitted).toEqual('foo');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('focus', function() {
|
||||
|
||||
describe('call the listener asynchronously during $apply', function() {
|
||||
function run(scope) {
|
||||
inject(function($compile) {
|
||||
element = $compile('<input type="text" ng-focus="focus()">')(scope);
|
||||
scope.focus = jasmine.createSpy('focus');
|
||||
|
||||
scope.$apply(function() {
|
||||
element.triggerHandler('focus');
|
||||
expect(scope.focus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(scope.focus).toHaveBeenCalledOnce();
|
||||
});
|
||||
}
|
||||
|
||||
it('should call the listener with non isolate scopes', inject(function($rootScope) {
|
||||
run($rootScope.$new());
|
||||
}));
|
||||
|
||||
it('should call the listener with isolate scopes', inject(function($rootScope) {
|
||||
run($rootScope.$new(true));
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
it('should call the listener synchronously inside of $apply if outside of $apply',
|
||||
inject(function($rootScope, $compile) {
|
||||
element = $compile('<input type="text" ng-focus="focus()" ng-model="value">')($rootScope);
|
||||
$rootScope.focus = jasmine.createSpy('focus').andCallFake(function() {
|
||||
$rootScope.value = 'newValue';
|
||||
});
|
||||
|
||||
element.triggerHandler('focus');
|
||||
|
||||
expect($rootScope.focus).toHaveBeenCalledOnce();
|
||||
expect(element.val()).toBe('newValue');
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('blur', function() {
|
||||
|
||||
describe('call the listener asynchronously during $apply', function() {
|
||||
function run(scope) {
|
||||
inject(function($compile) {
|
||||
element = $compile('<input type="text" ng-blur="blur()">')(scope);
|
||||
scope.blur = jasmine.createSpy('blur');
|
||||
|
||||
scope.$apply(function() {
|
||||
element.triggerHandler('blur');
|
||||
expect(scope.blur).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(scope.blur).toHaveBeenCalledOnce();
|
||||
});
|
||||
}
|
||||
|
||||
it('should call the listener with non isolate scopes', inject(function($rootScope) {
|
||||
run($rootScope.$new());
|
||||
}));
|
||||
|
||||
it('should call the listener with isolate scopes', inject(function($rootScope) {
|
||||
run($rootScope.$new(true));
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
it('should call the listener synchronously inside of $apply if outside of $apply',
|
||||
inject(function($rootScope, $compile) {
|
||||
element = $compile('<input type="text" ng-blur="blur()" ng-model="value">')($rootScope);
|
||||
$rootScope.blur = jasmine.createSpy('blur').andCallFake(function() {
|
||||
$rootScope.value = 'newValue';
|
||||
});
|
||||
|
||||
element.triggerHandler('blur');
|
||||
|
||||
expect($rootScope.blur).toHaveBeenCalledOnce();
|
||||
expect(element.val()).toBe('newValue');
|
||||
}));
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,23 +34,5 @@ describe('ngKeyup and ngKeydown directives', function() {
|
||||
expect($rootScope.touched).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should get called on focus', inject(function($rootScope, $compile) {
|
||||
element = $compile('<input ng-focus="touched = true">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.touched).toBeFalsy();
|
||||
|
||||
browserTrigger(element, 'focus');
|
||||
expect($rootScope.touched).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should get called on blur', inject(function($rootScope, $compile) {
|
||||
element = $compile('<input ng-blur="touched = true">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.touched).toBeFalsy();
|
||||
|
||||
browserTrigger(element, 'blur');
|
||||
expect($rootScope.touched).toEqual(true);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -939,8 +939,8 @@ describe('ngRepeat', function() {
|
||||
it('should throw error on adding existing duplicates and recover', function() {
|
||||
scope.items = [a, a, a];
|
||||
scope.$digest();
|
||||
expect($exceptionHandler.errors.shift().message).
|
||||
toMatch(/^\[ngRepeat:dupes\] Duplicates in a repeater are not allowed\. Use 'track by' expression to specify unique keys\. Repeater: item in items, Duplicate key: object:003/);
|
||||
expect($exceptionHandler.errors.shift().message).toMatch(
|
||||
/^\[ngRepeat:dupes\] Duplicates in a repeater are not allowed\. Use 'track by' expression to specify unique keys\. Repeater: item in items, Duplicate key: object:003, Duplicate value: {}/);
|
||||
|
||||
// recover
|
||||
scope.items = [a];
|
||||
@@ -959,8 +959,8 @@ describe('ngRepeat', function() {
|
||||
it('should throw error on new duplicates and recover', function() {
|
||||
scope.items = [d, d, d];
|
||||
scope.$digest();
|
||||
expect($exceptionHandler.errors.shift().message).
|
||||
toMatch(/^\[ngRepeat:dupes\] Duplicates in a repeater are not allowed\. Use 'track by' expression to specify unique keys\. Repeater: item in items, Duplicate key: object:009/);
|
||||
expect($exceptionHandler.errors.shift().message).toMatch(
|
||||
/^\[ngRepeat:dupes\] Duplicates in a repeater are not allowed\. Use 'track by' expression to specify unique keys\. Repeater: item in items, Duplicate key: object:009, Duplicate value: {}/);
|
||||
|
||||
// recover
|
||||
scope.items = [a];
|
||||
|
||||
@@ -83,6 +83,11 @@ describe('filters', function() {
|
||||
num = formatNumber(123.1, pattern, ',', '.', 3);
|
||||
expect(num).toBe('123.100');
|
||||
});
|
||||
|
||||
it('should format numbers that round to zero as nonnegative', function(){
|
||||
var num = formatNumber(-0.01, pattern, ',', '.', 1);
|
||||
expect(num).toBe('0.0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('currency', function() {
|
||||
@@ -176,7 +181,7 @@ describe('filters', function() {
|
||||
expect(number(1e-6, 6)).toEqual('0.000001');
|
||||
expect(number(1e-7, 6)).toEqual('0.000000');
|
||||
|
||||
expect(number(-1e-50, 0)).toEqual('-0');
|
||||
expect(number(-1e-50, 0)).toEqual('0');
|
||||
expect(number(-1e-6, 6)).toEqual('-0.000001');
|
||||
expect(number(-1e-7, 6)).toEqual('-0.000000');
|
||||
});
|
||||
|
||||
@@ -17,6 +17,16 @@ describe('Filter: orderBy', function() {
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "reverse")).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
|
||||
it('should sort inherited from array', function(){
|
||||
function BaseCollection(){}
|
||||
BaseCollection.prototype = Array.prototype;
|
||||
var child = new BaseCollection();
|
||||
child.push({a:2});
|
||||
child.push({a:15});
|
||||
|
||||
expect(orderBy(child, 'a', true)).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
|
||||
it('should sort array by predicate', function() {
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['a', 'b'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['b', 'a'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
|
||||
@@ -87,6 +87,19 @@ describe('$location', function() {
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/new/path?search=a&b=c&d#hash');
|
||||
});
|
||||
|
||||
it('path() should not break on numeric values', function() {
|
||||
url.path(1);
|
||||
expect(url.path()).toBe('/1');
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/1?search=a&b=c&d#hash');
|
||||
});
|
||||
|
||||
|
||||
it('path() should set to empty path on null value', function () {
|
||||
url.path('/foo');
|
||||
expect(url.path()).toBe('/foo');
|
||||
url.path(null);
|
||||
expect(url.path()).toBe('/');
|
||||
});
|
||||
|
||||
it('search() should accept string', function() {
|
||||
url.search('x=y&c');
|
||||
@@ -127,6 +140,13 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
|
||||
it('search() should accept numeric keys', function() {
|
||||
url.search({1: 'one', 2: 'two'});
|
||||
expect(url.search()).toEqual({'1': 'one', '2': 'two'});
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?1=one&2=two#hash');
|
||||
});
|
||||
|
||||
|
||||
it('search() should handle multiple value', function() {
|
||||
url.search('a&b');
|
||||
expect(url.search()).toEqual({a: true, b: true});
|
||||
@@ -143,6 +163,8 @@ describe('$location', function() {
|
||||
it('search() should handle single value', function() {
|
||||
url.search('ignore');
|
||||
expect(url.search()).toEqual({ignore: true});
|
||||
url.search(1);
|
||||
expect(url.search()).toEqual({1: true});
|
||||
});
|
||||
|
||||
|
||||
@@ -163,6 +185,20 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
|
||||
it('hash() should accept numeric parameter', function() {
|
||||
url.hash(5);
|
||||
expect(url.hash()).toBe('5');
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#5');
|
||||
});
|
||||
|
||||
|
||||
it('hash() should accept null parameter', function() {
|
||||
url.hash(null);
|
||||
expect(url.hash()).toBe('');
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d');
|
||||
});
|
||||
|
||||
|
||||
it('url() should change the path, search and hash', function() {
|
||||
url.url('/some/path?a=b&c=d#hhh');
|
||||
expect(url.url()).toBe('/some/path?a=b&c=d#hhh');
|
||||
|
||||
@@ -1006,6 +1006,43 @@ describe('parser', function() {
|
||||
expect(scope.$eval('fn().anotherFn()')).toBe(true);
|
||||
});
|
||||
|
||||
it('should disallow traversing the Function object in a setter: E02', function() {
|
||||
expect(function() {
|
||||
// This expression by itself isn't dangerous. However, one can use this to
|
||||
// automatically call an object (e.g. a Function object) when it is automatically
|
||||
// toString'd/valueOf'd by setting the RHS to Function.prototype.call.
|
||||
scope.$eval('hasOwnProperty.constructor.prototype.valueOf = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: hasOwnProperty.constructor.prototype.valueOf = 1');
|
||||
});
|
||||
|
||||
it('should disallow passing the Function object as a parameter: E03', function() {
|
||||
expect(function() {
|
||||
// This expression constructs a function but does not execute it. It does lead the
|
||||
// way to execute it if one can get the toString/valueOf of it to call the function.
|
||||
scope.$eval('["a", "alert(1)"].sort(hasOwnProperty.constructor)');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should prevent exploit E01', function() {
|
||||
// This is a tracking exploit. The two individual tests, it('should … : E02') and
|
||||
// it('should … : E03') test for two parts to block this exploit. This exploit works
|
||||
// as follows:
|
||||
//
|
||||
// • Array.sort takes a comparison function and passes it 2 parameters to compare. If
|
||||
// the result is non-primitive, sort then invokes valueOf() on the result.
|
||||
// • The Function object conveniently accepts two string arguments so we can use this
|
||||
// to construct a function. However, this doesn't do much unless we can execute it.
|
||||
// • We set the valueOf property on Function.prototype to Function.prototype.call.
|
||||
// This causes the function that we constructed to be executed when sort calls
|
||||
// .valueOf() on the result of the comparison.
|
||||
expect(function() {
|
||||
scope.$eval('' +
|
||||
'hasOwnProperty.constructor.prototype.valueOf=valueOf.call;' +
|
||||
'["a","alert(1)"].sort(hasOwnProperty.constructor)');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should call the function once when it is part of the context', function() {
|
||||
var count = 0;
|
||||
|
||||
Reference in New Issue
Block a user