【问题标题】:Identify specific Angular TemplateRef from a QueryList从 QueryList 中识别特定的 Angular TemplateRef
【发布时间】:2019-03-25 00:34:08
【问题描述】:

在 Angular 6/7 中,我有一个组件,我将内容投影到其中(ParentComponent 模板):

<my-component [templateNames]="['t1', 't2']">
  <ng-template name="t1">...</ng-template>
  <ng-template name="t2">...</ng-template>
  <ng-template>...</ng-template> <!-- not included in [templateNames] -->
</my-component>

MyComponent 类中,我可以使用 ContentChildren 装饰器获取所有模板的 QueryList

@ContentChildren(TemplateRef) templates: QueryList<TemplateRef<any>>;

挑战在于我想在特定模板上执行代码,该模板由 ParentComponent 通过 @Input() templateNames 设置的内容标识。

processTemplates() {
  for (const name of this.templateNames) {
    const templateRef = this.getTemplateByName(name);
    this.doWork(templateRef);
  }
}

getTemplateByName(name) {
  const templates = this.templates.toArray();

  return templates.find(t => ?); // what can I query to distinguish templates?
}

问题是我不知道如何读取name 属性或我在ParentComponent 中的ng-template 标记上设置的任何其他内容。我不知道如何区分一个 TemplateRef 和另一个;

请记住,MyComponent 不能对将使用什么名称或是否应该处理所有 ng-templates 做出任何假设——我的示例中的最后一个上面的内容不应该得到处理,因为它没有在 @Input() 模板名称 中列出。我可以在 ParentComponent 中设置什么来帮助我区分这两个 TemplateRef 吗?

【问题讨论】:

    标签: angular angular-template angular-template-variable


    【解决方案1】:

    您可以使用名称输入参数创建指令:

    @Directive({
      selector: '[template-name]'
    })
    export class TableColumnDirective {
    
      constructor(public readonly template: TemplateRef<any>) { }
    
      @Input('template-name') columnName: string;
    }
    

    这样使用:

      <my-component>
          <ng-template template-name="t1">...</ng-template>
          <ng-template template-name="t2">...</ng-template>
          ...
    

    然后以my-component 的方式注入:

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

    有关更详细的解释/示例,请查看接受的答案from this question

    【讨论】:

    • 这很好用。两个快速提示: 1) 不要忘记将指令添加到您的模块中,否则templates 将是空的。 2) templates 直到ngAfterContentInit() 才会被初始化
    【解决方案2】:

    您可以选择以下方法之一:

    如果它仅适用于 2 个组件,您可以使用 QueryList getter(第一个和最后一个)访问它们

    @ContentChildren(TemplateRef) templates: QueryList<TemplateRef<any>>;
    
    ngAfterContentInit() {
        console.log(this.templates.first);    // Gives you the 1st template child
        console.log(this.templates.last);     // Last template child (2nd child)     
    }
    

    按索引查找

    this.templates.find((template, index) => index == 1); // 2nd template child
    

    其他选择

    使用组件上的扩展创建了 Stackblitz Demo

    1.) 创建 TemplateContentComponent 这将作为您的 ChildComponent 并添加 @Input()

        @Component({
          selector: 'template-content',
          template: `
              // If no ng-template reference available, show its ng-content
              <ng-content *ngIf="!template"></ng-content>
    
             // Else, show the ng-template through ng-container
             <ng-container *ngIf="template"
                           [ngTemplateOutlet]="template"></ng-container>
          ` 
        })
        export class TemplateContentComponent {
            @Input() name: string;    // Serves as your component id
        }
    

    2.) 创建 TemplateContainerComponent - 这将用作您的 ParentComponent

     @Component({
      selector: 'template-container',
      template: `<ng-content></ng-content>`
    })
    export class TemplateContainerComponent implements AfterContentInit  {
    
        @ContentChildren(TemplateContentComponent) templates: QueryList<TemplateRef<any>>;
    
          ngAfterContentInit() {
            // You can now check whether you'll be fetching a template
            // based on the names you want provided from parent template.
    
            const t1 = this.templates.find((template: any) => template.name === 't1');
    
            console.log(t1);   // This will show the t1 component
                               // which t1 and t2 are the same component
                               // but had set a name @Input() as their ID
          }
    
        }
    

    3.) 在您的 AppComponent 模板上

    <template-container>
      // Can be a raw template, good for ng-content
      <template-content [name]="'t1'">t1 template</template-content>
    
      // Or a template from ng-template, good for ng-container
      <template-content [name]="'t2'"
                        [template]="userList"></template-content>
    </template-container>
    
    
    // User List Template
    <ng-template #userList>
      <h1>User List</h1>
    </ng-template>
    

    【讨论】:

    • 谢谢。这些想法很酷,但对我不起作用。对于我的用例,父组件需要为 TemplateRefs 分配 id。可能存在应该忽略的 TemplateRef(只有父级知道哪个),而子 MyComponent 不知道会有多少个 TemplateRef,也不知道它们的顺序。
    • 我已经改写了问题以使要求更加清晰。
    • 已经更新了我的解决方案,介意您是否可以检查一下,它是否适用于您当前的问题?已经添加了一个 stackblitz 演示链接,或者您可以在此处访问它stackblitz.com/edit/ngx-content-templateref 并检查预览的底部部分,以查看查询列表的父组件控制台日志中的控制台结果
    • 它没有使用 ng-template,因为它以某种方式难以检查对它们的引用,因此以某种方式创建了一个模板内容组件,该组件将用作您的基本模板,充当 ng -template 也接受内部模板内容。
    • 嘿,你的解决方案真的很有创意; +1。它比我现在的解决方案更复杂。我目前在父组件类中使用@ViewChild()(每个templateRef 一个)来获取对TemplateRefs 的引用。然后我可以将 TemplateRefs 直接查看 @Input() 发送到 MyComponent,因此输入从 ['t1', 't2'][{name: 't1', templateRef: ref1}, {name: 't2', templateRef: ref2}]。那行得通,但我希望找到更精简的东西;这就是为什么我在这里发布我的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-06
    • 1970-01-01
    • 2018-01-24
    • 1970-01-01
    相关资源
    最近更新 更多