Angular2: ¿Cómo se expande ngfor?
angular2-template (2)
Sé que las reglas del libro de texto sobre eso
<div *ngFor="let foo of foobars">{{foo.stuff}}</div>
convierte en
<template ngFor let-foo="$implicit" [ngForOf]="foobars"><div>...</div></template>
.
Mi pregunta es doble:
- ¿CÓMO?
- ¿Qué debo hacer para aprovechar este mecanismo ("microsintaxis")?
Es decir, convertir
<div *myDirective="item">{{item.stuff}}</div>
en
<template myDirective let-item="$implicit"><div>{{item.stuff}}</div></template>
?
Desde que leí el código fuente de ngFor de arriba a abajo, solo puedo suponer que esta magia oscura está en el compilador en alguna parte, he estado arriba y abajo del github angular, pero no puedo señalarlo. ¡Ayuda!
Sí, toda la magia ocurre en el compilador.
Tomemos esta plantilla:
<div *ngFor="let foo of foobars">{{foo}}</div>
Primero se transformará a lo siguiente:
<div template="ngFor let foo of foobars>{{foo}}</div>
Y entonces:
<template ngFor let-foo [ngForOf]="foobars"><div>{{foo}}</div></template>
Primero se genera un nodo de árbol ast (nodo de árbol de sintaxis abstracta) y luego toda la magia ocurre en
TemplateParseVisitor.visitElement
(
https://github.com/angular/angular/blob/2.0.0-rc.4/modules/%40angular/compiler/src/template_parser.ts#L284
) específicamente en la parte inferior (
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;
Este método devuelve
EmbeddedTemplateAst
.
Es lo mismo que:
<template ngFor let-foo [ngForOf]="foobars"><div>{{foo}}</div></template>
Si quieres girar:
<div *myDirective="item">{{item.stuff}}</div>
dentro
<template myDirective let-item><div>{{item.stuff}}</div></template>
entonces necesita usar la siguiente sintaxis:
<div *myDirective="let item">{{item.stuff}}</div>
Pero en este caso no pasarás contexto. Su directiva estructural personalizada podría verse así:
@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
}
}
Y puedes usarlo como:
<div *myDirective="item">{{item.stuff}}</div>
||
//
<div template="myDirective:item">{{item.stuff}}</div>
||
//
<template [myDirective]="item">
<div>{{item.stuff}}</div>
</template>
Espero que esto le ayude a comprender cómo funcionan las directivas estructurales.
Actualizar:
Veamos cómo funciona ( plunker )
*dir="let foo v foobars" => [dirV]="foobars"
Entonces puede escribir la siguiente directiva:
@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]
}
Aquí está el Plunker correspondiente
Ver también
- 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
Ejemplo en vivo que puedes encontrar aquí https://alexzuza.github.io/enjoy-ng-parser/
*ngFor
,
*ngIf
, ... son directivas estructurales.
Aplíquelo en un elemento
<template>
o prefije con un
*
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(); } } }