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

Angular内容投影引发服务注入异常问题排查与解决

Angular内容投影下LevelDirective层级计算异常问题

问题背景

实现了Angular指令LevelDirective,用于根据父级level值计算自身层级(父级level为1则自身为2),通过独立的LevelService存储层级数据。常规嵌套场景下工作正常,但引入带内容投影的自定义组件UsingLevelComponent后,层级计算出现异常。

相关代码

指令与服务实现

@Injectable()
export class LevelService {
    public level = signal(1);
    public id = new Guid().toString(); // 自定义工具生成唯一ID
}

@Directive({
    selector: "[myLevel]",
    standalone: true,
    providers: [
        {
            provide: LevelService,
            useFactory: (self: LevelDirective): LevelService => {
                const service = new LevelService();
                if (self) {
                    service.level.set(self.level());
                }
                return service;
            },
            deps: [LevelDirective],
        },
    ],
})
export class LevelDirective {
    private readonly parentService = inject(LevelService, { skipSelf: true, optional: true });

    public readonly level = computed<number>((): number => {
        return this.parentService ? this.parentService.level() + 1 : 1;
    });

    @HostBinding("class")
    public get hostClasses(): string {
        return `level-${this.level()} service-id-${this.parentService?.id}`;
    }
}

层级显示组件

@Component({
    selector: "level-aware",
    template: `
        <p>Current level: {{ levelService?.level() ?? "undefined" }}</p>
    `,
})
export class LevelAwareComponent {
    constructor(private levelService: LevelService) {}
}

带内容投影的组件

@Component({
    selector: "using-level",
    template: `
        <div myLevel>
            <ng-content></ng-content>
        </div>
    `,
})
export class UsingLevelComponent {}

问题现象

  1. 正常场景:直接嵌套使用myLevel和level-aware时,层级计算正确
<div myLevel>
    <div myLevel>
        <level-aware></level-aware>
    </div>
</div>

输出:

Current level: 2
  1. 内容投影场景异常:引入using-level后,层级未正确累加
<div myLevel>
    <div myLevel>
        <using-level>
            <level-aware></level-aware>
        </using-level>
    </div>
</div>

实际输出:

Current level: 2

预期输出应为Current level: 3(共3个myLevel实例)

  1. 多层嵌套投影异常:多层using-level嵌套时,后续指令实例共享同一服务
<div myLevel>
    <div myLevel>
        <using-level>
            <using-level>
                <level-aware></level-aware>
            </using-level>
        </using-level>
    </div>
</div>

输出:

Current level: 2

渲染后的DOM显示,内层myLevel的service-id与上层完全相同,说明服务实例未正确隔离。

核心疑问

  • 为何内容投影时,DOM结构的层级与实际注入的服务实例不匹配?
  • 如何确保投影内容能获取到当前组件内正确的LevelService实例?

已尝试的无效方案

  • 移除LevelService,直接注入LevelDirective访问其level计算信号,问题依旧
  • 将LevelDirective改为组件,异常现象未改变

修改后的代码片段:

// 修改后的LevelDirective
public readonly parent = inject(LevelDirective, { skipSelf: true, optional: true }); // 替代parentService

// 修改后的LevelAwareComponent
public readonly levelDirective = inject(LevelDirective, { optional: true }); // 替代levelService

解决方案

问题根源

Angular的内容投影规则是:投影内容是在父组件的注入上下文中解析的,而非接收投影的组件内部上下文。也就是说,写在<using-level>标签内的<level-aware>,本质属于外层父组件的模板,它会注入父组件上下文中最近的LevelService(即第二层myLevel对应的服务),完全无法获取到using-level内部myLevel提供的服务实例。

修复方案:用NgTemplateOutlet传递注入上下文

将内容投影改为模板传递,通过NgTemplateOutlet在目标组件的上下文内渲染模板,让模板内的组件能获取到当前组件提供的服务实例。

  1. 修改UsingLevelComponent:
import { Component, Input, TemplateRef } from '@angular/core';
import { LevelDirective } from './level.directive';

@Component({
    selector: "using-level",
    standalone: true,
    imports: [LevelDirective],
    template: `
        <div myLevel>
            <ng-container [ngTemplateOutlet]="content"></ng-container>
        </div>
    `,
})
export class UsingLevelComponent {
    @Input() content!: TemplateRef<any>;
}
  1. 使用时传递模板:
<div myLevel>
    <div myLevel>
        <using-level [content]="levelAwareTpl"></using-level>
    </div>
</div>

<ng-template #levelAwareTpl>
    <level-aware></level-aware>
</ng-template>

原理说明

NgTemplateOutlet会在当前组件的注入上下文中渲染模板,因此<level-aware>会注入<using-level>内部myLevel提供的LevelService,层级计算就能正确累加。多层嵌套时,每一层using-level都会提供独立的服务实例,确保层级计算准确。


内容的提问来源于stack exchange,提问作者Alexandru Tatarciuc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 17:38:13