Angular 2 中的动态模板网址
Dynamic template URLs in Angular 2
过去几天我一直在研究 Angular 2,想知道是否可以为 @View
装饰器提供一个动态的 templateUrl
。
我试过向它传递一个函数并从它返回一个字符串,但整个函数只是变成了一个字符串。
我之前也没有真正使用过 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;
}
}
如有任何帮助,我们将不胜感激。
虽然这可能不是最优雅的解决方案,但我使用 DynamicComponentLoader 和 ElementRef 将模板值动态分配给组件。事实上,我一直在寻找一种可以将多个自定义组件添加到占位符中的解决方案。
我尝试在 shmck 概述的函数中注入服务,这不起作用,因为调用模板函数时服务尚不可用。实际上,this
指的是 Window 对象。
我使用的解决方案的参考网址位于:
站点 Dartdocs 提供了关于 Angular 2 DynamicComponentLoader class 的很好的文档,也适用于 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);
}
辅助函数作为 util
放置在某处
function toComponent(template, directives = []) {
@Component({ selector: 'fake-component' })
@View({ template, directives })
class FakeComponent { }
return FakeComponent;
}
由于安全问题,Angular2 似乎无法使用这种创建动态模板的方法。不幸的是,来自 Angular 1 我以前的应用程序是通过这种方式动态驱动的。
对于 Angular 2 - 这可能是做同样事情的不同方式(下面的 link 示例)。通过将模板 html 文件更新为应用程序中的组件,然后将它们注入(您尝试使用字符串等创建 templateUrl 的地方)查看组件模板参数作为元素(使用 DynamicComponentLoader)。
https://angular.io/docs/js/latest/api/core/DynamicComponentLoader-class.html
我的解决方案:
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})
]);
不完全是您要求的,但值得一提:
另一个适用于大多数用例的简单解决方案是将逻辑放在模板本身中,如下所示:
@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 大小开销是可以接受的)。
我的解决方案:(关于 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, []);
});
}
}
@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 }
]
});
希望github example for you对您有所帮助!有编译动态的例子html。因此,您可以通过任何服务加载 HTML 然后编译它。
1- 安装这个库
npm i -D html-loader
============================================= ===============
2- 在 webpack.config 中对 html 文件使用 html-loader
{ test: /\.html$/, loaders: ['html-loader'] }
============================================= ===============
3- 如果你使用 ionic ,你可以从路径中复制 webpack.config.js
"node_modules/@ionic/app-scripts/config/webpack.config.js"
然后向其中添加 html 加载器
============================================= ================
4-如果你使用ionic
在 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 文件中,如下所示:
声明变量要求:任何;
使用 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']
})
我知道这在技术上并不能回答所问的问题,但在许多情况下,您可以通过创建一个扩展预期组件并使用不同 templateUrl
的新组件来达到预期的效果。然后,在父组件中使用*ngIf
加载正确的模板。
使用模板 1 的组件:
@Component({
selector: 'template-one-component',
templateUrl: './template-one.html'
})
export class TemplateOneComponent {
title = 'This component uses one template';
}
使用模板 2 的组件:
@Component({
selector: 'template-two-component',
templateUrl: './template-two.html'
})
export class TemplateTwoComponent extends TemplateOneComponent {
}
父组件:
@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;
}
过去几天我一直在研究 Angular 2,想知道是否可以为 @View
装饰器提供一个动态的 templateUrl
。
我试过向它传递一个函数并从它返回一个字符串,但整个函数只是变成了一个字符串。
我之前也没有真正使用过 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;
}
}
如有任何帮助,我们将不胜感激。
虽然这可能不是最优雅的解决方案,但我使用 DynamicComponentLoader 和 ElementRef 将模板值动态分配给组件。事实上,我一直在寻找一种可以将多个自定义组件添加到占位符中的解决方案。
我尝试在 shmck 概述的函数中注入服务,这不起作用,因为调用模板函数时服务尚不可用。实际上,this
指的是 Window 对象。
我使用的解决方案的参考网址位于:
站点 Dartdocs 提供了关于 Angular 2 DynamicComponentLoader class 的很好的文档,也适用于 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);
}
辅助函数作为 util
放置在某处function toComponent(template, directives = []) {
@Component({ selector: 'fake-component' })
@View({ template, directives })
class FakeComponent { }
return FakeComponent;
}
由于安全问题,Angular2 似乎无法使用这种创建动态模板的方法。不幸的是,来自 Angular 1 我以前的应用程序是通过这种方式动态驱动的。
对于 Angular 2 - 这可能是做同样事情的不同方式(下面的 link 示例)。通过将模板 html 文件更新为应用程序中的组件,然后将它们注入(您尝试使用字符串等创建 templateUrl 的地方)查看组件模板参数作为元素(使用 DynamicComponentLoader)。
https://angular.io/docs/js/latest/api/core/DynamicComponentLoader-class.html
我的解决方案:
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})
]);
不完全是您要求的,但值得一提:
另一个适用于大多数用例的简单解决方案是将逻辑放在模板本身中,如下所示:
@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 大小开销是可以接受的)。
我的解决方案:(关于 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, []);
});
}
}
@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 }
]
});
希望github example for you对您有所帮助!有编译动态的例子html。因此,您可以通过任何服务加载 HTML 然后编译它。
1- 安装这个库
npm i -D html-loader
============================================= ===============
2- 在 webpack.config 中对 html 文件使用 html-loader
{ test: /\.html$/, loaders: ['html-loader'] }
============================================= ===============
3- 如果你使用 ionic ,你可以从路径中复制 webpack.config.js "node_modules/@ionic/app-scripts/config/webpack.config.js" 然后向其中添加 html 加载器
============================================= ================
4-如果你使用ionic 在 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 文件中,如下所示:
声明变量要求:任何;
使用 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']
})
我知道这在技术上并不能回答所问的问题,但在许多情况下,您可以通过创建一个扩展预期组件并使用不同 templateUrl
的新组件来达到预期的效果。然后,在父组件中使用*ngIf
加载正确的模板。
使用模板 1 的组件:
@Component({
selector: 'template-one-component',
templateUrl: './template-one.html'
})
export class TemplateOneComponent {
title = 'This component uses one template';
}
使用模板 2 的组件:
@Component({
selector: 'template-two-component',
templateUrl: './template-two.html'
})
export class TemplateTwoComponent extends TemplateOneComponent {
}
父组件:
@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;
}