Динамические URL шаблонов в Angular 2
В течение последних нескольких дней я играл с Angular 2 и задавался вопросом, можно ли предоставить динамический templateUrl
декоратору @View
.
Я пробовал передать ему функцию и вернул строчную форму, но вся функция просто превратилась в строку.
Я раньше не использовал Angular 1.x, поэтому не знаю, буду ли я просто ошибаться, но возможно ли это, или есть лучший способ создать динамический просмотры?
Например, я могу отобразить форму, если пользователь не вошел в систему, но отображает текстовое сообщение, если оно зарегистрировано.
Что-то вроде этого не работает:
@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;
}
}
Любая помощь будет оценена.
Ответы
Ответ 1
Хотя, возможно, это не самое элегантное решение, я использовал DynamicComponentLoader и ElementRef для динамического назначения значения шаблона компоненту. Фактически, я искал решение, в котором я могу добавить несколько настраиваемых компонентов в местозаполнитель.
Я попробовал инъекцию услуг в функции, как описано в shmck, это не работает, поскольку службы пока недоступны при вызове функции шаблона. Действительно, this
относится к объекту Window.
Справочные URL-адреса для используемого решения можно найти на: создать динамическое имя-anchorName/Components с ComponentResolver и ngFor в Angular2
Я также ссылаюсь на Plnkr1 и Plnkr2.
Сайт Dartdocs обеспечивает хорошую документацию по классу Angular 2 DynamicComponentLoader, также применимому к TypeScript.
Короче:
Простой компонент в качестве используемого шаблона
@Component({
selector: 'dt2-simple-block',
properties: ["idx"],
template: `<h1>Simple block for {{ idx }} </h1>`,
directives: []
})
class dt2SimpleBlock {
constructor() {
}
}
Конструктор компонента, который содержит все компоненты для добавления (моему приложению требуется включить несколько дочерних элементов:
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);
}
И вспомогательная функция, которая будет помещена где-то в качестве утилиты
function toComponent(template, directives = []) {
@Component({ selector: 'fake-component' })
@View({ template, directives })
class FakeComponent { }
return FakeComponent;
}
Ответ 2
Мое решение:
Angular 2.0 Класс ViewResolver
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})
]);
Ответ 3
Мое решение: (Красота в этом - ленивая загрузка файлов html и css.)
Это 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';
});
}
}
Директива, которую я использовал и внесла несколько изменений:
Это находится в home.componenet.html
<dynamic-html-outlet [htmlPath]="html" [cssPath]="css"></dynamic-html-outlet>
Это директива для динамических компонентов:
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, []);
});
}
}
Ответ 4
Не совсем то, что вы просили, но стоит упомянуть:
Еще одно простое решение, которое работает для большинства случаев использования, заключается в том, чтобы поместить логику в сам шаблон:
@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;
}
}
Даунсайд для этого решения заключается в том, что ваш обслуживаемый js файл заканчивается тем, что содержит оба шаблона, поэтому это может быть проблемой для больших шаблонов (но только один шаблон фактически отображается, а во многих случаях допустимы накладные расходы js).
Ответ 5
Обновить до ответа @Eyal Vardi (ViewResolver
устарел):
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 }
]
});
Ответ 6
1- установить эту библиотеку
npm я -D html-loader
=============================================== =============
2- В webpack.config используйте html-loader для html файлов
{ test: /\.html$/, loaders: ['html-loader'] }
=============================================== =============
3- Если вы используете ионный, вы можете скопировать webpack.config.js из пути
"node_modules/@ионная/приложение-скрипты/конфигурация/webpack.config.js"
затем добавьте к нему html-загрузчик
=============================================== ==============
4 - Если вы используете ионный
В package.json добавьте эти строки
"config": {
"ionic_bundler": "webpack",
"ionic_webpack": "webpack.config.ionic.js"
},
=============================================== ==============
5-Тогда вы можете использовать его, как показано ниже
@Component({
selector: 'page-login',
// templateUrl:"./login.html"
template: function(){
if(globalVariables.test==2) {
return require("./login2.html")
}
else
{
return require("./login.html")
}
}(),
})
======================================
6. Если есть нерешенная ошибка с функцией require, вы можете поместить ее в файл declarations.d.ts следующим образом:
объявить var require: any;
Ответ 7
Похоже, что этот способ создания динамических шаблонов не будет доступен для Angular 2 из-за вопросов безопасности. К сожалению, начиная с Angular 1 мое предыдущее приложение динамически управлялось таким образом.
Для Angular 2 - Это может быть отличный способ сделать то же самое (пример ссылки ниже). Обновляя шаблонные html файлы, чтобы быть компонентами в приложении, затем вставляйте их (место, где вы пытались создать templateUrl со строкой и т.д.) Рассматриваете параметр шаблона компонента компонента как элементы (используя DynamicComponentLoader).
https://angular.io/docs/js/latest/api/core/DynamicComponentLoader-class.html
Ответ 8
Надеюсь, пример github для вас поможет вам! Есть пример для компиляции динамического html. Таким образом, вы можете загружать HTML любым из своих Сервисов, а затем компилировать его.
Ответ 9
Скомпилируйте приложение с помощью 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']
})