Dynamische Vorlagen-URLs in Angular 2

72

Ich habe in den letzten Tagen mit Angular 2 herumgespielt und mich gefragt, ob es möglich ist templateUrl, dem @ViewDekorateur eine Dynamik zu verleihen.

Ich habe versucht, ihm eine Funktion zu übergeben und einen String daraus zurückzugeben, aber die gesamte Funktion wird einfach in einen String umgewandelt.

Ich habe Angular 1.x vorher auch nicht wirklich verwendet, daher weiß ich nicht, ob ich dies nur falsch mache, aber ist dies möglich oder gibt es eine bessere Möglichkeit, dynamische Ansichten zu erstellen?

Zum Beispiel möchte ich möglicherweise ein Formular anzeigen, wenn der Benutzer nicht angemeldet ist, aber eine Textnachricht anzeigen, wenn er angemeldet ist.

So etwas funktioniert nicht:

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

Jede Hilfe wäre dankbar.

Lucas
quelle
3
Ich denke, das ist auf der Liste der fehlenden Funktionen. Siehe hier , wie es erwähnt More than one @View for a component.
Jesse Good

Antworten:

24

Obwohl dies möglicherweise nicht die eleganteste Lösung ist, habe ich DynamicComponentLoader und ElementRef verwendet, um einer Komponente dynamisch einen Vorlagenwert zuzuweisen. Tatsächlich suchte ich nach einer Lösung, mit der ich einem Platzhalter mehrere benutzerdefinierte Komponenten hinzufügen kann.

Ich habe versucht, die Dienstinjektion in die von shmck beschriebene Funktion einzufügen. Dies funktioniert nicht, da die Dienste beim Aufrufen der Vorlagenfunktion noch nicht verfügbar sind. thisBezieht sich in der Tat auf das Fensterobjekt.

Referenz-URLs für die von mir verwendete Lösung finden Sie unter: Erstellen Sie dynamische Ankernamen / Komponenten mit ComponentResolver und ngFor in Angular2

Ich beziehe mich auch auf Plnkr1 und Plnkr2 .

Die Site Dartdocs bietet eine schöne Dokumentation zur Angular 2 DynamicComponentLoader-Klasse, die auch für TypeScript gilt.

Zusamenfassend:

Eine einfache Komponente als zu verwendende Vorlage

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

Konstruktor der Komponente, die alle hinzuzufügenden Komponenten enthält (für meine App müssen mehrere untergeordnete Komponenten enthalten sein:

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

Und die Hilfsfunktion soll irgendwo als util platziert werden

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

  return FakeComponent;
}
TomG
quelle
Was ist das wörtlich {template, directives}?
LShi
4
@ LS.Shanghai: Es ist ES6 / TypeScript Abkürzung für {template: template, directives: directives}.
Eppsilon
9
Zu Ihrer Information DynamicComponentLoaderist veraltet. angular.io/docs/ts/latest/api/core/index/…
harunurhan
11

Meine Lösung:

Angular 2.0 ViewResolver-Klasse

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})
]);
Eyal Vardi
quelle
scheint ziemlich einfach! Funktioniert dies auf der neuesten RC-Version?
Sumeet Darade
2
Laut diesem github.com/angular/angular/commit/0988cc8 wird der ViewResolver mitDirectiveResolver
Aboodz
@Aboodz Irgendeine Idee, wie man das jetzt macht, DirectiveResolverdie entfernt wurde?
Precastic
10

Nicht ganz das, wonach Sie gefragt haben, aber es ist erwähnenswert:

Eine andere einfache Lösung, die für die meisten Anwendungsfälle funktioniert, besteht darin, die Logik wie folgt in die Vorlage selbst einzufügen:

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

Der Nachteil dieser Lösung ist, dass Ihre bereitgestellte JS-Datei letztendlich beide Vorlagen enthält. Dies kann daher ein Problem für große Vorlagen sein (es wird jedoch nur eine Vorlage gerendert, und der Overhead für die JS-Größe ist in vielen Fällen akzeptabel).

Yoav Aharoni
quelle
5

Meine Lösung: (Das Schöne daran ist das verzögerte Laden von HTML- und CSS-Dateien.)

Dies ist 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';
    });
  }

 }

Die Direktive, die ich verwendet und einige Änderungen vorgenommen habe: Dies ist in home.componenet.html

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

