Skip to content

Instantly share code, notes, and snippets.

@bennadel
Last active June 12, 2021 10:42
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bennadel/14a55ae91c514a34f72f9be9d438eb92 to your computer and use it in GitHub Desktop.
Save bennadel/14a55ae91c514a34f72f9be9d438eb92 to your computer and use it in GitHub Desktop.
Experimenting With Dynamic Template Rendering In Angular 2 RC 1
// 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"
}
];
}
}
// 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;
}
}
// 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