【问题标题】:Load Dynamic templates within a single component using Angular 4使用 Angular 4 在单个组件中加载动态模板
【发布时间】:2017-12-02 15:15:06
【问题描述】:

我的要求是构建一个具有 2 个或更多 html 模板的组件,其中每个 html 模板具有至少 20 个控件,并且根据少数条件加载该特定模板。

注意:我选择了 3 个不同的模板,因为控件因模板类型而异,其中作为输入的单个 ts 文件和驱动获取和保存模板中值的逻辑保持不变。因此我决定将 3 个模板和一个 ts 文件作为一个组件。

//sub.component.ts
@Component({
     selector: 'sub-component',
     template: `
               <div [ngSwitch]="templateType">
                    <ng-template *ngSwitchCase="'1'"> ${require('./sub1.component.html')} </template>
                    <ng-template *ngSwitchCase="'2'"> ${require('./sub2.component.html')} </template>
                    <ng-template *ngSwitchCase="'3'"> ${require('./sub3.component.html')} </template>
                    <ng-template ngSwitchDefault> ${require('./sub1.component.html')} </template>
</div>
    `
})

我已经尝试了上述替代方案,因为它看起来是实现该行为的简单解决方案,但编译失败并找不到要求。在 AngularJS 中,我们有 ng-Include 来填充任何模板,但看起来 ng-template 不支持加载外部 html 内容。

请不要将此标记为重复,因为出现了许多与此类似的查询,但大多数解决方案已被弃用或不适用于 Angular 4。请提供替代方案,而不是附加不同的链接。

【问题讨论】:

  • 没有解决办法吗?即使在 39 次浏览后也没有答案.. 我认为应该有一个 github 问题来跟踪这个项目。
  • 这个answer 也会帮助你
  • 嗨,@BVS 你有什么具体的解决方案吗?我也有同样的要求。
  • @Thakkar,不,我使用了四个不同的组件。

标签: angular angular4-forms


【解决方案1】:

我知道这个问题很老,但希望这对尝试类似事情的人有所帮助。

不幸的是,最简单的方法是让每个模板成为自己的组件。否则,您必须注入和清理 HTML。由于注入未经清理的 HTML 的安全风险,他们删除了 ng-include 和类似功能。如果您不必在模块中专门导入和声明所有这些附加组件,它就不会是 P.I.T.A 那么多,但是唉......

您可以创建一个简单的指令来获取 templateRefs,然后在页面上查询具有这些指令的元素,从中获取模板 ref,然后将它们插入到其他地方。这至少可以让您将所有模板保存在单独的文件中。我通常将 3 或 4 个模板放在一个单独的组件中,并将它们包含在要使用 .我将描述如何做到这一点。

获取模板引用的指令

import { Directive, TemplateRef, Input } from '@angular/core';

@Directive({
 selector: 'get-template',
})
export class GetTemplateDirective {
  @Input() name: string;
  constructor(public template: TemplateRef<any>) {  }
}

然后对于模板,创建一个包含所有模板的超级简单组件

@Component({
  selector: 'sub-component-templates',
  template: `

<ng-template get-template [name]="tpl1">
  Put Whatever here, including other components if you please
</ng-template> 

<ng-template get-template [name]="tpl2">
  Different template here
</ng-template> 

 ... and so on and so on...
`
})
export class Templates { }

将所有相关的新组件导入到您的模块中,然后将它们包含在将呈现模板的主组件中

我通常用 ng-content 来做,所以它在父组件中清楚地表明该组件正在为其模板引用另一个组件。

例如,在父..

<sub-component>
    <sub-component-templates></sub-component-templates>
</sub-component>

然后在子组件中

 import { Component, ViewChild, ContentChildren, QueryList } from '@angular/core';
import { GetTemplateDirective } from 'wherever';

@Component({
selector: 'sub-component',
template: `

<ng-content></ng-content>

 <div #templateRenderer></div>
`
})
export class SubComponent {

@ViewChild('templateRenderer',{read:ViewContainerRef}) anchor: ViewContainerRef;
@ContentChildren(GetTemplateDirective) templates: QueryList<GetTemplateDirective>;

ngAfterContentInit()  {

  ... at this stage, you will have access to all the included templates with that directive on them. You can perform your logic to choose which one you want. Once you have selected the proper one, you can embed it like so ... 
 let desiredTemplateName = 'whatever';

     for (let t of this.templates.toArray()) {
       if(t.name === desiredTemplateName) {
          this.anchor.createEmbeddedView(t.template);
          break;        
       } 
     }  
   }

}

