Angular2:ngfor是如何扩展的
Angular2: How is ngfor expanded
我知道教科书规定 <div *ngFor="let foo of foobars">{{foo.stuff}}</div>
变成 <template ngFor let-foo="$implicit" [ngForOf]="foobars"><div>...</div></template>
。我的问题有两个:
- 怎么做?
- 我自己需要做什么才能利用此机制 ("microsyntax")?
即将<div *myDirective="item">{{item.stuff}}</div>
变成<template myDirective let-item="$implicit"><div>{{item.stuff}}</div></template>
?
自从我从头到尾阅读了 ngFor 的源代码后,我只能假设这个黑魔法在编译器的某个地方,我一直在 angular github,但我可以把我的手指放在上面。 求助!
*ngFor
, *ngIf
, ... 是结构指令。
将其应用于 <template>
元素或在其前面加上 *
https://angular.io/docs/ts/latest/guide/structural-directives.html#!#unless
import { Directive, Input } from '@angular/core';
import { TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({ selector: '[myUnless]' })
export class UnlessDirective {
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) { }
@Input() set myUnless(condition: boolean) {
if (!condition) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}
是的,所有的魔法都发生在编译器中。
让我们使用这个模板:
<div *ngFor="let foo of foobars">{{foo}}</div>
首先会转化为:
<div template="ngFor let foo of foobars>{{foo}}</div>
然后:
<template ngFor let-foo [ngForOf]="foobars"><div>{{foo}}</div></template>
在 Angular2 rc.4 中它看起来像这样
首先生成ast树节点(抽象语法树节点)然后所有魔法都发生在TemplateParseVisitor.visitElement
(https://github.com/angular/angular/blob/2.0.0-rc.4/modules/%40angular/compiler/src/template_parser.ts#L284) specifically at the bottom (https://github.com/angular/angular/blob/2.0.0-rc.4/modules/%40angular/compiler/src/template_parser.ts#L394)
if (hasInlineTemplates) {
var templateCssSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
var templateDirectiveMetas = this._parseDirectives(this.selectorMatcher, templateCssSelector);
var templateDirectiveAsts = this._createDirectiveAsts(
true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [],
element.sourceSpan, []);
var templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, templateElementOrDirectiveProps, templateDirectiveAsts);
this._assertNoComponentsNorElementBindingsOnTemplate(
templateDirectiveAsts, templateElementProps, element.sourceSpan);
var templateProviderContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, parent.isTemplateElement,
templateDirectiveAsts, [], [], element.sourceSpan);
templateProviderContext.afterElement();
parsedElement = new EmbeddedTemplateAst(
[], [], [], templateElementVars, templateProviderContext.transformedDirectiveAsts,
templateProviderContext.transformProviders,
templateProviderContext.transformedHasViewContainer, [parsedElement], ngContentIndex,
element.sourceSpan);
}
return parsedElement;
此方法returnsEmbeddedTemplateAst
。等同于:
<template ngFor let-foo [ngForOf]="foobars"><div>{{foo}}</div></template>
如果要转:
<div *myDirective="item">{{item.stuff}}</div>
进入
<template myDirective let-item><div>{{item.stuff}}</div></template>
那么您需要使用以下语法:
<div *myDirective="let item">{{item.stuff}}</div>
但在这种情况下,您不会传递上下文。
您的自定义结构指令可能如下所示:
@Directive({
selector: '[myDirective]'
})
export class MyDirective {
constructor(
private _viewContainer: ViewContainerRef,
private _templateRef: TemplateRef<any>) {}
@Input() set myDirective(prop: Object) {
this._viewContainer.clear();
this._viewContainer.createEmbeddedView(this._templateRef, prop); <== pass context
}
}
您可以像这样使用它:
<div *myDirective="item">{{item.stuff}}</div>
||
\/
<div template="myDirective:item">{{item.stuff}}</div>
||
\/
<template [myDirective]="item">
<div>{{item.stuff}}</div>
</template>
我希望这能帮助您了解结构指令的工作原理。
更新:
让我们看看它是如何工作的(plunker)
*dir="let foo v foobars" => [dirV]="foobars"
所以你可以编写如下指令:
@Directive({
selector: '[dir]'
})
export class MyDirective {
@Input()
dirV: any;
@Input()
dirK: any;
ngAfterViewInit() {
console.log(this.dirV, this.dirK);
}
}
@Component({
selector: 'my-app',
template: `<h1>Angular 2 Systemjs start</h1>
<div *dir="let foo v foobars k arr">{ foo }</div>
`,
directives: [MyDirective]
})
export class AppComponent {
foobars = [1, 2, 3];
arr = [3,4,5]
}
这里对应的是Plunker
另见
- https://angular.io/docs/ts/latest/guide/structural-directives.html#!#the-asterisk-effect
- https://teropa.info/blog/2016/03/06/writing-an-angular-2-template-directive.html
- https://www.bennadel.com/blog/3076-creating-an-index-loop-structural-directive-in-angular-2-beta-14.htm
- https://egghead.io/lessons/angular-2-write-a-structural-directive-in-angular-2
您可以在此处找到实例 https://alexzuza.github.io/enjoy-ng-parser/
我知道教科书规定 <div *ngFor="let foo of foobars">{{foo.stuff}}</div>
变成 <template ngFor let-foo="$implicit" [ngForOf]="foobars"><div>...</div></template>
。我的问题有两个:
- 怎么做?
- 我自己需要做什么才能利用此机制 ("microsyntax")?
即将<div *myDirective="item">{{item.stuff}}</div>
变成<template myDirective let-item="$implicit"><div>{{item.stuff}}</div></template>
?
自从我从头到尾阅读了 ngFor 的源代码后,我只能假设这个黑魔法在编译器的某个地方,我一直在 angular github,但我可以把我的手指放在上面。 求助!
*ngFor
, *ngIf
, ... 是结构指令。
将其应用于 <template>
元素或在其前面加上 *
https://angular.io/docs/ts/latest/guide/structural-directives.html#!#unless
import { Directive, Input } from '@angular/core'; import { TemplateRef, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[myUnless]' }) export class UnlessDirective { constructor( private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef ) { } @Input() set myUnless(condition: boolean) { if (!condition) { this.viewContainer.createEmbeddedView(this.templateRef); } else { this.viewContainer.clear(); } } }
是的,所有的魔法都发生在编译器中。
让我们使用这个模板:
<div *ngFor="let foo of foobars">{{foo}}</div>
首先会转化为:
<div template="ngFor let foo of foobars>{{foo}}</div>
然后:
<template ngFor let-foo [ngForOf]="foobars"><div>{{foo}}</div></template>
在 Angular2 rc.4 中它看起来像这样
首先生成ast树节点(抽象语法树节点)然后所有魔法都发生在TemplateParseVisitor.visitElement
(https://github.com/angular/angular/blob/2.0.0-rc.4/modules/%40angular/compiler/src/template_parser.ts#L284) specifically at the bottom (https://github.com/angular/angular/blob/2.0.0-rc.4/modules/%40angular/compiler/src/template_parser.ts#L394)
if (hasInlineTemplates) {
var templateCssSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
var templateDirectiveMetas = this._parseDirectives(this.selectorMatcher, templateCssSelector);
var templateDirectiveAsts = this._createDirectiveAsts(
true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [],
element.sourceSpan, []);
var templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, templateElementOrDirectiveProps, templateDirectiveAsts);
this._assertNoComponentsNorElementBindingsOnTemplate(
templateDirectiveAsts, templateElementProps, element.sourceSpan);
var templateProviderContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, parent.isTemplateElement,
templateDirectiveAsts, [], [], element.sourceSpan);
templateProviderContext.afterElement();
parsedElement = new EmbeddedTemplateAst(
[], [], [], templateElementVars, templateProviderContext.transformedDirectiveAsts,
templateProviderContext.transformProviders,
templateProviderContext.transformedHasViewContainer, [parsedElement], ngContentIndex,
element.sourceSpan);
}
return parsedElement;
此方法returnsEmbeddedTemplateAst
。等同于:
<template ngFor let-foo [ngForOf]="foobars"><div>{{foo}}</div></template>
如果要转:
<div *myDirective="item">{{item.stuff}}</div>
进入
<template myDirective let-item><div>{{item.stuff}}</div></template>
那么您需要使用以下语法:
<div *myDirective="let item">{{item.stuff}}</div>
但在这种情况下,您不会传递上下文。 您的自定义结构指令可能如下所示:
@Directive({
selector: '[myDirective]'
})
export class MyDirective {
constructor(
private _viewContainer: ViewContainerRef,
private _templateRef: TemplateRef<any>) {}
@Input() set myDirective(prop: Object) {
this._viewContainer.clear();
this._viewContainer.createEmbeddedView(this._templateRef, prop); <== pass context
}
}
您可以像这样使用它:
<div *myDirective="item">{{item.stuff}}</div>
||
\/
<div template="myDirective:item">{{item.stuff}}</div>
||
\/
<template [myDirective]="item">
<div>{{item.stuff}}</div>
</template>
我希望这能帮助您了解结构指令的工作原理。
更新:
让我们看看它是如何工作的(plunker)
*dir="let foo v foobars" => [dirV]="foobars"
所以你可以编写如下指令:
@Directive({
selector: '[dir]'
})
export class MyDirective {
@Input()
dirV: any;
@Input()
dirK: any;
ngAfterViewInit() {
console.log(this.dirV, this.dirK);
}
}
@Component({
selector: 'my-app',
template: `<h1>Angular 2 Systemjs start</h1>
<div *dir="let foo v foobars k arr">{ foo }</div>
`,
directives: [MyDirective]
})
export class AppComponent {
foobars = [1, 2, 3];
arr = [3,4,5]
}
这里对应的是Plunker
另见
- https://angular.io/docs/ts/latest/guide/structural-directives.html#!#the-asterisk-effect
- https://teropa.info/blog/2016/03/06/writing-an-angular-2-template-directive.html
- https://www.bennadel.com/blog/3076-creating-an-index-loop-structural-directive-in-angular-2-beta-14.htm
- https://egghead.io/lessons/angular-2-write-a-structural-directive-in-angular-2
您可以在此处找到实例 https://alexzuza.github.io/enjoy-ng-parser/