URL des modèles dynamiques dans Angular 2

J'ai joué avec Angular 2 depuis quelques jours et je me suis demandé s'il était possible de fournir un templateUrl dynamique pour le décorateur @View .

J'ai essayé de passer une fonction et de renvoyer une chaîne de forme, mais la fonction entière juste obtenir est transformé en une chaîne.

Je n'ai pas vraiment utilisé Angular 1.x avant, alors je ne sais pas si je m'en remets dans le mauvais sens, mais est-ce possible ou existe-t-il un meilleur moyen de créer des vues dynamiques?

Par exemple, je pourrais vouloir afficher un formulaire si l'utilisateur n'est pas connecté, mais afficher un message texte s'ils sont connectés.

Quelque chose comme ça ne fonctionne pas:

 @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; } } 

Toute aide serait appréciée.

Bien que ce ne soit peut-être pas la solution la plus élégante, j'ai utilisé DynamicComponentLoader et ElementRef pour affecter dynamiquement la valeur du modèle à un composant. En fait, je cherchais une solution où je peux ajouter de multiples composants personnalisés à un espace réservé.

J'ai essayé l'injection de service dans la fonction comme indiqué par shmck, cela ne fonctionne pas car les services ne sont pas encore disponibles lorsque la fonction de modèle est appelée. En effet, this réfère à l'objet Window.

Les URL de référence pour la solution que j'ai utilisée se trouvent sur: create dynamic anchorName / Components with ComponentResolver et ngFor in Angular2

Je me réfère également à Plnkr1 et Plnkr2 .

Le site Dartdocs fournit une documentation intéressante sur la classe Angular 2 DynamicComponentLoader, également applicable à TypeScript.

En bref:

Un composant simple comme modèle à utiliser

 @Component({ selector: 'dt2-simple-block', properties: ["idx"], template: `<h1>Simple block for {{ idx }} </h1>`, directives: [] }) class dt2SimpleBlock { constructor() { } } 

Constructeur du composant qui supporte tous les composants à ajouter (mon application requiert plusieurs enfants à inclure:

  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); } 

Et la fonction helper à mettre quelque part comme util

 function toComponent(template, directives = []) { @Component({ selector: 'fake-component' }) @View({ template, directives }) class FakeComponent { } return FakeComponent; } 

Ma solution:

Angular 2.0 ViewResolver Classe

 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}) ]); 

Ma solution: (La beauté à ce sujet est un chargement paresseux pour les fichiers html et css.)

Ceci est 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'; }); } } 

La directive que j'ai utilisée et fait quelques modifications: ceci est dans home.componenet.html

 <dynamic-html-outlet [htmlPath]="html" [cssPath]="css"></dynamic-html-outlet> 

C'est la directive pour les composants dynamiques:

 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, []); }); } } 

Pas tout à fait ce que vous avez demandé, mais il convient de mentionner:

Une autre solution simple, qui fonctionne pour la plupart des cas d'utilisation, est de mettre la logique dans le modèle lui-même, de la manière suivante:

 @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; } } 

L'inconvénient de cette solution est que votre fichier js final finit par contenir les deux modèles, ce qui pourrait être un problème pour les grands modèles (mais un seul modèle est effectivement rendu et les frais généraux js sont acceptables dans de nombreux cas).

Mise à jour de la réponse de @Eyal Vardi ( ViewResolver est obsolète):

 import { Directive, Type, Component } from '@angular/core'; import { DirectiveResolver } from '@angular/compiler'; class myViewUrlResolver extends DirectiveResolver { resolve(type: Type<any>, throwIfNotFound?: boolean): Directive { let view = <any>super.resolve(type, throwIfNotFound); if (typeof view["templateUrl"] !== "undefined") { console.log("Yay!"); let originalUrl = (<Component>view).templateUrl; (<Component> view).templateUrl = environment.nativeScriptAppPrePathPrefix + originalUrl.replace(".html", ".tns.html"); } if (typeof view["styleUrls"] !== "undefined") { console.log("Yay2!"); let originalUrls = (<Component>view).styleUrls; originalUrls.forEach((originalUrl, at) => (<Component>view).styleUrls[at] = environment.nativeScriptAppPrePathPrefix + originalUrl.replace(".html", ".tns.css")); } return view; } } platformNativeScriptDynamic().bootstrapModule(AppModule,{ providers: [ { provide: DirectiveResolver, useClass: myViewUrlResolver } ] }); 

Il semble que cette façon de créer des modèles dynamiques ne soit pas disponible pour Angular 2 en raison de problèmes de sécurité. Malheureux, venant d'Angular 1, ma précédente application a été dynamiquement conduite de cette façon.

Pour Angular 2 – Cela pourrait être une manière différente de faire la même chose (exemple de lien ci-dessous). En mettant à jour les fichiers html du modèle pour être des composants dans l'application, les injecter dans (l'endroit où vous essayiez de créer le templateUrl avec une chaîne, etc.) visualiser le paramètre du modèle de composant comme éléments (en utilisant DynamicComponentLoader).

https://angular.io/docs/js/latest/api/core/DynamicComponentLoader-class.html

J'espère que l' exemple de Github vous aidera! Il existe un exemple pour compiler html dynamique. Ainsi, vous pouvez charger HTML par n'importe lequel de vos services et ensuite le compiler.

1- installez cette bibliothèque

Npm i -D html-loader

================================================== ==========

2- Dans webpack.config, utilisez html-loader pour les fichiers html

  { test: /\.html$/, loaders: ['html-loader'] } 

================================================== ==========

3- Si vous utilisez ionic, vous pouvez copier webpack.config.js à partir du chemin "node_modules/@ionic/app-scripts/config/webpack.config.js" puis ajoutez le chargeur html à celui-ci

================================================== ===========

4-Si vous utilisez ionic In package.json, ajoutez ces lignes

 "config": { "ionic_bundler": "webpack", "ionic_webpack": "webpack.config.ionic.js" }, 

================================================== ===========

5-Alors vous pouvez l'utiliser comme ci-dessous

 @Component({ selector: 'page-login', // templateUrl:"./login.html" template: function(){ if(globalVariables.test==2) { return require("./login2.html") } else { return require("./login.html") } }(), }) 

======================================

6-S'il y a une erreur non résolue avec la fonction requise, vous pouvez l'insérer dans le fichier declarations.d.ts ci-dessous:

Déclarer var exiger: tout;

La fonction n'est pas appelée. Essayez de l'envelopper dans une expression qui s'appelle.

 templateUrl: (function() { return this.isLoggedIn ? 'logged-in.html' : 'logged-out.html'; }())