【问题标题】:Angular: understanding how DI works in dynamicaly loaded componentAngular:了解 DI 如何在动态加载的组件中工作
【发布时间】:2021-01-24 03:52:28
【问题描述】:

我遇到了注入到动态加载组件的服务的奇怪行为。考虑以下服务

@Injectable({
    providedIn: 'root'
})
export class SomeService {
    private random = Math.random() * 100;

    constructor() {
        console.log('random', this.random);
    }
}

该服务被添加为两个组件的依赖项。第一个组件是延迟加载模块的一部分。而第二个是动态加载的。以下服务使用动态组件加载模块

export const COMPONENT_LIST = new InjectionToken<any>('COMPONENT_LIST');
export const COMPONENT_TYPE = new InjectionToken<any>('COMPONENT_TYPE');

@Injectable({
    providedIn: 'root'
})
export class LoaderService {
    constructor(
        private injector: Injector,
        private compiler: Compiler,
    ) { }

    getFactory<T>(componentId: string): Observable<ComponentFactory<T>> {
        // COMPONENT_LIST is passed through forRoot() from the module that declares first component
        const componentList = this.injector.get(COMPONENT_LIST); 
        const m = componentList.find(m => m.componentId === componentId);

        const promise: Promise<ComponentFactory<T>> = (!m) ? null :
            m.loadChildren
                .then((mod: any) => {
                    return this.compiler.compileModuleAsync(mod);
                })
                .then((mf: NgModuleFactory<any>) =>  {
                    const mr: NgModuleRef<any> = mf.create(this.injector);
                    const type: Type<T> = mr.injector.get<Type<T>>(COMPONENT_TYPE); // DYNAMIC_COMPONENT is provided in loaded module
                    return mr.componentFactoryResolver.resolveComponentFactory<T>(dynamicComponentType);
                });

        return from(promise);
    }
}

在同一个模块中,我声明了以下组件(用于放置动态加载的组件)和指令

@Component({
    selector: 'dynamic-wrapper',
    template: `<ng-container dynamicItem></ng-container>`
})
export class DynamicWrapperComponent implements AfterViewInit, OnDestroy {
    @Input() itemId: string;
    @Input() inputParameters: any;

    @ViewChild(DynamicItemDirective)
    private dynamicItem: DynamicItemDirective;

    private unsubscribe$: Subject<void> = new Subject();

    constructor(
        private loaderService: LoaderService
    ) { }

    ngAfterViewInit(): void {
        this.loaderService.getComponentFactory(this.itemId).subscribe((cf: ComponentFactory<any>) => {
            this.dynamicItem.addComponent(cf, this.inputParameters);
        });
    }
}
...
@Directive({
    selector: '[dynamicItem]'
})
export class DynamicItemDirective {

    constructor(protected viewContainerRef: ViewContainerRef) { }

    public addComponent(cf: ComponentFactory<any>, inputs: any): void {
        this.viewContainerRef.clear();
        const componentRef: ComponentRef<any> = this.viewContainerRef.createComponent(cf);
        Object.assign(componentRef.instance, inputs);
        // if I do not call detectChanges, ngOnInit in loaded component will not fire up
        componentRef.changeDetectorRef.detectChanges();
    }
}

SomeService 定义在一个单独的模块中,该模块在具有第一个组件的延迟加载模块和动态加载的模块中都导入。

两个组件都初始化后,我在控制台中看到console.log('random', this.random) 的输出带有两个不同的数字,尽管装饰器中有providedIn: 'root'。出现这种奇怪行为的原因是什么?

【问题讨论】:

  • 作为测试,我会尝试将相同的 Injector 和/或 ComponentFactoryResolver 传递给服务 LoaderService。或者更绝望,尝试在“平台”中提供它(如果您正在运行多个应用程序)

标签: javascript angular


【解决方案1】:

延迟加载的模块不会像预期的那样对providedIn: 'root' 做出反应。 仅当导入服务的模块不是延迟加载时,此选项才会将服务推送到 AppModule(根模块)。

为什么你会看到不同的随机数?

因为SomeService定义的模块被启动了两次! (随意将console.log放在这个模块的构造函数中)-因此服务启动了两次(我自己在我的项目延迟加载平台中遇到了这个问题)。

为什么包含SomeService的惰性模块会启动两次?

loadChildrenmoduleFactory.create(this.injector) 不保存延迟加载模块的缓存。他们启动一个模块并将其注入器作为叶子附加到输入注入器。

如果包含SomeService 的模块是从moduleFactory.create 创建的 - 您可以添加缓存层以返回缓存的初始化延迟加载模块。

例如:

private lazyLoadedModulesCache: Map&lt;Type&lt;any&gt;, NgModuleRef&lt;any&gt;&gt;;

【讨论】:

    猜你喜欢
    • 2021-12-20
    • 2019-10-19
    • 1970-01-01
    • 2021-01-10
    • 2020-04-17
    • 2019-02-06
    • 1970-01-01
    • 2016-08-01
    • 1970-01-01
    相关资源
    最近更新 更多