Определите конкретный 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-шаблоны - последний из моих примеров выше не должен обрабатываться, поскольку он не указан в @Input () templateNames. Есть ли что-нибудь, что я могу установить в ParentComponent, который поможет мне различить два TemplateRef?
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>;
Для более подробного объяснения/примера посмотрите принятый ответ на этот вопрос .
Вы можете выбрать один из следующих методов:
Если это только для 2 компонентов, вы можете получить к ним доступ с помощью получателей 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
Другая альтернатива
Создал демонстрацию 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>