You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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官方推荐的模板复用方式,自动继承注入环境:

  1. 修改LoadersComponent模板:
<ng-container [ngTemplateOutlet]="activeLoader"></ng-container>

<!--Table Loader-->
<ng-template #table>
    <!-- 原table模板内容 -->
</ng-template>

<!--Default Loader-->
<ng-template #defaultLoader>
    <!-- 原default模板内容 -->
</ng-template>
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 00:48:21