基于值而非变量的动态模板,使用ngTemplateOutlet实现

7
我正在尝试模拟一组动态问题。想象一下一个测验,其中一个问题是多项选择题,第二个问题是单个答案,第三个问题是yes或no等。
使用angular 4.1,我认为使用ngTemplateOutlet模板是最好的方法,这样可以将所有复选框样式设置为相同,所有单选按钮也是如此。
@Component({
  selector: 'my-component',
  template: `
    <div *ngFor="let item of items">
        <ng-template [ngTemplateOutlet]="item.type" [ngOutletContext]="{ item: item }"></ng-template>
    </div>`
})
export class MyComponent {
  @Input() items: any[];
}

@Component({
  selector: 'my-app',
  template: `
    <div>
      <my-component [items]="questions">
        <ng-template #question let-item="item">{{item?.question}}</ng-template>
        <ng-template #check let-item="item">checkboxes here {{item?.type}} - {{item?.values}}</ng-template>
        <ng-template #radio let-item="item">radio buttons here{{item?.type}} - {{item?.values}}</ng-template>
        <ng-template #bool let-item="item">boolean here{{item?.type}} - {{item?.values}}</ng-template>
        <ng-template #textbox let-item="item">textbox here{{item?.type}} - {{item?.values}}</ng-template>
      </my-component>
    </div>`
})
export class App {
  @ViewChild('question') question;
  @ViewChild('type') type;
  @ViewChild('values') values;
  questions = [
      { question: "my checkbox question", type: "check", values: ["checkbox1","checkbox2","checkbox3","checkbox4"] },
      { question: "my radiobutton question", type: "radio", values: ["radio1","radio2","radio3","radio4"] } ,
      { question: "my boolean question", type: "bool", values: ["yes", "no"] } ,
      { question: "my textbox question", type: "textbox", values: ["maybe something maybe nothing"] } 
    ];

我已经创建了这个Plunker作为概念验证的努力,但它并没有起作用。所有代码都在src/app.ts文件中。

我想要的是像这样的东西:

My checkbox question?
checkbox 1, checkbox2, checkbox3

my radio button question
radiobutton1, radiobutton2, radiobutton3

my boolean question?
yes, no 

我该如何修改这段代码,使用变量的值来指示使用哪个模板?

ngTemplateOutlet should be TemplateRef while you're passing string - yurzui
2
我会加载组件而不是模板。在我看来,这样可以提供更多隔离的功能实现和更多动态化。我发表了我的答案,提供了另一个观点,我认为这可能是一个不错的方法。(并不是说模板加载是错误的,我只是提供了一个可能有用的不同方法) - SrAxi
3个回答

14

如我在评论中所说,您需要传递TemplateRefngTemplateOutlet属性。可以这样做:

@Directive({
  selector: 'ng-template[type]'
})
export class QuestionTemplate {
  @Input() type: string;
  constructor(public template: TemplateRef) {}
}

app.html

<my-component [items]="questions">
  <ng-template type="question" ...>...</ng-template>
  <ng-template type="check" ...>...</ng-template>
  ...

my.component.ts

@Component({
  selector: 'my-component',
  template: `
    <div *ngFor="let item of items">
        <ng-template 
           [ngTemplateOutlet]="dict['question']" 
           [ngOutletContext]="{ item: item }"></ng-template>
        <ng-template 
           [ngTemplateOutlet]="dict[item.type]" 
           [ngOutletContext]="{ item: item }"></ng-template>
    </div>`
})
export class MyComponent {
  @Input() items: any[];

  @ContentChildren(QuestionTemplate) templates: QueryList<QuestionTemplate>;

  dict = {};

  ngAfterContentInit() {
    this.templates.forEach(x => this.dict[x.type] = x.template);
  }
}

Plunker示例


1
你在哪里看到@Directive已被弃用的信息? - yurzui
似乎只是我的无知。Angular中存在3种指令类型,第一种是组件。从指令到组件的转变让我感到困惑。 - crthompson
你的意思是什么?type=question?在你的数组中没有这种类型的元素。 - yurzui
数据分为三个部分:问题、类型和值。目前只有值和类型被使用,而问题并未被使用。 - crthompson
我明白了,所以问题不再使用“question”模板,而是嵌入到检查或单选框模板中。 - crthompson
显示剩余4条评论

2

您可以稍微改变您的方法,因为这只是一个可选建议

@Component({
  selector: 'my-component',
 template: `
   <div *ngFor="let item of items">
      <ng-container [ngTemplateOutlet]="item.type" [ngTemplateOutletContext]="{ item: item }"> 
      </ng-container>
   </div>

    <ng-template #question let-item="item"></ng-template>
    <ng-template #check let-item="item">checkboxes here {{item?.type}} - {{item?.values}}</ng-template>
    <ng-template #radio let-item="item">radio buttons here{{item?.type}} - {{item?.values}}</ng-template>
    <ng-template #bool let-item="item">boolean here{{item?.type}} - {{item?.values}}</ng-template>
    <ng-template #textbox let-item="item">textbox here{{item?.type}} - {{item?.values}}</ng-template>
`
})
export class MyComponent {
  @Input() items: any[];
}

你可以根据"item.type"调用模板,你的方法在结构方面看起来很好。

啊!原来是这样把数据传递给模板输出:[ngTemplateOutletContext]。谢谢! - Camille

2
我会改变方法,以下是我的建议:
为每种选项(复选框、单选框、下拉框等)创建一个组件。
将它们存储在常量中,将组件名称映射为字符串与组件类一起使用,例如:
export const optionTypes = {
    'TypeRadio': TypeRadio,
    'TypeCheckBox': TypeCheckBox,
};

在组件中:
 private optionsModule: NgModuleFactory<any>; // we have our components for our options declared in OptionsModule, for example
 private optionTypes = optionTypes;

 constructor(private compiler: Compiler) {

        // Declaring Options Module
        this.optionsModule = compiler.compileModuleSync(OptionsModule);
 }

在组件的模板中:
<fieldset *ngFor="let question of questions">
                <ng-container *ngComponentOutlet="optionTypes[question.type];
                                    ngModuleFactory: optionsModule;"></ng-container>
            </fieldset>

请注意,为了使此操作起作用,您的对象数据应更改type属性:
questions = [
      { question: "my checkbox question", type: "TypeCheckBox", values: ["checkbox1","checkbox2","checkbox3","checkbox4"] },
      { question: "my radiobutton question", type: "TypeRadio", values: ["radio1","radio2","radio3","radio4"] }
    ];

总结:
  1. 我们创建了 OptionsModule
  2. 我们为每个选项/问题类型创建了一个组件(包括其模板和逻辑)
  3. 我们将这些组件的名称添加到数据对象的 type 属性中(或创建一个简单的映射方法:radio -> TypeRadio
  4. 我们使用 NgComponentOutlet 动态渲染我们的组件。
  5. 我们使用 NgModuleFactory 从一个导入的模块中渲染那些组件。
结果:

我们有一个用于测验的动态组件加载系统。每个组件都有自己的逻辑,并为您添加了许多很酷的功能和行为的可能性!

这种方法的示例(我使用它来拥有100%的动态表单字段:输入、选择、单选按钮、复选框等): Angular2: Use Pipe to render templates dynamically


你有这个的代码片段吗? - crthompson
@paqogomez 不好意思 :( 我在公共访问中最接近的东西是我解释我的解决方案(这种方法)的帖子链接。很抱歉,但很容易使用,检查文档并遵循此答案或我链接的答案,您应该没问题。如果您决定走这条路,请告诉我,我可以帮助您。 - SrAxi
我又回到了这个问题,但是我有几个问题。我已经创建了选项模块(但我不确定里面应该放什么),还有类型组件,数据对象也已经根据类型进行了更改。但是根据您的评论,我不确定如何将它们连接在一起。 - crthompson
还有,我能把“question”对象传递到我的类型组件中吗? - crthompson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接