Angular 2 中的动态模板 URL

Posted

技术标签:

【中文标题】Angular 2 中的动态模板 URL【英文标题】:Dynamic template URLs in Angular 2 【发布时间】:2015-10-19 23:02:09 【问题描述】:

过去几天我一直在玩 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;
  

任何帮助将不胜感激。

【问题讨论】:

我认为这在缺失的功能列表中。请参阅here,因为它提到了More than one @View for a component 这些帖子有帮助吗:***.com/questions/33851658/…***.com/questions/31548311/angular-2-html-binding 【参考方案1】:

我知道这在技术上并不能回答所提出的问题,但在许多情况下,您可以通过创建一个扩展预期组件并使用不同 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;

【讨论】:

【参考方案2】:

使用 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']
)

【讨论】:

在这里硬编码'2' 有什么意义?它不会破坏OP的问题吗?例如,如果我有一个返回 '2' 的服务,那么我如何(从组件类)调用该方法?【参考方案3】:

1- 安装这个库

npm i -D html-loader

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

2- 在 webpack.config 中使用 html-loader 处理 html 文件

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

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

3- 如果你使用 ionic ,你可以从路径中复制 webpack.config.js “node_modules/@ionic/app-scripts/config/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 要求:任何;

【讨论】:

【参考方案4】:

希望github example for you 能帮到你!有编译动态html的例子。因此,您可以通过任何 Service 加载 HTML,然后对其进行编译。

【讨论】:

【参考方案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  
  ]
);

【讨论】:

这里的templateUrl和StyleUrls对象好像总是未定义,样式和模板已经编译好了【参考方案6】:

我的解决方案:(关于这个的美妙之处在于延迟加载 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, []);
      );
  

【讨论】:

ComponentMetadata 和 ComponentResolver 似乎不再在 2.0 Angular 核心中可用:( 我们现在如何使用 angular 2.0 来做到这一点? :(【参考方案7】:

不完全符合您的要求,但值得一提:

另一个适用于大多数用例的简单解决方案是将逻辑放入模板本身,如下所示:

@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 大小开销是可以接受的)。

【讨论】:

【参考方案8】:

我的解决方案:

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

【讨论】:

看起来相当简单!这适用于最新的 rc 版本吗? 根据这个github.com/angular/angular/commit/0988cc8,ViewResolver合并到DirectiveResolver @Aboodz 知道DirectiveResolver 已被删除后该怎么做吗?【参考方案9】:

虽然可能不是最优雅的解决方案,但我使用 DynamicComponentLoader 和 ElementRef 将模板值动态分配给组件。事实上,我一直在寻找一种可以将多个自定义组件添加到占位符中的解决方案。

我在 shmck 概述的函数中尝试了服务注入,这不起作用,因为调用模板函数时服务还不可用。实际上,this 指的是 Window 对象。

我使用的解决方案的参考 URL 位于:create dynamic anchorName/Components with ComponentResolver and ngFor in 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);
    

辅助函数作为 util 放置在某处

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

  return FakeComponent;

【讨论】:

这个字面量template, directives是什么? @LS.Shanghai:这是template: template, directives: directives的ES6/TypeScript简写。 仅供参考 DynamicComponentLoader 已弃用。 angular.io/docs/ts/latest/api/core/index/…【参考方案10】:

由于安全问题,Angular 2 似乎无法使用这种创建动态模板的方式。不幸的是,来自 Angular 1 的我之前的应用程序是以这种方式动态驱动的。

对于 Angular 2 - 这可能是一种不同的方法(下面的链接示例)。通过将模板 html 文件更新为应用程序中的组件,然后将它们注入(您尝试使用字符串等创建 templateUrl 的位置)视图组件模板参数作为元素(使用 DynamicComponentLoader)。

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

【讨论】:

以上是关于Angular 2 中的动态模板 URL的主要内容,如果未能解决你的问题,请参考以下文章

angular2中的递归动态模板编译

ngFor(Angular 9)中的动态模板引用变量

Angular 2中的动态管道

在Angular 2中渲染动态模板时如何提供@Input

Angular 2 将组件动态附加到 DOM 或模板

带有动态模板或 templateUrl 的 Angular 2/4 组件