实现链式数据显示的自定义指令
variable chain:
{"id":"1","name":"first","next":{
"id":"2","name":"second","next":{
"id":"3","name":"third","next":{"id":"4","name":"fourth"}
}
}
}
<div *lyChain="let item of chain" [nextVal]="next" >{{item.id}}:{{item.name}}</div>
遍历chain直到item.next不存在。
##什么是结构型指令? 结构型指令的职责是 HTML 布局。 它们塑造或重塑 DOM 的结构,比如添加、移除或维护这些元素。
像其它指令一样,你可以把结构型指令应用到一个宿主元素上。 然后它就可以对宿主元素及其子元素做点什么。
结构型指令非常容易识别。 在这个例子中,星号(*)被放在指令的属性名之前。
不是通常的模板表达式。 Angular 会解开这个语法糖,变成一个 标记,包裹着宿主元素及其子元素。 每个结构型指令都可以用这个模板做点不同的事情。
星号是一个用来简化更复杂语法的“语法糖”。 从内部实现来说,Angular 把 *ngIf 属性 翻译成一个 元素 并用它来包裹宿主元素。
/**
* @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';
/**
*
*/
export class NgForOfContext<T> {
constructor(
public $implicit: T, public ngForOf: NgIterable<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:
*
* - `$implicit: T`: The value of the individual items in the iterable (`ngForOf`).
* - `ngForOf: NgIterable<T>`: The value of the iterable expression. Useful when the expression is
* more complex then a property access, for example when using the async pipe (`userStreams |
* async`).
* - `index: number`: The index of the current item in the iterable.
* - `first: boolean`: True when the item is the first item in the iterable.
* - `last: boolean`: True when the item is the last item in the iterable.
* - `even: boolean`: True when the item has an even index in the iterable.
* - `odd: boolean`: True when the item has an odd index in the iterable.
*
*
* <li *ngFor="let user of userObservable | async as users; index as i; first as isFirst">
* {{i}}/{{users.length}}. {{user}} <span *ngIf="isFirst">default</span>
* </li>
*
*
* ### 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; index as i; trackBy: trackByFn">...</li>`
*
* With `<ng-template>` element:
*
*
* <ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
* <li>...</li>
* </ng-template>
*
*
* ### Example
*
* See a [live demo](http://plnkr.co/edit/KVuXxDp0qinGDyo307QW?p=preview) for a more detailed
* example.
*
*
*/
@Directive({selector: '[ngFor][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 = null;
private _trackByFn: TrackByFunction<T>;
constructor(
private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForOfContext<T>>,
private _differs: IterableDiffers) {}
@Input()
set ngForTemplate(value: TemplateRef<NgForOfContext<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.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 NgForOfContext<T>(null !, this.ngForOf, -1, -1), currentIndex);
const tuple = new RecordViewTuple<T>(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<NgForOfContext<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<NgForOfContext<T>>>this._viewContainer.get(i);
viewRef.context.index = i;
viewRef.context.count = ilen;
}
changes.forEachIdentityChange((record: any) => {
const viewRef =
<EmbeddedViewRef<NgForOfContext<T>>>this._viewContainer.get(record.currentIndex);
viewRef.context.$implicit = record.item;
});
}
private _perViewChange(
view: EmbeddedViewRef<NgForOfContext<T>>, record: IterableChangeRecord<any>) {
view.context.$implicit = record.item;
}
}
class RecordViewTuple<T> {
constructor(public record: any, public view: EmbeddedViewRef<NgForOfContext<T>>) {}
}
export function getTypeNameForDebugging(type: any): string {
return type['name'] || typeof type;
}