简体   繁体   English

Angular 2 中的动态模板 URL

[英]Dynamic template URLs in Angular 2

I've been playing around with Angular 2 for the past few days and wondered if it was possible to provide a dynamic templateUrl to the @View decorator.过去几天我一直在玩 Angular 2,想知道是否可以为@View装饰器提供动态templateUrl

I have tried passing it a function and returning a string form it but the entire function just get's turned into a string.我试过给它传递一个函数并从它返回一个字符串,但整个函数只是变成了一个字符串。

I haven't really used Angular 1.x before either so I don't know if I'm just going about this in the wrong way, but is this possible, or is there a better way to create dynamic views?我之前也没有真正使用过 Angular 1.x,所以我不知道我是否只是以错误的方式来解决这个问题,但这是否可能,或者是否有更好的方法来创建动态视图?

For example I might want to display a form if the user is not logged in, but display a text message if they are logged in.例如,如果用户未登录,我可能想显示一个表单,但如果他们已登录,则显示一条文本消息。

Something like this doesn't work:像这样的东西不起作用:

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

Any help would be appreciated.任何帮助,将不胜感激。

Although maybe not the most elegant solution, I used the DynamicComponentLoader and ElementRef to dynamically assign template value to a component.虽然可能不是最优雅的解决方案,但我使用 DynamicComponentLoader 和 ElementRef 为组件动态分配模板值。 In fact, I was looking for a solution where I can add multiple custom components into a placeholder.事实上,我一直在寻找一种解决方案,我可以在其中将多个自定义组件添加到占位符中。

I tried service injection in the function as outlined by shmck this doesn't work as the services are not available yet when the template function is called.我在 shmck 概述的函数中尝试了服务注入,这不起作用,因为在调用模板函数时服务尚不可用。 Indeed, this refers to the Window object.实际上, this指的是 Window 对象。

Reference URLs for the solution I used are to be found on: create dynamic anchorName/Components with ComponentResolver and ngFor in Angular2我使用的解决方案的参考 URL 可以在以下位置找到: create dynamic anchorName/Components with ComponentResolver 和 ngFor in Angular2

I also refer this way to Plnkr1 and Plnkr2 .我也将这种方式称为 Plnkr1Plnkr2

The site Dartdocs provides nice documentation on Angular 2 DynamicComponentLoader class, also applicable to TypeScript. Dartdocs站点提供了关于 Angular 2 DynamicComponentLoader 类的很好的文档,也适用于 TypeScript。

In short:简而言之:

A simple Component as the to be used template一个简单的组件作为要使用的模板

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

Constructor of the Component that holds all Components to be added (my app requires multiple childs to be included:包含要添加的所有组件的组件的构造函数(我的应用程序需要包含多个子组件:

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

And the helper function to be put somewhere as util并且将作为 util 放置在某处的辅助函数

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

  return FakeComponent;
}

My solution:我的解决方案:

Angular 2.0 ViewResolver Class 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})
]);

Not quite what you asked for, but it's worth mentioning:不完全是你所要求的,但值得一提的是:

Another simple solution, which works for most use cases, is to put the logic in the template itself, like so:另一个适用于大多数用例的简单解决方案是将逻辑放在模板本身中,如下所示:

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

Downside for this solution is that your served js file ends up containing both templates, so this might be an issue for big templates (but only one template is actually rendered and the js size overhead is acceptable in many cases).此解决方案的缺点是您提供的 js 文件最终包含两个模板,因此这可能是大模板的问题(但实际上只渲染一个模板,并且在许多情况下 js 大小开销是可以接受的)。

My solution:(The beauty about this is lazy loading for html and css files.)我的解决方案:(关于此的美妙之处在于 html 和 css 文件的延迟加载。)

This is home.componenet.ts这是 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';
    });
  }

 }

The directive I used and made few changes: This is in home.componenet.html我使用的指令并做了一些更改:这是在 home.componenet.html

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

This is the directive for dynamic components:这是动态组件的指令:

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, []);
      });
  }
}

Compile your application with aot "ng serve --aot".使用 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']
})

Update to @Eyal Vardi's answer ( ViewResolver is deprecated):更新@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 } 
  ]
});

It appears this way of creating dynamic templates will not be available to Angular 2 due to security matters.由于安全问题,Angular 2 似乎无法使用这种创建动态模板的方式。 Unfortunate, coming from Angular 1 my previous application was dynamically driven this way.不幸的是,来自 Angular 1 的我以前的应用程序是以这种方式动态驱动的。

For Angular 2 - This could be a differeny way of doing the same (link example below).对于 Angular 2 - 这可能是执行相同操作的不同方式(下面的链接示例)。 By updating the template html files to be components in the application, then injecting them into (the place where you were trying to create the templateUrl with a string etc) view component template parameter as elements (using DynamicComponentLoader).通过将模板 html 文件更新为应用程序中的组件,然后将它们注入(您尝试使用字符串等创建 templateUrl 的位置)视图组件模板参数作为元素(使用 DynamicComponentLoader)。

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

Hope, github example for you will help you!希望github 示例对您有所帮助! There are example to compile dynamic html.有编译动态html的例子。 So, you can load HTML by any of your Service and then compile it.因此,您可以通过任何服务加载 HTML,然后编译它。

1- install this library 1-安装这个库

npm i -D html-loader npm i -D html-loader

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

2- In webpack.config use html-loader for html files 2- 在 webpack.config 中对 html 文件使用 html-loader

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

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

3- If you use ionic , you can copy webpack.config.js from the path "node_modules/@ionic/app-scripts/config/webpack.config.js" then add the html loader to it 3- 如果您使用 ionic ,您可以从路径“node_modules/@ionic/app-scripts/config/webpack.config.js”复制 webpack.config.js,然后向其中添加 html 加载器

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

4-If you use ionic In package.json add these lines 4-如果您使用 ionic 在 package.json 中添加这些行

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

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

5-Then you can use it as below 5-然后你可以使用它如下

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

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

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

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

6-If there is unresolved error with require function you can put it in declarations.d.ts file as the below : 6-如果 require 函数存在未解决的错误,您可以将其放入 declarations.d.ts 文件中,如下所示:

declare var require: any;声明 var 要求:任何;

I know this doesn't technically answer the question asked, but, in many cases, you can achieve the desired effect by creating a new component that extends the intended component and uses a different templateUrl .我知道这在技术上并不能回答所提出的问题,但是,在许多情况下,您可以通过创建一个扩展预期组件并使用不同templateUrl的新组件来实现预期效果。 Then, use *ngIf in the parent component to load the correct template.然后,在父组件中使用*ngIf加载正确的模板。

Component using template 1:使用模板 1 的组件:

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

Component using template 2:使用模板 2 的组件:

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

Parent component:父组件:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM