Angular Injector的实现初解


在Angular的开发实践中,我们大量依赖Angular自带的@Injectable注解,以及provide装饰器,来管理依赖。

在Angular组件实现中或通过构造函数自动注入,或通过inject方法主动注入,那么Angular是如何实现依赖注入的呢?

接下来我们将主要分析各个provider的注册过程,已及inject时寻找provider的过程。

Injector

Injector是provider的容器,其实现类为R3InjectorNodeInjectorChainedInjector, 继承了Injector类,并实现了get方法。

注意:Injector类似Zone.js,具有链式结构,get方法通常会逐层向上查找所需provider。

但Injector的实现并不依赖于Zone.js。

angularInjector继承关系

provider的注册过程

1. boostrap

项目bootstrap的阶段,将会创建platformInjectorapplicationInjector,调用路径bootstrapApplication -> internalCreateApplication

其中platformInjector是Injector类型,而applicationInjector是R3Injector类型。applicationInjector的parent为platformInjector。

export function internalCreateApplication(config: {
    rootComponent?: Type<unknown>,
    appProviders?: Array<Provider|EnvironmentProviders>,
    platformProviders?: Provider[],
}): Promise<ApplicationRef> {
    try {
        const {rootComponent, appProviders, platformProviders} = config;

        if ((typeof ngDevMode === 'undefined' || ngDevMode) && rootComponent !== undefined) {
            assertStandaloneComponentType(rootComponent);
        }

        const platformInjector = createOrReusePlatformInjector(platformProviders as StaticProvider[]);

        // Create root application injector based on a set of providers configured at the platform
        // bootstrap level as well as providers passed to the bootstrap call by a user.
        const allAppProviders = [
            provideZoneChangeDetection(),
            ...(appProviders || []),
        ];
        const adapter = new EnvironmentNgModuleRefAdapter({
            providers: allAppProviders,
            parent: platformInjector as EnvironmentInjector,
            debugName: (typeof ngDevMode === 'undefined' || ngDevMode) ? 'Environment Injector' : '',
            // We skip environment initializers because we need to run them inside the NgZone, which
            // happens after we get the NgZone instance from the Injector.
            runEnvironmentInitializers: false,
        });
        const envInjector = adapter.injector;
        const ngZone = envInjector.get(NgZone);

        // ...
    } catch (e) {
        return Promise.reject(e);
    }
}

createModule

创建module时将同时创建相应的R3Injector,此时同时收集provider。

createComponent

创建component将同时创建ChainedInjector与,NodeInjector。请参考源码 ComponentFactory.get 实现。

provider的查找过程

如果想要注入provider,有两点很重要,查找到当前的injector,以及通过injector逐层查找对应的provider。

以下以inject方法为例。

其请求路径 inject -> ɵɵinject -> getInjectImplementation | injectInjectorOnly -> XXXInjector.get

export function ɵɵinject<T>(token: ProviderToken<T>, flags = InjectFlags.Default): T|null {
    return (getInjectImplementation() || injectInjectorOnly)(resolveForwardRef(token), flags);
}

getInjectImplementation | injectInjectorOnly 获取当前injector。

在获取到injector后调用重写的get方法以在不同Injector中以特定的逻辑获取provider。

对于R3Injector,调用get时将会重新设定currentInjectImplementation和currentInjector。

在组件生成阶段,框架会注入context,参考Inject context。不过这部分还未找到源码验证。

/**
 * Current implementation of inject.
 *
 * By default, it is `injectInjectorOnly`, which makes it `Injector`-only aware. It can be changed
 * to `directiveInject`, which brings in the `NodeInjector` system of ivy. It is designed this
 * way for two reasons:
 *  1. `Injector` should not depend on ivy logic.
 *  2. To maintain tree shake-ability we don't want to bring in unnecessary code.
 */
let _injectImplementation: (<T>(token: ProviderToken<T>, flags?: InjectFlags) => T | null)|
        undefined;
export function getInjectImplementation() {
    return _injectImplementation;
}
export function injectInjectorOnly<T>(token: ProviderToken<T>): T;
export function injectInjectorOnly<T>(token: ProviderToken<T>, flags?: InjectFlags): T|null;
export function injectInjectorOnly<T>(token: ProviderToken<T>, flags = InjectFlags.Default): T|
    null {
    if (_currentInjector === undefined) {
        throw new RuntimeError(
            RuntimeErrorCode.MISSING_INJECTION_CONTEXT,
            ngDevMode &&
            `inject() must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with \`EnvironmentInjector#runInContext\`.`);
    } else if (_currentInjector === null) {
        return injectRootLimpMode(token, undefined, flags);
    } else {
        return _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags);
    }
}
© 2024 Hogan Hu