Angular2: how ngfor extends
I know that the textbook rules on this <div *ngFor="let foo of foobars">{{foo.stuff}}</div> turn into <template ngFor let-foo="$implicit" [ngForOf]="foobars"><div>...</div></template> . My question is twofold:
- AS?
- What do I need to do to use this mechanism ("microsyntax") myself?
Those. turn <div *myDirective="item">{{item.stuff}}</div> into <template myDirective let-item="$implicit"><div>{{item.stuff}}</div></template> ?
Since I am reading the ngFor source code from top to bottom, I can only assume that this dark magic is in the compiler somewhere, I was up and down gythub angular, but I can’t put it on it. Help!
Yes, all the magic happens in the compiler.
Take this template:
<div *ngFor="let foo of foobars">{{foo}}</div> First, it is converted to the following:
<div template="ngFor let foo of foobars>{{foo}}</div> And then:
<template ngFor let-foo [ngForOf]="foobars"><div>{{foo}}</div></template> In Angular2 rc.4, it looks like this: 
First an ast node tree is generated (abstract node syntax tree), and then all the magic happens in TemplateParseVisitor.visitElement ( https://github.com/angular/angular/blob/2.0.0-rc.4/modules/%40angular/compiler /src/template_parser.ts#L284 ), in particular below ( 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; This method returns EmbeddedTemplateAst . This is the same as:
<template ngFor let-foo [ngForOf]="foobars"><div>{{foo}}</div></template> If you want to rotate:
<div *myDirective="item">{{item.stuff}}</div> at
<template myDirective let-item><div>{{item.stuff}}</div></template> then you need to use the following syntax:
<div *myDirective="let item">{{item.stuff}}</div> But in this case, you will not go through the context. Your custom structured directive might look like this:
@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 } } And you can use it like:
<div *myDirective="item">{{item.stuff}}</div> || \/ <div template="myDirective:item">{{item.stuff}}</div> || \/ <template [myDirective]="item"> <div>{{item.stuff}}</div> </template> Hope this helps you understand how structural directives work.
Update:
See how it works ( plunker )
*dir="let foo v foobars" => [dirV]="foobars" So you can write the following directive:
@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] } Here is the relevant Plunker
see also
- 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
In a live example, you can find here https://alexzuza.imtqy.com/enjoy-ng-parser/
*ngFor , *ngIf , ... are structural directives.
Either apply it to the <template> element, or prefix it with *
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(); } } } 