I've been playing around with Angular 2 for the past few days and wondered if it was possible to provide a dynamic templateUrl
to the @View
decorator.
I have tried passing it a function and returning a string form it but the entire function just get's turned into a string.
I haven't really used Angular 1.x before either so I don't know if I'm just going about this in the wrong way, but is this possible, or is there a better way to create dynamic views?
For example I might want to display a form if the user is not logged in, but display a text message if they are logged in.
Something like this doesn't work:
@Component({
selector: 'my-component'
})
@View({
// This doesn't work
templateUrl: function() {
return this.isLoggedIn ? 'logged-in.html' : 'logged-out.html';
}
})
class MyComponent {
constructor() {
this.loggedIn = false;
}
}
Any help would be appreciated.
Although maybe not the most elegant solution, I used the DynamicComponentLoader and ElementRef to dynamically assign template value to a component. In fact, I was looking for a solution where I can add multiple custom components into a placeholder.
I tried service injection in the function as outlined by shmck this doesn't work as the services are not available yet when the template function is called. Indeed, this
refers to the Window object.
Reference URLs for the solution I used are to be found on: create dynamic anchorName/Components with ComponentResolver and ngFor in Angular2
I also refer this way to Plnkr1 and Plnkr2.
The site Dartdocs provides nice documentation on Angular 2 DynamicComponentLoader class, also applicable to TypeScript.
In short:
A simple Component as the to be used template
@Component({
selector: 'dt2-simple-block',
properties: ["idx"],
template: `<h1>Simple block for {{ idx }} </h1>`,
directives: []
})
class dt2SimpleBlock {
constructor() {
}
}
Constructor of the Component that holds all Components to be added (my app requires multiple childs to be included:
constructor(loader: DynamicComponentLoader, elementRef: ElementRef) {
//iterate
for (var i = 0; i < toSomething; i++) {
// build the template
var blockdirective = 'dt2-simple-block'
var template = '<' + blockdirective +
' idx="' + this.userBlocks.userHomePanelBlocks[i] +
'"></' + blockdirective + '>';
console.log(template); // debugging purpose
var directives = [dt2SimpleBlock];
loader.loadNextToLocation(toComponent(template, directives), elementRef);
}
And the helper function to be put somewhere as util
function toComponent(template, directives = []) {
@Component({ selector: 'fake-component' })
@View({ template, directives })
class FakeComponent { }
return FakeComponent;
}
My solution:
Angular 2.0 ViewResolver Class
class myViewResolver extends ViewResolver{
resolve(component: Type): ViewMetadata {
var view = super.resolve(component);
// TODO: Write logic here:-)
view.templateUrl = 'app/app.html';
return view;
}
}
bootstrap(App,[
provide(ViewResolver , {useClass:myViewResolver})
]);
Not quite what you asked for, but it's worth mentioning:
Another simple solution, which works for most use cases, is to put the logic in the template itself, like so:
@Component({
selector: 'my-component'
})
@View({
// Note1: Here, I use template instead of templateUrl.
// Note2: I use ES6 string interpolation + require() to embed/load the other templates, but you can do it however you like.
template: `
<div [ngSwitch]="loggedIn">
<template [ngSwitchCase]="true"> ${require('./logged-in.html')} </template>
<template ngSwitchDefault> ${require('./logged-out.html')} </template>
</div>`
})
class MyComponent {
constructor() {
this.loggedIn = false;
}
}
Downside for this solution is that your served js file ends up containing both templates, so this might be an issue for big templates (but only one template is actually rendered and the js size overhead is acceptable in many cases).
My solution:(The beauty about this is lazy loading for html and css files.)
This is home.componenet.ts
import { Component } from '@angular/core';
import { DynamicHTMLOutlet } from './../../directives/dynamic-html-outlet/dynamicHtmlOutlet.directive';
import { TranslateService, LangChangeEvent } from 'ng2-translate/ng2-translate';
@Component({
selector: 'lib-home',
templateUrl: './app/content/home/home.component.html',
directives: [DynamicHTMLOutlet]
})
export class HomeComponent {
html_template = `./app/content/home/home_`;
html: string;
css: string;
constructor(translate: TranslateService) {
this.html = this.html_template + translate.currentLang;
this.css = './app/content/home/home.component.css';
translate.onLangChange.subscribe((event: LangChangeEvent) => {
this.html = this.html_template + translate.currentLang;
this.css = './app/content/home/home.component.css';
});
}
}
The directive I used and made few changes: This is in home.componenet.html
<dynamic-html-outlet [htmlPath]="html" [cssPath]="css"></dynamic-html-outlet>
This is the directive for dynamic components:
import {
Component,
Directive,
ComponentFactory,
ComponentMetadata,
ComponentResolver,
Input,
ReflectiveInjector,
ViewContainerRef,
} from '@angular/core';
import { TranslatePipe } from 'ng2-translate/ng2-translate';
declare var $:any;
export function createComponentFactory(resolver: ComponentResolver, metadata: ComponentMetadata): Promise<ComponentFactory<any>> {
const cmpClass = class DynamicComponent {};
const decoratedCmp = Component(metadata)(cmpClass);
return resolver.resolveComponent(decoratedCmp);
}
@Directive({
selector: 'dynamic-html-outlet',
})
export class DynamicHTMLOutlet {
@Input() htmlPath: string;
@Input() cssPath: string;
constructor(private vcRef: ViewContainerRef, private resolver: ComponentResolver) {
}
ngOnChanges() {
if (!this.htmlPath) return;
$('dynamic-html') && $('dynamic-html').remove();
const metadata = new ComponentMetadata({
selector: 'dynamic-html',
templateUrl: this.htmlPath +'.html',
styleUrls: [this.cssPath],
pipes: [TranslatePipe]
});
createComponentFactory(this.resolver, metadata)
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.vcRef.createComponent(factory, 0, injector, []);
});
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With