Dies ist die Direktive für dynamische Komponenten:

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, []);
      });
  }
}
Daniel Abzakh
quelle
2
ComponentMetadata und ComponentResolver scheinen nicht mehr in 2.0 Angular Core verfügbar zu sein :(
Max The Cat
2
Wie können wir es jetzt mit Angular 2.0 machen? :(
Jaumard
3

Update auf die Antwort von @Eyal Vardi ( ViewResolververaltet):

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 } 
  ]
});
Sebas
quelle
Es scheint, dass die Objekte templateUrl und StyleUrls hier immer undefiniert sind und die Stile und Vorlagen bereits kompiliert wurden
cmptrwizard
3

Kompilieren Sie Ihre Anwendung mit aot "ng serve --aot".

export let DEFAULT_PREFIX :string= './app.component';
//or localStorage.getItem('theme')
export function getMySuperTemplate(template: string) {
  return DEFAULT_PREFIX + template + '.html';
}

@Component({
  selector: 'app-root',
  templateUrl: getMySuperTemplate('2'),
  styleUrls:['./app.component.css']
})
Weslley De Souza
quelle
2
Was bringt Hardcodierung '2'hier? Besiegt es nicht den Punkt der OP-Frage? Wenn ich beispielsweise einen Dienst habe, der zurückgibt '2', wie kann ich diese Methode dann (aus der Komponentenklasse) aufrufen?
Moody
1

Es scheint, dass Angular 2 diese Art der Erstellung dynamischer Vorlagen aus Sicherheitsgründen nicht zur Verfügung steht. Leider wurde meine vorherige Anwendung aus Angular 1 dynamisch auf diese Weise gesteuert.

Für Winkel 2 - Dies kann eine andere Möglichkeit sein, dasselbe zu tun (Linkbeispiel unten). Durch Aktualisieren der HTML-Vorlagendateien als Komponenten in der Anwendung und anschließendes Einfügen in (den Ort, an dem Sie versucht haben, die templateUrl mit einer Zeichenfolge usw. zu erstellen) wird der Komponentenvorlagenparameter als Elemente angezeigt (mithilfe von DynamicComponentLoader).

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

Nick Taras
quelle
1

Hoffe, Github Beispiel für Sie wird Ihnen helfen! Es gibt Beispiele zum Kompilieren von dynamischem HTML. Sie können also HTML von jedem Ihrer Dienste laden und dann kompilieren.

Ivan
quelle
1

1- Installieren Sie diese Bibliothek

npm i -D HTML-Loader

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

2- Verwenden Sie in webpack.config den HTML-Loader für HTML-Dateien

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

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

3- Wenn Sie ionic verwenden, können Sie webpack.config.js aus dem Pfad "node_modules/@ionic/app-scripts/config/webpack.config.js" kopieren und dann den HTML-Loader hinzufügen

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

4-Wenn Sie ionic In package.json verwenden, fügen Sie diese Zeilen hinzu

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

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

5-Dann können Sie es wie folgt verwenden

@Component({
  selector: 'page-login',
 // templateUrl:"./login.html"

   template:     function(){
    if(globalVariables.test==2) {

      return require("./login2.html")
    }
    else
    {
      return require("./login.html")
    }
  }(),
})

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

6-Wenn ein ungelöster Fehler mit der Funktion require vorliegt, können Sie ihn wie folgt in die Datei declarations.d.ts einfügen:

deklariere var require: any;

Bahgat Mashaly
quelle
-2

Ich weiß, dass dies die gestellte Frage technisch nicht beantwortet, aber in vielen Fällen können Sie den gewünschten Effekt erzielen, indem Sie eine neue Komponente erstellen, die die beabsichtigte Komponente erweitert und eine andere verwendet templateUrl. Verwenden Sie dann *ngIfin der übergeordneten Komponente, um die richtige Vorlage zu laden.

Komponente mit Vorlage 1:

@Component({
  selector: 'template-one-component',
  templateUrl: './template-one.html'
})
export class TemplateOneComponent {
  title = 'This component uses one template';
}

Komponente mit Vorlage 2:

@Component({
  selector: 'template-two-component',
  templateUrl: './template-two.html'
})
export class TemplateTwoComponent extends TemplateOneComponent {
}

Übergeordnete Komponente:

@Component({
  selector: 'parent-component',
  template: `
    <template-one-component *ngIf="useTemplateOne; else useTemplateTwo"></template-one-component>

    <ng-template #useTemplateTwo>
      <template-two-component></template-two-component>
    <ng-template>
  `
})
export class ParentComponent {
  useTemplateOne: boolean;
}
nikojpapa
quelle