Note, this affects the underlying class and should not affect usage. DEPRECATION: - the `NgFor` class is now deprecated. Use `NgForOf<T>` instead. IMPORTANT: Only the `NgFor` class is deprecated, not the `ngFor` directive. The `*ngFor` and related directives are unaffected by this change as references to the `NgFor` class generated from templates will be automatically converted to references to `NgForOf<T>` without requiring any template modifications. - `TrackByFn` is now deprecated. Use `TrackByFunction<T>` instead. Migration: - Replace direct references to the `NgFor` class to `NgForOf<any>`. - Replace references to `TrackByFn` to `TrackByFunction<any>`. BREAKING CHANGE: A definition of `Iterable<T>` is now required to correctly compile Angular applications. Support for `Iterable<T>` is not required at runtime but a type definition `Iterable<T>` must be available. `NgFor`, and now `NgForOf<T>`, already supports `Iterable<T>` at runtime. With this change the type definition is updated to reflect this support. Migration: - add "es2015.iterable.ts" to your tsconfig.json "libs" fields. Part of #12398 PR Close #14104
256 lines
11 KiB
TypeScript
256 lines
11 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {ChangeDetectorRef, Directive, DoCheck, EmbeddedViewRef, Input, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDiffers, NgIterable, OnChanges, SimpleChanges, TemplateRef, TrackByFunction, ViewContainerRef, forwardRef, isDevMode} from '@angular/core';
|
|
|
|
import {getTypeNameForDebugging} from '../facade/lang';
|
|
|
|
export class NgForOfRow<T> {
|
|
constructor(public $implicit: T, public index: number, public count: number) {}
|
|
|
|
get first(): boolean { return this.index === 0; }
|
|
|
|
get last(): boolean { return this.index === this.count - 1; }
|
|
|
|
get even(): boolean { return this.index % 2 === 0; }
|
|
|
|
get odd(): boolean { return !this.even; }
|
|
}
|
|
|
|
/**
|
|
* The `NgForOf` directive instantiates a template once per item from an iterable. The context
|
|
* for each instantiated template inherits from the outer context with the given loop variable
|
|
* set to the current item from the iterable.
|
|
*
|
|
* ### Local Variables
|
|
*
|
|
* `NgForOf` provides several exported values that can be aliased to local variables:
|
|
*
|
|
* * `index` will be set to the current loop iteration for each template context.
|
|
* * `first` will be set to a boolean value indicating whether the item is the first one in the
|
|
* iteration.
|
|
* * `last` will be set to a boolean value indicating whether the item is the last one in the
|
|
* iteration.
|
|
* * `even` will be set to a boolean value indicating whether this item has an even index.
|
|
* * `odd` will be set to a boolean value indicating whether this item has an odd index.
|
|
*
|
|
* ### Change Propagation
|
|
*
|
|
* When the contents of the iterator changes, `NgForOf` makes the corresponding changes to the DOM:
|
|
*
|
|
* * When an item is added, a new instance of the template is added to the DOM.
|
|
* * When an item is removed, its template instance is removed from the DOM.
|
|
* * When items are reordered, their respective templates are reordered in the DOM.
|
|
* * Otherwise, the DOM element for that item will remain the same.
|
|
*
|
|
* Angular uses object identity to track insertions and deletions within the iterator and reproduce
|
|
* those changes in the DOM. This has important implications for animations and any stateful
|
|
* controls (such as `<input>` elements which accept user input) that are present. Inserted rows can
|
|
* be animated in, deleted rows can be animated out, and unchanged rows retain any unsaved state
|
|
* such as user input.
|
|
*
|
|
* It is possible for the identities of elements in the iterator to change while the data does not.
|
|
* This can happen, for example, if the iterator produced from an RPC to the server, and that
|
|
* RPC is re-run. Even if the data hasn't changed, the second response will produce objects with
|
|
* different identities, and Angular will tear down the entire DOM and rebuild it (as if all old
|
|
* elements were deleted and all new elements inserted). This is an expensive operation and should
|
|
* be avoided if possible.
|
|
*
|
|
* To customize the default tracking algorithm, `NgForOf` supports `trackBy` option.
|
|
* `trackBy` takes a function which has two arguments: `index` and `item`.
|
|
* If `trackBy` is given, Angular tracks changes by the return value of the function.
|
|
*
|
|
* ### Syntax
|
|
*
|
|
* - `<li *ngFor="let item of items; let i = index; trackBy: trackByFn">...</li>`
|
|
* - `<li template="ngFor let item of items; let i = index; trackBy: trackByFn">...</li>`
|
|
*
|
|
* With `<template>` element:
|
|
*
|
|
* ```
|
|
* <template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
|
|
* <li>...</li>
|
|
* </template>
|
|
* ```
|
|
*
|
|
* ### Example
|
|
*
|
|
* See a [live demo](http://plnkr.co/edit/KVuXxDp0qinGDyo307QW?p=preview) for a more detailed
|
|
* example.
|
|
*
|
|
* @stable
|
|
*/
|
|
@Directive({
|
|
selector: '[ngFor][ngForOf]',
|
|
providers: [{provide: forwardRef(() => NgFor), useExisting: forwardRef(() => NgForOf)}]
|
|
})
|
|
export class NgForOf<T> implements DoCheck,
|
|
OnChanges {
|
|
@Input() ngForOf: NgIterable<T>;
|
|
@Input()
|
|
set ngForTrackBy(fn: TrackByFunction<T>) {
|
|
if (isDevMode() && fn != null && typeof fn !== 'function') {
|
|
// TODO(vicb): use a log service once there is a public one available
|
|
if (<any>console && <any>console.warn) {
|
|
console.warn(
|
|
`trackBy must be a function, but received ${JSON.stringify(fn)}. ` +
|
|
`See https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html#!#change-propagation for more information.`);
|
|
}
|
|
}
|
|
this._trackByFn = fn;
|
|
}
|
|
|
|
get ngForTrackBy(): TrackByFunction<T> { return this._trackByFn; }
|
|
|
|
private _differ: IterableDiffer<T> = null;
|
|
private _trackByFn: TrackByFunction<T>;
|
|
|
|
constructor(
|
|
private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForOfRow<T>>,
|
|
private _differs: IterableDiffers, private _cdr: ChangeDetectorRef) {}
|
|
|
|
@Input()
|
|
set ngForTemplate(value: TemplateRef<NgForOfRow<T>>) {
|
|
// TODO(TS2.1): make TemplateRef<Partial<NgForRowOf<T>>> once we move to TS v2.1
|
|
// The current type is too restrictive; a template that just uses index, for example,
|
|
// should be acceptable.
|
|
if (value) {
|
|
this._template = value;
|
|
}
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges): void {
|
|
if ('ngForOf' in changes) {
|
|
// React on ngForOf changes only once all inputs have been initialized
|
|
const value = changes['ngForOf'].currentValue;
|
|
if (!this._differ && value) {
|
|
try {
|
|
this._differ = this._differs.find(value).create(this._cdr, this.ngForTrackBy);
|
|
} catch (e) {
|
|
throw new Error(
|
|
`Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ngDoCheck(): void {
|
|
if (this._differ) {
|
|
const changes = this._differ.diff(this.ngForOf);
|
|
if (changes) this._applyChanges(changes);
|
|
}
|
|
}
|
|
|
|
private _applyChanges(changes: IterableChanges<T>) {
|
|
const insertTuples: RecordViewTuple<T>[] = [];
|
|
changes.forEachOperation(
|
|
(item: IterableChangeRecord<any>, adjustedPreviousIndex: number, currentIndex: number) => {
|
|
if (item.previousIndex == null) {
|
|
const view = this._viewContainer.createEmbeddedView(
|
|
this._template, new NgForOfRow(null, null, null), currentIndex);
|
|
const tuple = new RecordViewTuple(item, view);
|
|
insertTuples.push(tuple);
|
|
} else if (currentIndex == null) {
|
|
this._viewContainer.remove(adjustedPreviousIndex);
|
|
} else {
|
|
const view = this._viewContainer.get(adjustedPreviousIndex);
|
|
this._viewContainer.move(view, currentIndex);
|
|
const tuple = new RecordViewTuple(item, <EmbeddedViewRef<NgForOfRow<T>>>view);
|
|
insertTuples.push(tuple);
|
|
}
|
|
});
|
|
|
|
for (let i = 0; i < insertTuples.length; i++) {
|
|
this._perViewChange(insertTuples[i].view, insertTuples[i].record);
|
|
}
|
|
|
|
for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
|
|
const viewRef = <EmbeddedViewRef<NgForOfRow<T>>>this._viewContainer.get(i);
|
|
viewRef.context.index = i;
|
|
viewRef.context.count = ilen;
|
|
}
|
|
|
|
changes.forEachIdentityChange((record: any) => {
|
|
const viewRef = <EmbeddedViewRef<NgForOfRow<T>>>this._viewContainer.get(record.currentIndex);
|
|
viewRef.context.$implicit = record.item;
|
|
});
|
|
}
|
|
|
|
private _perViewChange(view: EmbeddedViewRef<NgForOfRow<T>>, record: IterableChangeRecord<any>) {
|
|
view.context.$implicit = record.item;
|
|
}
|
|
}
|
|
|
|
class RecordViewTuple<T> {
|
|
constructor(public record: any, public view: EmbeddedViewRef<NgForOfRow<T>>) {}
|
|
}
|
|
|
|
/**
|
|
* The `NgFor` directive instantiates a template once per item from an iterable. The context
|
|
* for each instantiated template inherits from the outer context with the given loop variable
|
|
* set to the current item from the iterable.
|
|
*
|
|
* ### Local Variables
|
|
*
|
|
* `NgFor` provides several exported values that can be aliased to local variables:
|
|
*
|
|
* * `index` will be set to the current loop iteration for each template context.
|
|
* * `first` will be set to a boolean value indicating whether the item is the first one in the
|
|
* iteration.
|
|
* * `last` will be set to a boolean value indicating whether the item is the last one in the
|
|
* iteration.
|
|
* * `even` will be set to a boolean value indicating whether this item has an even index.
|
|
* * `odd` will be set to a boolean value indicating whether this item has an odd index.
|
|
*
|
|
* ### Change Propagation
|
|
*
|
|
* When the contents of the iterator changes, `NgFor` makes the corresponding changes to the DOM:
|
|
*
|
|
* * When an item is added, a new instance of the template is added to the DOM.
|
|
* * When an item is removed, its template instance is removed from the DOM.
|
|
* * When items are reordered, their respective templates are reordered in the DOM.
|
|
* * Otherwise, the DOM element for that item will remain the same.
|
|
*
|
|
* Angular uses object identity to track insertions and deletions within the iterator and reproduce
|
|
* those changes in the DOM. This has important implications for animations and any stateful
|
|
* controls (such as `<input>` elements which accept user input) that are present. Inserted rows can
|
|
* be animated in, deleted rows can be animated out, and unchanged rows retain any unsaved state
|
|
* such as user input.
|
|
*
|
|
* It is possible for the identities of elements in the iterator to change while the data does not.
|
|
* This can happen, for example, if the iterator produced from an RPC to the server, and that
|
|
* RPC is re-run. Even if the data hasn't changed, the second response will produce objects with
|
|
* different identities, and Angular will tear down the entire DOM and rebuild it (as if all old
|
|
* elements were deleted and all new elements inserted). This is an expensive operation and should
|
|
* be avoided if possible.
|
|
*
|
|
* To customize the default tracking algorithm, `NgFor` supports `trackBy` option.
|
|
* `trackBy` takes a function which has two arguments: `index` and `item`.
|
|
* If `trackBy` is given, Angular tracks changes by the return value of the function.
|
|
*
|
|
* ### Syntax
|
|
*
|
|
* - `<li *ngFor="let item of items; let i = index; trackBy: trackByFn">...</li>`
|
|
* - `<li template="ngFor let item of items; let i = index; trackBy: trackByFn">...</li>`
|
|
*
|
|
* With `<template>` element:
|
|
*
|
|
* ```
|
|
* <template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
|
|
* <li>...</li>
|
|
* </template>
|
|
* ```
|
|
*
|
|
* ### Example
|
|
*
|
|
* See a [live demo](http://plnkr.co/edit/KVuXxDp0qinGDyo307QW?p=preview) for a more detailed
|
|
* example.
|
|
*
|
|
* @deprecated v4.0.0 - Use `NgForOf<T>` instead.
|
|
*/
|
|
export class NgFor extends NgForOf<any> {} |