Angular 4中tableOfContents指令如何获取所有嵌套Page组件?
嘿,我之前在Angular 4里也踩过类似内容查询的坑,给你几个实用的解决方案,应该能解决你的问题:
1. 先排查@ContentChildren的配置和组件结构问题
你提到用@ContentChildren(Page, {descendants: true})只拿到直接子组件,大概率是因为那些嵌套在static-page/dynamic-page里的Page组件不是通过内容投影(<ng-content>)嵌入的。
Angular的@ContentChildren只能捕获宿主元素下通过内容投影传入的组件,如果你的static-page组件内部是直接写<app-page>(而不是用<ng-content>接收外部传入的Page),那这些Page属于static-page的私有视图,不属于tableOfContents指令宿主元素的内容投影后代,自然抓不到。
解决思路:
- 检查static-page/dynamic-page的模板,如果是内部渲染Page,要么改成用
<ng-content>让Page成为外部内容投影的一部分,要么给static-page/dynamic-page添加一个属性暴露它的Page子组件,比如:
然后tableOfContents指令可以先获取所有StaticPageComponent,再从中取出Page实例。@Component({selector: 'static-page', template: '<app-page></app-page>'}) export class StaticPageComponent { @ViewChild(Page) page: PageComponent; }
2. 处理动态添加/移除的组件
动态创建的组件(比如用ComponentFactoryResolver加载的)不属于内容投影的范畴,@ContentChildren和@ViewChildren都无法自动捕获到它们。这时候需要手动维护Page组件的列表:
方案:给Page组件添加生命周期事件通知
在Page组件里添加初始化和销毁的输出事件,让tableOfContents指令能监听并更新列表:
// PageComponent.ts import { Component, OnInit, OnDestroy, EventEmitter, Output } from '@angular/core'; @Component({ selector: 'app-page', template: `...你的Page模板...` }) export class PageComponent implements OnInit, OnDestroy { @Output() pageInitialized = new EventEmitter<PageComponent>(); @Output() pageDestroyed = new EventEmitter<PageComponent>(); ngOnInit() { this.pageInitialized.emit(this); } ngOnDestroy() { this.pageDestroyed.emit(this); } }
然后在tableOfContents指令里监听这些事件(Angular事件会自动冒泡,所以即使Page嵌套在多层组件里也能监听到):
// TableOfContentsDirective.ts import { Directive, HostListener } from '@angular/core'; import { PageComponent } from './page.component'; @Directive({ selector: '[tableOfContents]' }) export class TableOfContentsDirective { private allPages: PageComponent[] = []; @HostListener('pageInitialized', ['$event']) handlePageInit(page: PageComponent) { if (!this.allPages.includes(page)) { this.allPages.push(page); this.updateTableOfContents(); } } @HostListener('pageDestroyed', ['$event']) handlePageDestroy(page: PageComponent) { this.allPages = this.allPages.filter(p => p !== page); this.updateTableOfContents(); } private updateTableOfContents() { // 这里写你的目录生成/更新逻辑,比如遍历allPages提取标题等 console.log('当前所有Page组件:', this.allPages); } }
3. 结合静态内容和动态组件的处理
如果既有静态内容投影的Page,又有动态创建的Page,可以把两种方式结合起来:
// TableOfContentsDirective.ts 补充代码 import { ContentChildren, QueryList, AfterContentInit } from '@angular/core'; @Directive({selector: '[tableOfContents]'}) export class TableOfContentsDirective implements AfterContentInit { @ContentChildren(PageComponent, {descendants: true}) private staticPages: QueryList<PageComponent>; private allPages: PageComponent[] = []; ngAfterContentInit() { // 初始化静态Page列表 this.allPages.push(...this.staticPages.toArray()); this.updateTableOfContents(); // 监听静态Page列表的变化(比如内容投影的组件被增删) this.staticPages.changes.subscribe((updatedList) => { // 先移除之前的静态Page,再添加更新后的 this.allPages = this.allPages.filter(p => !this.staticPages.includes(p)); this.allPages.push(...updatedList.toArray()); this.updateTableOfContents(); }); } // 加上之前的handlePageInit和handlePageDestroy方法... }
4. 为什么@ViewChildren返回undefined?
这个很正常,因为你的tableOfContents是一个指令,指令本身没有自己的视图(不像组件有template),@ViewChildren是用来查询当前组件视图内的子元素/组件的,所以在指令里用@ViewChildren自然会返回undefined,应该用@ContentChildren来查询宿主元素下的内容投影组件。
内容的提问来源于stack exchange,提问作者Kwiat

