diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 4d5e9ccd58eb..9e6d738a7e66 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -119,7 +119,7 @@ tests once on Chrome run: yarn grunt test:unit ``` -To run the tests on other browsers (Chrome and Firefox are pre-configured) use: +To run the tests on other browsers use the command line flag: ```shell yarn grunt test:unit --browsers=Chrome,Firefox @@ -127,9 +127,6 @@ yarn grunt test:unit --browsers=Chrome,Firefox **Note:** there should be _no spaces between browsers_. `Chrome, Firefox` is INVALID. -If you want to test locally on Safari, Internet Explorer, or Edge, you must install -the respective `karma--launcher` from npm. - If you have a Saucelabs or Browserstack account, you can also run the unit tests on these services via our pre-defined customLaunchers. See the [karma config file](/karma-shared.conf.js) for all pre-configured browsers. diff --git a/Gruntfile.js b/Gruntfile.js index d6c3e66f242f..33470ec15faf 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -328,7 +328,7 @@ module.exports = function(grunt) { files: [ // The source files are needed by the embedded examples in the docs app. { - src: 'build/angular*.{js.map,min.js}', + src: 'build/angular*.{js,js.map,min.js}', dest: 'uploadDocs/', expand: true, flatten: true diff --git a/docs/content/guide/$location.ngdoc b/docs/content/guide/$location.ngdoc index d60d4d088513..368074dd8119 100644 --- a/docs/content/guide/$location.ngdoc +++ b/docs/content/guide/$location.ngdoc @@ -391,7 +391,7 @@ called `/base`). The URL `/base` is actually outside the application (it refers in the root `/` folder). If you wish to be able to navigate to the application via a URL such as `/base` then you should ensure that -you server is setup to redirect such requests to `/base/`. +your server is setup to redirect such requests to `/base/`. See https://github.com/angular/angular.js/issues/14018 for more information. diff --git a/docs/content/guide/animations.ngdoc b/docs/content/guide/animations.ngdoc index 669c8f92bfa3..a13661a36a68 100644 --- a/docs/content/guide/animations.ngdoc +++ b/docs/content/guide/animations.ngdoc @@ -66,7 +66,7 @@ You may also want to setup a separate CSS file for defining CSS-based animations ## How they work Animations in AngularJS are completely based on CSS classes. As long as you have a CSS class -attached to a HTML element within your application, you can apply animations to it. Lets say for +attached to an HTML element within your application, you can apply animations to it. Let's say for example that we have an HTML template with a repeater like so: ```html diff --git a/docs/content/guide/di.ngdoc b/docs/content/guide/di.ngdoc index 0701ef6d1fc8..99c8052e4a84 100644 --- a/docs/content/guide/di.ngdoc +++ b/docs/content/guide/di.ngdoc @@ -14,26 +14,34 @@ and providing them to other components as requested. ## Using Dependency Injection -DI is pervasive throughout Angular. You can use it when defining components or when providing `run` -and `config` blocks for a module. +Dependency Injection is pervasive throughout AngularJS. You can use it when defining components +or when providing `run` and `config` blocks for a module. -- Components such as services, directives, filters, and animations are defined by an injectable -factory method or constructor function. These components can be injected with "service" and "value" -components as dependencies. +- {@link angular.Module#service Services}, {@link angular.Module#directive directives}, +{@link angular.Module#filter filters}, and {@link angular.Module#animation animations} are +defined by an injectable factory method or constructor function, and can be injected with +"services", "values", and "constants" as dependencies. -- Controllers are defined by a constructor function, which can be injected with any of the "service" -and "value" components as dependencies, but they can also be provided with special dependencies. See -{@link di#controllers Controllers} below for a list of these special dependencies. +- {@link ng.$controller Controllers} are defined by a constructor function, which can be injected +with any of the "service" and "value" as dependencies, but they can also be provided with +"special dependencies". See {@link di#controllers Controllers} below for a list of these +special dependencies. -- The `run` method accepts a function, which can be injected with "service", "value" and "constant" -components as dependencies. Note that you cannot inject "providers" into `run` blocks. +- The {@link angular.Module#run `run`} method accepts a function, which can be injected with +"services", "values" and, "constants" as dependencies. Note that you cannot inject "providers" +into `run` blocks. -- The `config` method accepts a function, which can be injected with "provider" and "constant" -components as dependencies. Note that you cannot inject "service" or "value" components into -configuration. +- The {@link angular.Module#config `config`} method accepts a function, which can be injected with +"providers" and "constants" as dependencies. Note that you cannot inject "services" or +"values" into configuration. + +- The {@link angular.Module#provider `provider`} method can only be injected with other "providers". +However, only those that have been **registered beforehand** can be injected. This is different +from services, where the order of registration does not matter. See {@link module#module-loading-dependencies Modules} for more details about `run` and `config` -blocks. +blocks and {@link guide/providers Providers} for more information about the different provider +types. ### Factory Methods diff --git a/docs/content/guide/external-resources.ngdoc b/docs/content/guide/external-resources.ngdoc index d2553b0319bf..215bd13dcd04 100644 --- a/docs/content/guide/external-resources.ngdoc +++ b/docs/content/guide/external-resources.ngdoc @@ -48,7 +48,7 @@ This is a collection of external, 3rd party resources for learning and developin * **Django:** [Tutorial](http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework), [Integrating AngularJS with Django](http://django-angular.readthedocs.org/en/latest/integration.html), [Getting Started with Django Rest Framework and AngularJS](http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html) * **FireBase:** [AngularFire](http://angularfire.com/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU) -* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/developers/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications/), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos) +* **Google Cloud Platform:** [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos) * **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/) * **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610) * **Rails: **[Tutorial](http://coderberry.me/blog/2013/04/22/angularjs-on-rails-4-part-1/), [AngularJS with Rails4](https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4), [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails) diff --git a/docs/content/guide/module.ngdoc b/docs/content/guide/module.ngdoc index 62ab1c51f73f..f6a371ba11f6 100644 --- a/docs/content/guide/module.ngdoc +++ b/docs/content/guide/module.ngdoc @@ -3,12 +3,14 @@ @sortOrder 320 @description -# What is a Module? +# Modules + +## What is a Module? You can think of a module as a container for the different parts of your app – controllers, services, filters, directives, etc. -# Why? +## Why? Most applications have a main method that instantiates and wires together the different parts of the application. @@ -23,7 +25,7 @@ should be bootstrapped. There are several advantages to this approach: * End-to-end tests can use modules to override configuration. -# The Basics +## The Basics I'm in a hurry. How do I get a Hello World module working? @@ -65,7 +67,7 @@ Important things to notice: This array is the list of modules `myApp` depends on. -# Recommended Setup +## Recommended Setup While the example above is simple, it will not scale to large applications. Instead we recommend that you break your application to multiple modules like this: @@ -136,39 +138,46 @@ The above is a suggestion. Tailor it to your needs. -# Module Loading & Dependencies +## Module Loading -A module is a collection of configuration and run blocks which get applied to the application -during the bootstrap process. In its simplest form the module consists of a collection of two kinds -of blocks: +A {@link angular.Module module} is a collection of providers, services, directives etc., +and optionally config and run blocks which get applied to the application during the +bootstrap process. - 1. **Configuration blocks** - get executed during the provider registrations and configuration - phase. Only providers and constants can be injected into configuration blocks. This is to - prevent accidental instantiation of services before they have been fully configured. - 2. **Run blocks** - get executed after the injector is created and are used to kickstart the - application. Only instances and constants can be injected into run blocks. This is to prevent - further system configuration during application run time. +The {@link angular.Module module API} describes all the available methods and how they can be used. -```js -angular.module('myModule', []). - config(function(injectables) { // provider-injector - // This is an example of config block. - // You can have as many of these as you want. - // You can only inject Providers (not instances) - // into config blocks. - }). - run(function(injectables) { // instance-injector - // This is an example of a run block. - // You can have as many of these as you want. - // You can only inject instances (not Providers) - // into run blocks - }); -``` +See {@link guide/di#using-dependency-injection Using Dependency Injection} to find out which +dependencies can be injected in each method. + +### Dependencies and Order of execution + +Modules can list other modules as their dependencies. Depending on a module implies that the required +module will be loaded before the requiring module is loaded. + +In a single module the order of execution is as follows: + +1. {@link angular.Module#provider provider} functions are executed, so they and the services they +define can be made available to the {@link auto.$injector $injector}. -## Configuration Blocks +2. After that, the configuration blocks ({@link angular.Module#config config} functions) are executed. +This means the configuration blocks of the required modules execute before the configuration blocks +of any requiring module. + +This continues until all module dependencies has been resolved. + +Then, the {@link angular.Module#run run} blocks that have been collected from each module are +executed in order of requirement. + +Note: each module is only loaded once, even if multiple other modules require it. +Note: the factory function for "values" and "services" is called lazily when the value/service is +injected for the first time. + +### Registration in the config block + +While it is recommended to register injectables directly with the {@link angular.Module module API}, +it is also possible to register services, directives etc. by injecting +{@link $provide $provide} or the individual service providers into the config function: -There are some convenience methods on the module which are equivalent to the `config` block. For -example: ```js angular.module('myModule', []). @@ -188,12 +197,7 @@ angular.module('myModule', []). }); ``` -
-When bootstrapping, first Angular applies all constant definitions. -Then Angular applies configuration blocks in the same order they were registered. -
- -## Run Blocks +### Run Blocks Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application. It is executed after all of the services have been @@ -201,22 +205,14 @@ configured and the injector has been created. Run blocks typically contain code to unit-test, and for this reason should be declared in isolated modules, so that they can be ignored in the unit-tests. -## Dependencies - -Modules can list other modules as their dependencies. Depending on a module implies that the required -module needs to be loaded before the requiring module is loaded. In other words the configuration -blocks of the required modules execute before the configuration blocks of the requiring module. -The same is true for the run blocks. Each module can only be loaded once, even if multiple other -modules require it. - -## Asynchronous Loading +### Asynchronous Loading Modules are a way of managing $injector configuration, and have nothing to do with loading of scripts into a VM. There are existing projects which deal with script loading, which may be used with Angular. Because modules do nothing at load time they can be loaded into the VM in any order and thus script loaders can take advantage of this property and parallelize the loading process. -## Creation versus Retrieval +### Creation versus Retrieval Beware that using `angular.module('myModule', [])` will create the module `myModule` and overwrite any existing module named `myModule`. Use `angular.module('myModule')` to retrieve an existing module. @@ -235,7 +231,7 @@ var myModule = angular.module('myModule', []); var myModule = angular.module('myOtherModule'); ``` -# Unit Testing +## Unit Testing A unit test is a way of instantiating a subset of an application to apply stimulus to it. Small, structured modules help keep unit tests concise and focused. diff --git a/karma-shared.conf.js b/karma-shared.conf.js index 65e5fa3bbae4..7498cdcd4c08 100644 --- a/karma-shared.conf.js +++ b/karma-shared.conf.js @@ -62,17 +62,17 @@ module.exports = function(config, specificOptions) { browserName: 'firefox', version: 'latest-1' }, - 'SL_Safari_8': { + 'SL_Safari-1': { base: 'SauceLabs', browserName: 'safari', - platform: 'OS X 10.10', - version: '8' + platform: 'OS X 10.12', + version: 'latest-1' }, - 'SL_Safari_9': { + 'SL_Safari': { base: 'SauceLabs', browserName: 'safari', - platform: 'OS X 10.11', - version: '9' + platform: 'OS X 10.12', + version: 'latest' }, 'SL_IE_9': { base: 'SauceLabs', diff --git a/package.json b/package.json index 3b516a858162..1ecbb9a3986b 100644 --- a/package.json +++ b/package.json @@ -65,10 +65,13 @@ "karma": "^2.0.0", "karma-browserstack-launcher": "^1.2.0", "karma-chrome-launcher": "^2.1.1", + "karma-edge-launcher": "^0.4.2", "karma-firefox-launcher": "^1.0.1", + "karma-ie-launcher": "^1.0.0", "karma-jasmine": "^1.1.0", "karma-junit-reporter": "^1.2.0", "karma-ng-scenario": "^1.0.0", + "karma-safari-launcher": "^1.0.0", "karma-sauce-launcher": "^1.2.0", "karma-script-launcher": "^1.0.0", "karma-spec-reporter": "^0.0.31", diff --git a/scripts/code.angularjs.org-firebase/functions/index.js b/scripts/code.angularjs.org-firebase/functions/index.js index 8b54322395cc..47dffc0ce81e 100644 --- a/scripts/code.angularjs.org-firebase/functions/index.js +++ b/scripts/code.angularjs.org-firebase/functions/index.js @@ -61,12 +61,8 @@ function sendStoredFile(request, response) { return new Promise((resolve, reject) => { const readStream = file.createReadStream() - .on('error', error => { - reject(error); - }) - .on('response', () => { - resolve(response); - }); + .on('error', reject) + .on('finish', resolve); response .status(200) @@ -75,7 +71,7 @@ function sendStoredFile(request, response) { 'Cache-Control': `public, max-age=${BROWSER_CACHE_DURATION}, s-maxage=${CDN_CACHE_DURATION}` }); - readStream.pipe(response); + readStream.pipe(response); }); }); diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh index f59711467e99..fdf94d8f13de 100755 --- a/scripts/travis/build.sh +++ b/scripts/travis/build.sh @@ -10,7 +10,7 @@ SAUCE_ACCESS_KEY=$(echo "$SAUCE_ACCESS_KEY" | rev) BROWSERS="SL_Chrome,SL_Chrome-1,\ SL_Firefox,SL_Firefox-1,\ -SL_Safari_8,SL_Safari_9,\ +SL_Safari,SL_Safari-1,\ SL_iOS,\ SL_IE_9,SL_IE_10,SL_IE_11,\ SL_EDGE,SL_EDGE-1" diff --git a/src/auto/injector.js b/src/auto/injector.js index d8ae717a7250..b64deacd58a4 100644 --- a/src/auto/injector.js +++ b/src/auto/injector.js @@ -440,6 +440,9 @@ function annotate(fn, strictDi, name) { * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the * console or not. * + * It is possible to inject other providers into the provider function, + * but the injected provider must have been defined before the one that requires it. + * * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provider'` key. * @param {(Object|function())} provider If the provider is: diff --git a/src/loader.js b/src/loader.js index ba950d5c1906..fc8a180a3f5b 100644 --- a/src/loader.js +++ b/src/loader.js @@ -344,7 +344,13 @@ function setupModuleLoader(window) { * @param {Function} configFn Execute this function on module load. Useful for service * configuration. * @description - * Use this method to register work which needs to be performed on module loading. + * Use this method to configure services by injecting their + * {@link angular.Module#provider `providers`}, e.g. for adding routes to the + * {@link ngRoute.$routeProvider $routeProvider}. + * + * Note that you can only inject {@link angular.Module#provider `providers`} and + * {@link angular.Module#constant `constants`} into this function. + * * For more about how to configure services, see * {@link providers#provider-recipe Provider Recipe}. */ diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js index e7e1a80adea4..db4b627f3b7b 100644 --- a/src/ng/directive/ngRepeat.js +++ b/src/ng/directive/ngRepeat.js @@ -74,71 +74,148 @@ * For example, if an item is added to the collection, `ngRepeat` will know that all other items * already have DOM elements, and will not re-render them. * - * The default tracking function (which tracks items by their identity) does not allow - * duplicate items in arrays. This is because when there are duplicates, it is not possible - * to maintain a one-to-one mapping between collection items and DOM elements. - * - * If you do need to repeat duplicate items, you can substitute the default tracking behavior - * with your own using the `track by` expression. - * - * For example, you may track items by the index of each item in the collection, using the - * special scope property `$index`: - * ```html - *
- * {{n}} - *
- * ``` - * - * You may also use arbitrary expressions in `track by`, including references to custom functions - * on the scope: - * ```html - *
- * {{n}} - *
- * ``` + * All different types of tracking functions, their syntax, and and their support for duplicate + * items in collections can be found in the + * {@link ngRepeat#ngRepeat-arguments ngRepeat expression description}. * *
- * If you are working with objects that have a unique identifier property, you should track - * by this identifier instead of the object instance. Should you reload your data later, `ngRepeat` - * will not have to rebuild the DOM elements for items it has already rendered, even if the - * JavaScript objects in the collection have been substituted for new ones. For large collections, - * this significantly improves rendering performance. If you don't have a unique identifier, - * `track by $index` can also provide a performance boost. + * **Best Practice:** If you are working with objects that have a unique identifier property, you + * should track by this identifier instead of the object instance, + * e.g. `item in items track by item.id`. + * Should you reload your data later, `ngRepeat` will not have to rebuild the DOM elements for items + * it has already rendered, even if the JavaScript objects in the collection have been substituted + * for new ones. For large collections, this significantly improves rendering performance. *
* - * ```html - *
- * {{model.name}} - *
- * ``` + * ### Effects of DOM Element re-use * - *
- *
- * Avoid using `track by $index` when the repeated template contains - * {@link guide/expression#one-time-binding one-time bindings}. In such cases, the `nth` DOM - * element will always be matched with the `nth` item of the array, so the bindings on that element - * will not be updated even when the corresponding item changes, essentially causing the view to get - * out-of-sync with the underlying data. - *
+ * When DOM elements are re-used, ngRepeat updates the scope for the element, which will + * automatically update any active bindings on the template. However, other + * functionality will not be updated, because the element is not re-created: * - * When no `track by` expression is provided, it is equivalent to tracking by the built-in - * `$id` function, which tracks items by their identity: - * ```html - *
- * {{obj.prop}} - *
- * ``` + * - Directives are not re-compiled + * - {@link guide/expression#one-time-binding one-time expressions} on the repeated template are not + * updated if they have stabilized. * - *
- *
- * **Note:** `track by` must always be the last expression: - *
- * ``` - *
- * {{model.name}} - *
- * ``` + * The above affects all kinds of element re-use due to tracking, but may be especially visible + * when tracking by `$index` due to the way ngRepeat re-uses elements. * + * The following example shows the effects of different actions with tracking: + + + + angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) { + var friends = [ + {name:'John', age:25}, + {name:'Mary', age:40}, + {name:'Peter', age:85} + ]; + + $scope.removeFirst = function() { + $scope.friends.shift(); + }; + + $scope.updateAge = function() { + $scope.friends.forEach(function(el) { + el.age = el.age + 5; + }); + }; + + $scope.copy = function() { + $scope.friends = angular.copy($scope.friends); + }; + + $scope.reset = function() { + $scope.friends = angular.copy(friends); + }; + + $scope.reset(); + }); + + +
+
    +
  1. When you click "Update Age", only the first list updates the age, because all others have + a one-time binding on the age property. If you then click "Copy", the current friend list + is copied, and now the second list updates the age, because the identity of the collection items + has changed and the list must be re-rendered. The 3rd and 4th list stay the same, because all the + items are already known according to their tracking functions. +
  2. +
  3. When you click "Remove First", the 4th list has the wrong age on both remaining items. This is + due to tracking by $index: when the first collection item is removed, ngRepeat reuses the first + DOM element for the new first collection item, and so on. Since the age property is one-time + bound, the value remains from the collection item which was previously at this index. +
  4. +
+ + + + +
+
+ track by $id(friend) (default): +
    +
  • + {{friend.name}} is {{friend.age}} years old. +
  • +
+ track by $id(friend) (default), with age one-time binding: +
    +
  • + {{friend.name}} is {{::friend.age}} years old. +
  • +
+ track by friend.name, with age one-time binding: +
    +
  • + {{friend.name}} is {{::friend.age}} years old. +
  • +
+ track by $index, with age one-time binding: +
    +
  • + {{friend.name}} is {{::friend.age}} years old. +
  • +
+
+
+ + .example-animate-container { + background:white; + border:1px solid black; + list-style:none; + margin:0; + padding:0 10px; + } + + .animate-repeat { + line-height:30px; + list-style:none; + box-sizing:border-box; + } + + .animate-repeat.ng-move, + .animate-repeat.ng-enter, + .animate-repeat.ng-leave { + transition:all linear 0.5s; + } + + .animate-repeat.ng-leave.ng-leave-active, + .animate-repeat.ng-move, + .animate-repeat.ng-enter { + opacity:0; + max-height:0; + } + + .animate-repeat.ng-leave, + .animate-repeat.ng-move.ng-move-active, + .animate-repeat.ng-enter.ng-enter-active { + opacity:1; + max-height:30px; + } + +
+ * * ## Special repeat start and end points * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending @@ -215,24 +292,38 @@ * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are * mapped to the same DOM element, which is not possible.) * - *
- * Note: the `track by` expression must come last - after any filters, and the alias expression. - *
+ * *Default tracking: $id()*: `item in items` is equivalent to `item in items track by $id(item)`. + * This implies that the DOM elements will be associated by item identity in the collection. * - * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements - * will be associated by item identity in the array. + * The built-in `$id()` function can be used to assign a unique + * `$$hashKey` property to each item in the collection. This property is then used as a key to associated DOM elements + * with the corresponding item in the collection by identity. Moving the same object would move + * the DOM element in the same way in the DOM. + * Note that the default id function does not support duplicate primitive values (`number`, `string`), + * but supports duplictae non-primitive values (`object`) that are *equal* in shape. * - * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique - * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements - * with the corresponding item in the array by identity. Moving the same object in array would move the DOM - * element in the same way in the DOM. + * *Custom Expression*: It is possible to use any AngularJS expression to compute the tracking + * id, for example with a function, or using a property on the collection items. + * `item in items track by item.id` is a typical pattern when the items have a unique identifier, + * e.g. database id. In this case the object identity does not matter. Two objects are considered + * equivalent as long as their `id` property is same. + * Tracking by unique identifier is the most performant way and should be used whenever possible. * - * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this - * case the object identity does not matter. Two objects are considered equivalent as long as their `id` - * property is same. + * *$index*: This special property tracks the collection items by their index, and + * re-uses the DOM elements that match that index, e.g. `item in items track by $index`. This can + * be used for a performance improvement if no unique identfier is available and the identity of + * the collection items cannot be easily computed. It also allows duplicates. * - * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter - * to items in conjunction with a tracking expression. + *
+ * Note: Re-using DOM elements can have unforeseen effects. Read the + * {@link ngRepeat#tracking-and-duplicates section on tracking and duplicates} for + * more info. + *
+ * + *
+ * Note: the `track by` expression must come last - after any filters, and the alias expression: + * `item in items | filter:searchText as results track by item.id` + *
* * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message @@ -241,10 +332,10 @@ * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after * the items have been processed through the filter. * - * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax so it can be used only at the end - * (and not as operator, inside an expression). + * Please note that `as [variable name] is not an operator but rather a part of ngRepeat + * micro-syntax so it can be used only after all filters (and not as operator, inside an expression). * - * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . + * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results track by item.id` . * * @example * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed @@ -255,7 +346,7 @@ I have {{friends.length}} friends. They are: