【问题标题】:Dynamic template URLs in Angular 2Angular 2 中的动态模板 URL
【发布时间】: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;
  }
}

任何帮助将不胜感激。

【问题讨论】:

标签: javascript angular


【解决方案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

          我也以这种方式引用Plnkr1Plnkr2

          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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-02-18
            • 1970-01-01
            • 1970-01-01
            • 2017-10-18
            • 1970-01-01
            • 1970-01-01
            • 2017-03-12
            相关资源
            最近更新 更多