Skip to content

Instantly share code, notes, and snippets.

@nirkaufman
Created April 12, 2020 22:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nirkaufman/a737b9551706e580042242452b9a1444 to your computer and use it in GitHub Desktop.
Save nirkaufman/a737b9551706e580042242452b9a1444 to your computer and use it in GitHub Desktop.
import {AfterViewInit, Component, OnInit, TemplateRef, ViewChild, ViewContainerRef,} from '@angular/core';
import {Card, CardTypes} from "./cards/card.types";
@Component({
selector: 'app-root',
template: `
<div class="container">
<h1 class="display-1">Angular<span class="text-muted">MasterClass</span></h1>
<!-- will render the built-in templates -->
<ng-container *nkDeck="let card for cards;"></ng-container>
<!-- optional override for the 'built-in' primary template -->
<ng-container *nkDeck="let card for cards; primary altPrimaryTemplate"></ng-container>
<ng-template #altPrimaryTemplate let-card>
<!-- custom content for a primary card-->
</ng-template>
</div>
`,
})
export class AppComponent{
cards: Card[] = [
{
type: CardTypes.Plain,
title: "The title",
text: "The Text"
},
{
type: CardTypes.Plain,
title: "Another title",
text: "another text to render"
},
{
type: CardTypes.Primary,
title: "What else",
text: "The Text The Text The Text",
header: 'Im The header',
smallText: 'and some small text'
},
]
}
import {Component, TemplateRef, ViewChild} from '@angular/core';
import {CardTemplateContext} from "./card.types";
// this component used as a 'TemplateStorage'
// no selector needed
@Component({
template: `
<ng-template #plainCard let-card>
<div class="card">
<div class="card-body">
<h5 class="card-title">{{card.title}}</h5>
<p class="card-text">{{card.text}}</p>
</div>
</div>
</ng-template>
<ng-template #primaryCard let-card>
<div class="card border-primary">
<div class="card-header">{{ card.header }}</div>
<div class="card-body text-primary">
<h5 class="card-title">{{card.title}}</h5>
<p class="card-text">{{card.text}}</p>
<p class="card-text text-small">{{ card.smallText }}</p>
</div>
</div>
</ng-template>
`,
})
export class CardTemplatesComponent {
@ViewChild('plainCard', {static: true}) plainCardTemplate: TemplateRef<CardTemplateContext>;
@ViewChild('primaryCard', {static: true}) primaryCardTemplate: TemplateRef<CardTemplateContext>;
}
export enum CardTypes {
Plain = 'Plain',
Primary = 'Primary',
}
export interface Card{
type: CardTypes,
header?: string;
title?: string;
text?: string;
smallText?: string
}
export interface CardTemplateContext {
$implicit: Card
}
import {
ComponentFactoryResolver, ComponentRef,
Directive,
ElementRef,
Injector,
Input, OnInit,
Renderer2, TemplateRef,
ViewContainerRef
} from '@angular/core';
import {Card, CardTemplateContext, CardTypes} from "./card.types";
import {CardTemplatesComponent} from "./card-templates.component";
@Directive({
selector: '[nkDeck]'
})
export class DeckDirective implements OnInit {
// Angular will make the cards and primary: *nkDeck="let card for cards; primary altPrimaryTemplate
// available via input by suffixing the name of the variable to the directive name
@Input('nkDeckFor') cards: Card[];
@Input('nkDeckPrimary') primaryTemplate: TemplateRef<CardTemplateContext>;
constructor(
private viewContainer: ViewContainerRef,
private renderer: Renderer2,
private cfr: ComponentFactoryResolver,
private injector: Injector,
private hostElement: ElementRef
) {}
ngOnInit(): void {
// get a reference to the the parent element of this directive
// hostElement (available via ElementRef injection)
const parentNode = this.renderer.parentNode(this.hostElement.nativeElement)
// create a brand new div element and set it's css class
const wrapper = this.renderer.createElement('div');
this.renderer.addClass(wrapper, 'card-deck')
// insert the the new div element under the parent above this directive host element
this.renderer.insertBefore(parentNode, wrapper, this.hostElement.nativeElement)
// remove this directive host element and append it as a child of the the new div element
this.renderer.removeChild(parentNode, this.hostElement.nativeElement)
this.renderer.appendChild(wrapper, this.hostElement.nativeElement)
// create a CardTemplatesComponent by resoling a factory and use it
const cardTemplateFactory = this.cfr.resolveComponentFactory<CardTemplatesComponent>(CardTemplatesComponent);
const cardTemplateComponent: ComponentRef<CardTemplatesComponent> = cardTemplateFactory.create(this.injector);
// loop over the cards and create embedded view for each card
this.cards.forEach( card => {
this.renderTemplate(card, cardTemplateComponent);
} )
}
// helper method for creating an embedded view out of templates
private renderTemplate(card: Card, templateComponent: ComponentRef<CardTemplatesComponent> ) {
switch (card.type) {
case CardTypes.Plain:
this.viewContainer.createEmbeddedView(
// try to use a provided template. if not available, use one from the CardComponentTemplate
this.primaryTemplate || templateComponent.instance.plainCardTemplate, {$implicit: card})
break
case CardTypes.Primary:
this.viewContainer.createEmbeddedView(templateComponent.instance.primaryCardTemplate, {$implicit: card})
break
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment