Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to design multi-level dependency injection in Angular

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/01 Report--

This article introduces the knowledge of "how to design multi-level dependency injection in Angular". Many people will encounter this dilemma in the operation of actual cases, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

Multi-level dependency injection

As we said earlier, injectors in Angular are inheritable and hierarchical.

In Angular, there are two injector hierarchies:

ModuleInjector module injector: configure ModuleInjector in this hierarchy using @ NgModule () or @ Injectable () annotations

ElementInjector element injector: implicitly created on each DOM element

Module injector and element injector are tree-like structures, but their hierarchical structures are not exactly the same.

Module injector

The hierarchical structure of the module injector is not only related to the module design in the application, but also the hierarchical structure of the platform module (PlatformModule) injector and the application module (AppModule) injector.

Platform module (PlatformModule) injector

In Angular terminology, the platform is the context in which Angular applications run. The most common platform for Angular applications is the Web browser, but it can also be an operating system for mobile devices or a Web server.

When the Angular application starts, it creates a platform layer:

Platform is the entry point of Angular on web pages, and there is only one platform for each page.

For each Angular application running on the page, the common services are bound within the platform

An Angular platform, which mainly includes the functions of creating module instances, destroying and so on:

@ Injectable () export class PlatformRef {/ / pass in injector as platform injector constructor (private _ injector: Injector) {} / / create an instance of @ NgModule for a given platform for offline compilation bootstrapModuleFactory (moduleFactory: NgModuleFactory, options?: BootstrapOptions): Promise {} / / uses the given runtime compiler Create an instance of @ NgModule for a given platform bootstrapModule (moduleType: Type, compilerOptions: (CompilerOptions&BootstrapOptions) | Array = []): Promise {} / / listener to be called when registering to destroy the platform onDestroy (callback: () = > void): void {} / / get the platform injector / / the platform injector is the parent injector for each Angular application on the page And provide singleton provider get injector (): Injector {} / destroy the current Angular platform and all Angular applications on the page, including destroying all modules and listeners registered on the platform destroy () {}}

In fact, at startup time (in the bootstrapModuleFactory method), the platform creates a ngZoneInjector in ngZone.run to create all the instantiated services in the Angular area, while ApplicationRef (the Angular application running on the page) will be created outside the Angular area.

When launched in a browser, a browser platform is created:

Export const platformBrowser: (extraProviders?: StaticProvider []) = > PlatformRef = createPlatformFactory (platformCore, 'browser', INTERNAL_BROWSER_PLATFORM_PROVIDERS); / / where the platformCore platform must be included in any other platform export const platformCore = createPlatformFactory (null,' core', _ CORE_PLATFORM_PROVIDERS)

When you create a platform using a platform factory (such as createPlatformFactory above), the platform for the page is implicitly initialized:

Export function createPlatformFactory (parentPlatformFactory: (extraProviders?: StaticProvider []) = > PlatformRef) | null, name: string, providers: StaticProvider [] = []): (extraProviders?: StaticProvider []) = > PlatformRef {const desc = `Platform: ${name} `; const marker = new InjectionToken (desc); / / DI token return (extraProviders: StaticProvider [] = []) = > {let platform = getPlatform () / / if the platform has been created, if (! platform | | platform.injector.get (ALLOW_MULTIPLE_PLATFORMS, false) {if (parentPlatformFactory) {/ / if there is a parent platform, then use the parent platform directly and update the corresponding provider parentPlatformFactory (providers.concat (extraProviders). Concat ({provide: marker, useValue: true})) } else {const injectedProviders: StaticProvider [] = providers.concat (extraProviders). Concat ({provide: marker, useValue: true}, {provide: INJECTOR_SCOPE, useValue: 'platform'}); / / if there is no parent platform, create a new injector and create a platform createPlatform ({providers: injectedProviders, name: desc}) Return assertPlatform (marker);};}

Through the above process, we know that when the Angular application creates the platform, it creates the platform's module injector ModuleInjector. As we can see from the Injector definition in the previous section, NullInjector is the top of all injectors:

Export abstract class Injector {static NULL: Injector = new NullInjector ();}

Therefore, there is also NullInjector () on top of the platform module injector. Under the platform module injector, there is also an application module injector.

Application Root Module (AppModule) injector

Each application has at least one Angular module, and the root module is the module used to launch the application:

@ NgModule ({providers: APPLICATION_MODULE_PROVIDERS}) export class ApplicationModule {/ / ApplicationRef requires bootstrap provider component constructor (appRef: ApplicationRef) {}}

The AppModule root application module is reexported by BrowserModule and is automatically included in the root AppModule when we create a new application using CLI's new command. In the application root module, the provider is associated with a built-in DI token to configure the root injector for the bootstrapper.

Angular also adds ComponentFactoryResolver to the root module injector. This parser stores the entryComponents series factory, so it is responsible for dynamically creating components.

Module injector level

At this point, we can simply sort out the hierarchical relationship of the module injector:

At the top of the module injector tree is the application root module (AppModule) injector, called root.

There are also two injectors on top of root, one is the platform module (PlatformModule) injector, and the other is NullInjector ().

Therefore, the hierarchical structure of the module injector is as follows:

In our practical application, it is likely to look like this:

Angular DI has a hierarchical injection system, which means that subordinate injectors can also create their own service instances.

Element injector

As mentioned earlier, there are two injector hierarchies in Angular, the module injector and the element injector.

Introduction of element injector

When lazily loaded modules began to be widely used in Angular, an issue emerged: the dependency injection system caused the instantiation of lazy loading modules to double.

In this fix, a new design is introduced: the injector uses two parallel trees, one for elements and the other for modules.

Angular creates host factories for all entryComponents, which are the root view of all other components.

This means that every time we create a dynamic Angular component, we use the root data (RootData) to create the root view (RootView):

Class ComponentFactory_ extends ComponentFactory {create (injector: Injector, projectableNodes?: any [] [], rootSelectorOrNode?: string | any, ngModule?: NgModuleRef): ComponentRef {if (! ngModule) {throw new Error ('ngModule should be provided');} const viewDef = resolveDefinition (this.viewDefFactory); const componentNodeIndex = viewDef.nodes [0] .element! .principentProvider! .nodeIndex / / create the root view with root data const view = Services.createRootView (injector, projectableNodes | | [], rootSelectorOrNode, viewDef, ngModule, EMPTY_CONTEXT); / / view.nodes 's accessor const component = asProviderData (view, componentNodeIndex) .instance; if (rootSelectorOrNode) {view.renderer.setAttribute (asElementData (view, 0). RenderElement, 'ng-version', VERSION.full) } / / create component return new ComponentRef_ (view, new ViewRef_ (view), component);}}

The root data (RootData) contains references to the elInjector and ngModule injectors:

Function createRootData (elInjector: Injector, ngModule: NgModuleRef, rendererFactory: RendererFactory2, projectableNodes: any [] [], rootSelectorOrNode: any): RootData {const sanitizer = ngModule.injector.get (Sanitizer); const errorHandler = ngModule.injector.get (ErrorHandler); const renderer = rendererFactory.createRenderer (null, null); return {ngModule, injector: elInjector, projectableNodes, selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer, errorHandler,};}

The element injector tree is introduced because the design is relatively simple. By changing the injector hierarchy, interleaving the insertion module and the component injector is avoided, resulting in double instantiation of the delayed loading module. Because each injector has only one parent object, and each parsing must accurately find an injector to retrieve dependencies.

Element injector (Element Injector)

In Angular, a view is a representation of a template that contains different types of nodes, including an element node on which the element injector is located:

Export interface ElementDef {. / / the public provider of DI visible in this view publicProviders: {[tokenKey: string]: NodeDef} | null; / / is the same as visiblePublicProviders, but also includes the private provider allProviders: {[tokenKey: string]: NodeDef} | null;} located on this element

ElementInjector is empty by default unless it is configured in the providers property of @ Directive () or @ Component ().

When Angular creates an element injector for a nested HTML element, it either inherits it from the parent element injector or assigns the parent element injector directly to the child node definition.

If the element injector on a child HTML element has a provider, it should be inherited. Otherwise, there is no need to create a separate injector for the child components, and if necessary, you can resolve the dependencies directly from the parent injector.

Design of element injector and module injector

So where did the element injector and the module injector start to become parallel trees?

We already know that the application root module (AppModule) is automatically included in the root AppModule when you create a new application using the CLI's new command.

When the application (ApplicationRef) starts (bootstrap), the entryComponent is created:

Const compRef = componentFactory.create (Injector.NULL, [], selectorOrNode, ngModule)

This process creates the root view (RootView) using the root data (RootData), as well as the root element injector, where elInjector is Injector.NULL.

Here, Angular's injector tree is divided into element injector tree and module injector tree, which are parallel trees.

Angular regularly creates a subordinate injector, and whenever Angular creates a component instance with providers specified in @ Component (), it also creates a new subinjector for that instance. Similarly, when a new NgModule is loaded at run time, Angular can create an injector for it with its own provider.

The submodule and the component injector are independent of each other and create their own instances for the services provided. When Angular destroys NgModule or component instances, it also destroys these injectors and those service instances in the injector.

Angular parsing dependency process

Above we introduced two kinds of injector trees in Angular: module injector tree and element injector tree. So, how does Angular parse when providing dependencies?

In Angular, when parsing token acquisition dependencies for components / instructions, Angular parses it in two phases:

For the ElementInjector hierarchy (its parent)

For the ModuleInjector hierarchy (its parent)

The process is as follows (refer to multi-level injector-parsing rules):

When a component declares a dependency, Angular tries to use its own ElementInjector to satisfy the dependency.

If the injector of the component lacks a provider, it passes the request to the ElementInjector of its parent component.

These requests will continue to be forwarded until Angular finds an injector that can process the request or runs out of ancestor ElementInjector.

If Angular cannot find a provider in any ElementInjector, it returns to the element that initiated the request and looks it up in the ModuleInjector hierarchy.

If Angular still cannot find the provider, it will raise an error.

For this reason, Angular introduces a special merge injector.

Merge injector (Merge Injector)

The merge injector itself does not have any value, it is just a combination of view and element definitions.

Class Injector_ implements Injector {constructor (private view: ViewData, private elDef: NodeDef | null) {} get (token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {const allowPrivateServices = this.elDef? (this.elDef.flags & NodeFlags.ComponentView)! = 0: false; return Services.resolveDep (this.view, this.elDef, allowPrivateServices, {flags: DepFlags.None, token, tokenKey: tokenKey (token)}, notFoundValue);}}

When Angular parses dependencies, the merge injector acts as a bridge between the element injector tree and the module injector tree. When Angular attempts to resolve some dependencies in a component or instruction, it uses the merge injector to traverse the element injector tree, and then, if no dependencies are found, switch to the module injector tree to resolve the dependency.

Class ViewContainerRef_ implements ViewContainerData {. / / query for parent element injector get parentInjector (): Injector {let view = this._view; let elDef = this._elDef.parent; while (! elDef & & view) {elDef = viewParentEl (view); view = view.parentinjector;} return view? New Injector_ (view, elDef): new Injector_ (this._view, null);}} parsing process

The injector is inheritable, which means that if the specified injector cannot resolve a dependency, it requests the parent injector to resolve it. The specific parsing algorithm is implemented in the resolveDep () method:

Export function resolveDep (view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {/ mod1 / el1 mod2 / /\ / / el2 / when requesting el2.injector.get (token) Check and return the first value found in the following order: / /-el2.injector.get (token, default) / /-el1.injector.get (token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)-> do not check the module / /-mod2.injector.get (token, default)}

If it is the root AppComponent component of such a template, there will be three views in Angular:

Some content

Depending on the parsing process, the parsing algorithm is based on the view hierarchy, as shown in the figure:

"if you resolve some tokens in a subcomponent, Angular will:"

First look at the child element injector to check elRef.element.allProviders | publicProviders.

Then iterate through all the parent view elements (1) and check the provider in the element injector.

If the next parent view element is equal to null (2), return to startView (3) and check startView.rootData.elnjector (4).

Check startView.rootData module.injector (5) only if the token cannot be found.

Thus, when Angular traverses a component to resolve certain dependencies, it searches for the parent element of a particular view rather than the parent element of a particular element. The parent element of the view can be obtained in the following ways:

/ / for component views, this is the host element / / for embedded views, this is the index export function viewParentEl (view: ViewData) that contains the parent node of the view container: NodeDef | null {const parentView = view.parent; if (parentView) {return viewview [XSS _ clean] Def! .parent;} else {return null;}} "how to design multi-level dependency injection in Angular" ends here, thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report