Определить конкретный Angular TemplateRef из QueryList

В 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 я могу получить QueryList всех шаблонов с помощью декоратора ContentChildren:

@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 или что-то еще, что я установил в теге ng-template в ParentComponent. Я понятия не имею, как отличить одну TemplateRef от другой;

Имейте в виду, что MyComponent не может делать никаких предположений о том, какие имена будут использоваться, и должны ли обрабатываться все ng-templates - последний в моем примере выше не должен обрабатываются, потому что он не указан в @Input () templateNames. Могу ли я установить в ParentComponent что-нибудь, что поможет мне отличить эти два элемента TemplateRef?


person BeetleJuice    schedule 20.10.2018    source источник


Ответы (2)


Вы можете создать директиву с входным параметром имени:

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

Для более подробного объяснения / примера посмотрите принятый ответ из этого вопроса

person lujop    schedule 12.02.2021
comment
Это прекрасно работает. Два быстрых совета: 1) Не забудьте добавить директиву в свой модуль, иначе templates будет пустым. 2) templates не будет инициализирован до ngAfterContentInit() - person Simon_Weaver; 26.02.2021

Вы можете выбрать один из следующих методов:

Если это только для двух компонентов, вы можете получить к ним доступ с помощью методов получения QueryList (первого и последнего)

@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

Другая альтернатива

Создал Demo Stackblitz с помощью расширения для компонентов.

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>

Шаблон

person KShewengger    schedule 20.10.2018
comment
Спасибо. Это крутые идеи, но они мне не подходят. В моем случае использования родительский компонент должен назначать идентификаторы TemplateRefs. Могут быть TemplateRefs, которые следует игнорировать (только родитель знает, какие), а дочерний элемент MyComponent не знает, сколько там будет TemplateRef и в каком порядке они будут. - person BeetleJuice; 20.10.2018
comment
Я переформулировал вопрос, чтобы сделать требования более ясными. - person BeetleJuice; 20.10.2018
comment
Обновили мое решение, не могли бы вы проверить и применимо ли оно к вашей текущей проблеме? Вы добавили ссылку на демонстрацию stackblitz, или вы можете посетить ее здесь stackblitz.com/edit/ngx-content- templateref и проверьте нижнюю часть предварительного просмотра на наличие результата консоли из журнала консоли родительского компонента для списка запросов. - person KShewengger; 20.10.2018
comment
Он не использовал ng-template, так как было как-то сложно проверить ссылку на них, поэтому каким-то образом был создан компонент содержимого шаблона, который будет использоваться в качестве вашего базового шаблона, который действует как ng-template, который также принимает внутренний шаблон содержимое внутри. - person KShewengger; 20.10.2018
comment
Привет, ваше решение действительно творческое; +1. Однако это более сложное решение, чем то, что у меня есть сейчас. В настоящее время я использую @ViewChild() (по одному для каждой templateRef) в родительском классе компонента, чтобы получить ссылку на TemplateRefs. Затем я могу отправить TemplateRefs напрямую view @Input() в MyComponent, чтобы входные данные переходили с ['t1', 't2'] на [{name: 't1', templateRef: ref1}, {name: 't2', templateRef: ref2}]. Это работает, но я надеялся найти что-то более упорядоченное; вот почему я разместил здесь свою проблему. - person BeetleJuice; 20.10.2018
comment
Замечательно. Извините, мое решение было довольно сложным, но я рад, что вы смогли разобраться в нем по-своему. Надеюсь, что будет также решение, которое будет опубликовано здесь в ближайшее время, так как мне также интересно узнать о других решениях. Спасибо. - person KShewengger; 21.10.2018