Im ersten Teil haben wir einen Blick auf die Möglichkeiten geworfen die Angular uns mittels ngContent, ngTemplateOutlet und ngTemplateRef bietet. Im zweiten Teil dreht sich alles um Komponenten die ganz dynamische ihr Template anpassen.
ComponentFactory
In diesem Beispiel werfen wir einen Blick auf eine Komponente die bei ihrer Verwendung dynamisch weitere Komponenten erzeugen und sie dem DOM Tree hinzufügt. Das Komponenten-Template ist erwartungsgemäß überschaubar:
view-container-ref.compoent.html
<div #content></div>
Innerhalb unseres Komponenten-Templates definieren wir lediglich eine Stelle an der später unsere dynamischen Komponenten eingefügt werden sollen. Spannender ist in diesem Fall der Controller:
view-container-ref.component.ts
@ViewChild('content', { read: ViewContainerRef }) content: ViewContainerRef; boxFactory: ComponentFactory<BoxWithDefaultComponent> constructor(r: ComponentFactoryResolver) { this.boxFactory = r.resolveComponentFactory(BoxWithDefaultComponent); } ngAfterViewInit() { let box1 = this.content.createComponent(this.boxFactory); box1.instance.title = "Dynamic (1)"; let box2 = this.content.createComponent(this.boxFactory); box2.instance.title = "Dynamic (2)"; }
Im ersten Schritt verwenden wir den alt bekannten @ViewChild Decorator um Zugriff auf ein Element unseres Templates zu erhalten. Anders als vielleicht erwartet müssen wir jedoch zusätzlich angeben welche Art von Referenz wir an dieser Stelle verwenden möchten. Würden wir die read Property weglassen, würde Angular in diesem Fall dafür sorgen das wir das DIV-Element injiziert bekommen. Um später jedoch dynamische Inhalte hinzufügen zu können benötigen wir eine ViewContainerRef. Um dies Angular deutlich zu machen verwenden wir als zweiten Parameter des ViewChild-Decorator eine entsprechende read-Property
Für die Erzeugung von Komponenten benötigen wir nun eine entsprechende Factory. Um die korrekte Factory für unsere gewünschte Komponente zu erhalten lassen wir uns im Konstruktor eine Instanz vom Typ ComponentFactoryResolver geben, mit dessen Methode resolveComponentFactory wir eine Factory-Implementierung erhaltenw welche den übergebenen Komponente-Typ erzeugen kann.
Nun haben wir zum einen die Referenz auf die Stelle in unserem Template an dem wir Inhalte einfügen wollen und eine Factory die unsere konkrete Komponente erzeugen kann. In der Methode ngAfterViewInit Methode fügen wir so über die ViewContainerRef und der Methode createComponent(FACTORY) neue Komponenten zu unserem Template hinzu. Über die Property instance des Rückgabewertes erhalten wie dann die erzeugte Komponente über dessen Property wir die Komponente mit Werten versorgen können.
Ein kleiner Schritt ist noch nötig: wir verwenden an dieser Stelle nicht direkt die Komponente BoxWithDefaultComponent damit der Angular Compiler aus Optimierungsgründe diese Komponente nicht aus dem Bundle wirft, benötigen wir noch einen Eintrag in der app.module.ts :
@NgModule({ ... entryComponents: [BoxWithDefaultComponent], ... })
createEmbeddedView
Neben dem Hinzufügen von „einfachen“ Komponenten bietet uns die ViewContainerRef auch die Möglichkeit dynamisch Templates zu verwenden. In diesem Beispiel Verwenden wir eine Direktive um eine eigene Iteration von Elementen zu implementieren:
news-repeat.directive.ts
@Directive({ selector: '[appNewsRepeat]' }) export class NewsRepeatDirective { @Input('appNewsRepeat') news: string[]; @ContentChild(TemplateRef) tmp: TemplateRef<any>; constructor(private view: ViewContainerRef) { } ngOnInit() { this.news.forEach(text => { let title = text.substr(0, 20) + '...' let dynamicView = this.view.createEmbeddedView(this.tmp, { params: { title, text } }); }); } }
Unsere einfache Direktive soll eine Liste von News-Beiträgen die sie erhält aufbereiten und als Liste darstellen. Anders als bisher lassen wir uns die ViewContainerRef per Konstruktor geben (unsere Direktive hat ja kein eigenes Template in dem wir eine Stelle „markieren“ können, also lassen wir uns damit die Referenz auf das Element geben an dem unsere Direktive verwendet wird). Es folgt die Injizierung der Referenz auf das vom Verwender bereit gestellte Template mittels ContetnChild.
Die eigentliche spannende Aufgabe übernimmt dann die ngOnInit Methode, die auf Basis der übergebenen Texte einen Titel extrahiert und dann für jedes Text-Element mittels createEmbeddedView ein Template hinzufügt. Um dem Anwender in seinem bereitgestellten Template den Zugriff auf das aktuelle Iterations-Objekt zu geben , definieren wir ein Übergabeobjekt params das wir für jede Iteration mit den entsprechenden Werten befüllen. Diese Werte sind nun im Template des Verwenders erreichbar:
Verwendung
<div [appNewsRepeat]="news"> <ng-template let-entry="params"> <div> <div> <h1>{{entry.title}}</h1> </div> <hr/> <div>{{entry.text}}</div> </div> </ng-template> </div>
Wir übergeben an unsere Direktive die News-Beiträge mittels [appNewsRepeat]=“news“ (String-Array) und definieren schließlich ein Template mit einer Variable let-entry um Zugriff auf das Übergabeobjekt =“params“ zu bekommen, um damit dann unser individuelles Template zu erstellen.
Der zweite Teil hat gezeigt das auch sehr dynamische Komponenten relativ leicht selber zu erstellen sind. Angular bietet hier diverse Möglichkeiten und schreibt WIEDERVERWENDBARKEIT groß.