Last active
June 12, 2021 10:42
Experimenting With Dynamic Template Rendering In Angular 2 RC 1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Import the core angular services. | |
import { Component } from "@angular/core"; | |
// Import the application components and services. | |
import { DynamicRepeaterComponent } from "./dynamic-repeater.component"; | |
@Component({ | |
selector: "my-app", | |
directives: [ DynamicRepeaterComponent ], | |
// In this view, we're passing a dynamic TemplateRef to the DynamicRepeater | |
// component. We're not passing it in like a property; rather, we're "tagging" it | |
// with the "#itemRenderer" handle. Then, the DynamicRepeater is going to query its | |
// content (via ContentChild) for the template reference. When this TemplateRef is | |
// "stamped out", it will make several local view variables available: | |
// -- | |
// * index | |
// * item | |
// -- | |
// Here, you can see that the template is hooking into those variables using the | |
// "let" syntax, ex. "let-color=item". | |
template: | |
` | |
<dynamic-repeater [items]="colors"> | |
<template #itemRenderer let-color="item" let-index="index"> | |
<div title="Item {{ index }}" class="swatch" [style.backgroundColor]="color.hex"> | |
<br /> | |
</div> | |
<div class="name"> | |
{{ color.name }} | |
</div> | |
</template> | |
</dynamic-repeater> | |
` | |
}) | |
export class AppComponent { | |
// I hold the collection of colors that will be rendered by the DynamicRepeater. | |
public colors: any[]; | |
// I initialize the component. | |
constructor() { | |
this.colors = [ | |
{ | |
hex: "#E50000", | |
name: "Red" | |
}, | |
{ | |
hex: "#FF028D", | |
name: "Hot Pink" | |
}, | |
{ | |
hex: "#FF81C0", | |
name: "Pink" | |
}, | |
{ | |
hex: "#FFD1DF", | |
name: "Light Pink" | |
}, | |
{ | |
hex: "#FFB07C", | |
name: "Peach" | |
}, | |
{ | |
hex: "#FF796C", | |
name: "Salmon" | |
} | |
]; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Import the core angular services. | |
import { Component } from "@angular/core"; | |
import { ContentChild } from "@angular/core"; | |
import { TemplateRef } from "@angular/core"; | |
// Import the application components and services. | |
import { createTemplateRenderer } from "./template-renderer.directive"; | |
@Component({ | |
selector: "dynamic-repeater", | |
inputs: [ "items" ], | |
// Here, we are querying for the <template> tags in the content. | |
queries: { | |
itemTemplateRef: new ContentChild( "itemRenderer" ) | |
}, | |
// We're going to provide a dynamically-generated directive that exposes custom | |
// inputs that we want to pass to our item renderer. In this case, we want to | |
// expose "context.item" and "context.index". This will return a directive with | |
// the selector, "template[render]", which are using in our view. | |
directives: [ | |
createTemplateRenderer( "item", "index" ) | |
], | |
template: | |
` | |
<header> | |
<h2> | |
Dynamic Repeater View | |
</h2> | |
</header> | |
<dynamic-repeater-body> | |
<dynamic-repeater-item *ngFor="let item of items; let index = index ;"> | |
<template | |
[render]="itemTemplateRef" | |
[context.item]="item" | |
[context.index]="index"> | |
</template> | |
</dynamic-repeater-item> | |
</dynamic-repeater-body> | |
<footer> | |
<p> | |
You have {{ items?.length }} item(s) being rendered. | |
</p> | |
</footer> | |
` | |
}) | |
export class DynamicRepeaterComponent { | |
// I hold the items to render in our repeater. | |
// -- | |
// NOTE: Injected property. | |
public items: any[]; | |
// I hold the template used to render the item. | |
// -- | |
// NOTE: Injected query. | |
public itemTemplateRef: TemplateRef<any>; | |
// I initialize the component. | |
constructor() { | |
this.items = []; | |
this.itemTemplateRef = null; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Import the core angular services. | |
import{ Directive } from "@angular/core"; | |
import{ OnInit } from "@angular/core"; | |
import{ TemplateRef } from "@angular/core"; | |
import{ ViewContainerRef } from "@angular/core"; | |
// I generate Class definitions that exposes custom sub-properties off the "context" | |
// namespace. This class always exposes: | |
// -- | |
// * render (aliased as "template") | |
// * context | |
// -- | |
// ... however, you can additionally provide other sub-properties of "conext" to make | |
// the binding syntax easier to read. | |
export function createTemplateRenderer( ...propertyNames: string[] ) { | |
// Let's convert the incoming sub-property names into namespaced inputs off the | |
// "context" object. For example, convert "foo" into "context.foo". | |
var contextProperties = propertyNames.map( | |
function operator( propertyName: string ) : string { | |
return( "context." + propertyName ); | |
} | |
); | |
@Directive({ | |
selector: "template[render]", | |
inputs: [ "template: render", "context", ...contextProperties ] | |
}) | |
class TemplateRendererDirective implements OnInit { | |
// I hold the context that will be exposed to the embedded view. | |
// -- | |
// NOTE: The context is an injectable input. However, it's sub-properties are | |
// also individually injectable properties based on the arguments passed to the | |
// factory function. | |
public context: any; | |
// I hold the TemplateRef that we are cloning into the view container. | |
public template: TemplateRef<any>; | |
// I hold the view container into which we are injecting the cloned template. | |
public viewContainerRef: ViewContainerRef; | |
// I initialize the directive. | |
constructor( viewContainerRef: ViewContainerRef ) { | |
this.context = {}; | |
this.viewContainerRef = viewContainerRef; | |
} | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I get called once, when the class is initialized, after the inputs have been | |
// bound for the first time. | |
public ngOnInit() : void { | |
if ( this.template && this.context ) { | |
this.viewContainerRef.createEmbeddedView( this.template, this.context ); | |
} | |
} | |
} | |
// Return the dynamically generated class. | |
return( TemplateRendererDirective ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment