Angular 2中的动态模板URL


72

我一直在玩弄角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;
  }
}

任何帮助,将不胜感激。


3
我认为这是缺少功能的列表。正如它所提到的,请看这里More than one @View for a component
杰西·古德

Answers:


24

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

我尝试了shmck概述的函数中的服务注入,由于调用模板函数时服务尚不可用,因此无法正常工作。确实,this是指Window对象。

我使用的解决方案的参考URL可在以下位置找到:在Angular2中使用ComponentResolver和ngFor创建动态的anchorName / Components

我也将这种方式称为Plnkr1Plnkr2

Dartdocs网站提供了有关Angular 2 DynamicComponentLoader类的出色文档,该类也适用于TypeScript。

简而言之:

一个简单的组件作为要使用的模板

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

包含所有要添加的组件的Component的构造方法(我的应用程序要求包含多个子元素:

 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}
石狮

4
@ LS.Shanghai:这是ES6 / TypeScript的简写{template: template, directives: directives}
埃普西隆'16

9
仅供参考DynamicComponentLoaderangular.io/docs/ts/latest/api/core/index/…–
harunurhan

11

我的解决方案:

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

看起来还算简单!可以在最新的rc版本上使用吗?
Sumeet Darade

2
根据此github.com/angular/angular/commit/0988cc8,ViewResolver被合并到DirectiveResolver
Aboodz

@Aboodz知道现在该怎么做DirectiveResolver了吗?
预制

10

并不是您所要求的,但是值得一提:

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

@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

我的解决方案:(有关此功能的好处是可以延迟加载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, []);
      });
  }
}

2
ComponentMetadata和ComponentResolver似乎在2.0 Angular核心中不再可用:(
Max The Cat

2
现在如何使用angular 2.0做到这一点?:(
jaumard '16

3

更新到@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对象在这里始终是未定义的,样式和模板已经被编译
cmptrwizard

3

用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
'2'在这里进行硬编码有什么意义?它是否击败了OP的问题?例如,如果我有一个返回的服务,那么我该'2'如何(从组件类)调用该方法?
穆迪

1

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

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

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



1

1-安装此库

npm i -D html-loader

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

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

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

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

3-如果使用ionic,则可以从路径“ node_modules/@ionic/app-scripts/config/webpack.config.js”复制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函数存在无法解决的错误,您可以将其放入clarifications.d.ts文件中,如下所示:

声明var require:any;


-2

我知道这从技术上讲并不能回答所提出的问题,但是在许多情况下,您可以通过创建一个新组件来实现预期的效果,该组件扩展了预期的组件并使用了另一个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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.