使用createComponent创建组件时Angular未检测到*ngFor指令问题
Angular结构型指令内嵌组件中*ngFor失效问题
我实现了一个根据状态切换加载组件的结构型指令ifNotLoading,当通过该指令加载目标组件时,组件内部的*ngFor无法渲染内容,IDE提示“No directive is matched on attribute ngFor”。模块中已正确声明NgForOf指令及相关组件,且测试发现*ngIf可正常工作,说明Angular并非无法识别*ngFor指令。
结构型指令代码
@Directive({ selector: '[ifNotLoading]', }) export class LoaderDirective implements OnInit, OnChanges { @Input('ifNotLoadingLoaderType') loaderType: TLoaderTypes; @Input('ifNotLoading') loading: boolean; private isInitialized = false; constructor(private templateRef: TemplateRef<any>, private vcRef: ViewContainerRef) {} ngOnChanges(changes: SimpleChanges): void { if (!this.isInitialized) { return; } this.handleCreateAndDestroyByLoadingState(); } ngOnInit(): void { this.isInitialized = true; this.handleCreateAndDestroyByLoadingState(); } private handleCreateAndDestroyByLoadingState() { this.vcRef.clear(); if (this.loading) { this.vcRef.createComponent(LoadersComponent).instance.loaderToActivate = this.loaderType; } else { // 嵌入宿主模板内容 this.vcRef.createEmbeddedView(this.templateRef); } } }
加载组件代码
@Component({ selector: 'app-loader', templateUrl: "./loaders.component.html", styleUrls: ['./loaders.component.scss'], }) export class LoadersComponent implements AfterViewInit { @ViewChild('defaultLoader') defaultLoader: TemplateRef<any>; @ViewChild('table') table: TemplateRef<any>; @Input('loaderToActivate') loaderToActivate: TLoaderTypes; constructor( private vcRef: ViewContainerRef, ) { } ngAfterViewInit(): void { switch (this.loaderToActivate) { case ELoaderTypes.table: this.vcRef.createEmbeddedView(this.table); break; default: this.vcRef.createEmbeddedView(this.defaultLoader); break; } } }
组件模板代码
<!--Table Loader--> <ng-template #table> <table class="table-loader table-1 table table-striped"> <thead> <tr> <th scope="col" class="text-center">XXX</th> <th scope="col" class="text-center">XXX</th> <th scope="col" class="text-center">XXX</th> <th scope="col" class="text-center">XXX</th> <th scope="col" class="text-center">XXX</th> <th scope="col" class="text-center">XXX</th> <th scope="col" class="text-center">XXX</th> <th scope="col" class="text-center">XXX</th> </tr> </thead> <tbody> <!-- table loader --> <ng-container> <tr *ngFor="let i of [1,2,3,4,5,6,7,8]" class="loading-reports"> <td *ngFor="let i of [1,2,3,4,5,6,7,8]"> <p class="animated-background row-data"></p> </td> </tr> </ng-container> </tbody> </table> </ng-template>
问题原因
手动调用vcRef.createEmbeddedView()创建模板时,若未传入组件的注入器,模板会使用隔离的注入环境,无法访问模块中声明的NgForOf等指令。*ngIf能工作是因为全局导入的BrowserModule已包含CommonModule,但隔离环境未继承该依赖。
修复方案
方案1:手动传入注入器
修改LoadersComponent的ngAfterViewInit方法,创建嵌入视图时传入当前组件的注入器:
ngAfterViewInit(): void { switch (this.loaderToActivate) { case ELoaderTypes.table: this.vcRef.createEmbeddedView(this.table, null, { injector: this.vcRef.injector }); break; default: this.vcRef.createEmbeddedView(this.defaultLoader, null, { injector: this.vcRef.injector }); break; } }
方案2:使用ngTemplateOutlet(推荐)
改用Angular官方推荐的模板复用方式,自动继承注入环境:
- 修改
LoadersComponent模板:
<ng-container [ngTemplateOutlet]="activeLoader"></ng-container> <!--Table Loader--> <ng-template #table> <!-- 原table模板内容 --> </ng-template> <!--Default Loader--> <ng-template #defaultLoader> <!-- 原default模板内容 --> </ng-template>
- 修改
LoadersComponent类:
export class LoadersComponent implements OnInit { @ViewChild('defaultLoader') defaultLoader: TemplateRef<any>; @ViewChild('table') table: TemplateRef<any>; @Input('loaderToActivate') loaderToActivate: TLoaderTypes; activeLoader: TemplateRef<any>; ngOnInit(): void { this.activeLoader = this.loaderToActivate === ELoaderTypes.table ? this.table : this.defaultLoader; } }
说明
ngTemplateOutlet会自动处理注入环境继承,避免手动创建视图的依赖问题,同时将逻辑移至ngOnInit比ngAfterViewInit更高效。
内容的提问来源于stack exchange,提问作者Or Shalmayev
相关产品推荐
相关产品推荐