您可以看到,对于您正在尝试做的事情来说,这非常复杂。将它们创建为单独的组件会更容易,并使用 ngSwitchCase 来选择合适的组件。我上面描述的方法的优点是它可以让您将模板保存在任何您真正想要的地方,并且您可以在同一个外部组件中包含 100 个(实际上不超过带有模板的最小装饰组件)如果您想要,或者通过服务移动它们,或其他任何东西。

有关如何使用编译器的工作示例,请参见此处 - https://plnkr.co/edit/fdP9Oc?p=info

还是挺复杂的……

如果您将模板存储为类的属性,您可以稍后根据需要更改它。只需添加一个模板引用导入...

 import { Component, ViewChild, ContentChildren, QueryList, TemplateRef } from '@angular/core';

然后创建一个属性

 template: TemplateRef<any>;

然后您可以使用查询列表中的一个将其切换出来,并使用视图容器的方法再次创建嵌入式视图。

Angular 2 / 4 使某些事情变得更容易......并使某些事情变得更加困难。但我想在这种情况下,它是以安全的名义。

【讨论】:

  • 我可以通过服务来实现这一点,但不能通过直接添加示例中的组件来实现。指令将实例化,我可以用日志消息证明它,但模板不可用。视图封装会影响吗?但是,我确实注意到每次将模板组件放入任何其他组件时,它都会重新加载所有模板。因此,无论如何使用服务效率要高得多,这非常有效。感谢您的回答!
  • @David 你介意分享一下你是如何通过服务做到这一点的吗?我有点迷茫
  • @Cec 我在类似于文档的服务中使用了 Rxjs Subject>:angular.io/guide/…
  • @Cec 在存储所有模板的组件中使用 ngAfterViewInit 生命周期挂钩,以将组件推送到服务。我更喜欢使用局部变量而不是指令,只是为了保持简单。所以 在组件 html 中,然后 @ViewChild('myTemplate', { read: TemplateRef }) template: TemplateRef 在 component.ts 中,然后在 ngAfterViewInit 中发送到服务。如果您需要更多示例,请提出另一个问题,我会在那里回答。
  • 你的 plunker 不工作,你能举个例子吗
【解决方案2】:

我实际上是在我们说话时试图弄清楚这一点,当我遇到上面的答案时,它给了我一个我即将尝试的想法。希望我能取得一些成功,并能够用更具体的东西来更新这个答案。

我一直在做我的问卷调查,我的问题是带有文字答案的多项选择题,带有“1-10 级”机制的多项选择题,还有一些需要文字回答的问题。

我认为创建一个component 并将每个模板包装在一个ngIf 条件中,该条件连接到将传递数据的类中的变量可以触发模板。

所以数据可能看起来像这样

Questions:[{
    question:'blahblahblah',
    answers: [..array of answers..],
    template: 'a'
    },
    {
    question: 'yaddayaddayadda',
    answers: [],
    template: 'b'
    },
    {
    etc.
    }
    ]

然后在组件类中可以有这样的东西

@Component ({
    selector: 'question-component',
    templateUrl: './template.html'
})

export class QuestionComponent {

    @input() data: yourDataType;

    constructor() {} 

}

然后在这个组件的模板中有类似的东西

<div *ngIf="data.template === a">
    <!-- code for template with binding and everything -->
</div>

<div *ngIf="data.template === b">
    <!-- code -->
</div>

<!-- etc. etc. etc. -->

然后在主组件的模板中你可以做类似的事情

<div *ngFor = "question of Questions">
    <question-component [data]="question"></question-component>
</div>

这都是我脑海中的假设,所以我可能会遗漏一些东西,但我觉得在此期间可以在这里开始探索是值得的。我要看看能否让它满足我的需求。

【讨论】:

    猜你喜欢
    • 2018-11-03
    • 1970-01-01
    • 2014-11-23
    • 2017-12-15
    • 1970-01-01
    • 1970-01-01
    • 2018-01-04
    • 2019-10-19
    相关资源
    最近更新 更多