Home>Article>Web Front-end> In-depth understanding of the dependency injection pattern in Angular (play case)

In-depth understanding of the dependency injection pattern in Angular (play case)

青灯夜游
青灯夜游 forward
2022-06-24 15:28:43 2599browse

This article will give you an in-depth understanding of the dependency injection mode inAngular, and share application and gameplay cases of the dependency injection mode. I hope it will be helpful to everyone!

In-depth understanding of the dependency injection pattern in Angular (play case)

1 Injection, a component tree hierarchical communication mode & design pattern

1.1 Component communication mode

In Angular project development, we usually use Input property binding and Output event binding for component communication. However, Input and Output can only pass information between parent and child components. Components form a component tree based on the calling relationship. If there are only property bindings and event bindings, then two non-direct relationship components need to communicate through each connection point itself. The middleman needs to continuously process and pass some things that it does not need to know. information (Figure 1 left). The Injectable Service provided in Angular can be provided in modules, components or instructions, and combined with injection in the constructor, can solve this problem (right in Figure 1). [Related tutorial recommendations: "angular tutorial"]

In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 1 Component communication mode

The picture on the left only transmits information through parent-child components , node a and node b need to communicate through many nodes; if node c wants to control node b through some configuration, the nodes between them must also set additional attributes or events to transparently transmit the corresponding information. The dependency injection mode node c in the picture on the right can provide a service for nodes a and b to communicate. Node a directly communicates with the service provided by node c, and node b also directly communicates with the service provided by node c. Finally, the communication is simplified, and the middle The node is not coupled to this part of the content, and has no obvious awareness of the communication that occurs between the upper and lower components.

1.2 Using Dependency Injection to Implement Inversion of Control

Dependency Injection (DI) is not unique to Angular, it is a means to implement the Inversion of Control (IOC) design pattern , The emergence of dependency injection solves the problem of over-coupling of manual instantiation. All resources are not managed by the two parties using the resources, but are provided by the resource center or a third party, which can bring many benefits. First, centralized management of resources makes resources configurable and easy to manage. Second, it reduces the degree of dependence between the two parties using resources, which is what we call coupling.

The analogy to the real world is that when we buy a product such as a pencil, we only need to find a store to buy a product of the type pencil. We don’t care where the pencil is produced. The wood and pencil lead are both How it is bonded? We only need it to complete the writing function of the pencil. We will not have any contact with the specific pencil manufacturer or factory. As for the store, it can purchase pencils from the appropriate channels and realize the configurability of resources.

Combined with coding scenarios, more specifically, users do not need to explicitly create an instance (new operation) to inject and use instances. The creation of instances is determined by providers. Resource management is through tokens. Since it does not care about the provider or the creation of instances, the user can use some local injection methods (secondary configuration of tokens) to finally achieve instance replacement and dependency injection mode. Applications and aspect programming (AOP) complement each other.

2 Dependency injection in Angular

Dependency injection is one of the most important core modules of the Angular framework. Angular not only provides Service type injection, but also its own component tree It is an injection dependency tree, and functions and values can also be injected. That is to say, in the Angular framework, child components can inject parent component instances through the parent component's token (usually the class name). In component library development, there are a large number of cases where interaction and communication are achieved by injecting parent components, including parameter mounting, state sharing, and even obtaining the DOM of the node where the parent component is located, etc.

2.1 Dependency resolution

To use Angular injection, you must first understand its injection resolution process. Similar to the parsing process of node_modules, when no dependencies are found, the dependencies will always bubble up to the parent layer to find dependencies. The old version of Angular (beforev6) divides the injection parsing process into multi-level module injectors, multi-level component injectors and element injectors. The new version (afterv9) is simplified to a two-level model. The first query chain is the static DOM level element injector, component injector, etc., collectively called element injectors, and the other query chain is the module injector. The order of parsing and the default value after parsing failure are explained more clearly in the official code comment document (provider_flag).

In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 2 Two-level injector search dependency process (Picture source)

That is to say, the component/instruction and the component/instruction level provide The injected content will first search for dependencies in the element in the component view all the way to the root element. If it is not found, it will then search in the module where the element is currently located and reference (including module reference and routing lazy loading reference) the parent module of the module until it is found. Root module and platform module.

Note that the injector here has inheritance. The element injector can create and inherit the search function of the injector of the parent element, and the module injector is similar. After continuous inheritance, it becomes a bit like the prototype chain of js objects.

2.2 Configuration provider

Understanding the order priority of dependency resolution, we can provide content at the appropriate level. We already know that it comes in two types: module injection and element injection.

  • Module injector: You can configure providers in the metadata attribute of @NgModule, and you can also use the @Injectable statement provided after v6. provideIn is declared as the module name, 'root', etc. (Actually, there are two injectors above the root module, Platform and Null. They will not be discussed here.)

  • Element injector: In the metadata attribute of component @Component You can configure providers, viewProviders, or providers in the @Directive metadata of the directive.

In addition, in fact, in addition to using the declaration module injector, the @Injectable decorator can also Declared as an element injector. More often it will be declared as provided at root to implement a singleton. It integrates metadata through the class itself to avoid modules or components directly explicitly declaring the provider. In this way, if the class does not have any component directive service and other classes to inject it, there will be no code linked to the type declaration and it can be ignored by the compiler, thus achieving Shake the tree.

Another way to provide it is to directly give the value when declaring InjectionToken.

Here are the shorthand templates for these methods:

@NgModule({ providers: [ // 模块注入器 ] }) export class MyModule {}
@Component({ providers: [ // 元素注入器 - 组件 ], viewProviders: [ // 元素注入器- 组件视图 ] }) export class MyComponent {}
@Directive({ providers: [ // 元素注入器 - 指令 ] }) export class MyDirective {}
@Injectable({ providedIn: 'root' }) export class MyService {}
export const MY_INJECT_TOKEN = new InjectionToken('my-inject-token', { providedIn: 'root', factory: () => { return new MyClass(); } });

Different options for providing dependency locations will bring some differences, which ultimately affect the size of the package and the dependencies that can be injected. Scope and dependency lifecycle. There are different applicable solutions for different scenarios, such as singleton (root), service isolation (module), multiple editing windows (component), etc. You should choose a reasonable location to avoid inappropriate shared information or redundant code packaging. .

2.3 Various value function tools

If you only provide instance injection, it will not show the flexibility of Angular framework dependency injection. Angular provides many flexible injection tools. useClass automatically creates new instances, useValue uses static values, useExisting can reuse existing instances, and useFactory is constructed through functions, with specified deps and specified constructor parameters. These combinations can be very versatile. . You can cut off the token token of a class and replace it with another instance you have prepared. You can create a token to save the value or instance first, and then replace it again when you need to use it later. You can even use the factory function to return it. The local information of the instance is mapped to another object or attribute value. The gameplay here will be explained through the following cases, so I won’t go into it here. The official website also has many examples for reference.

2.4 Injection consumption and decorator

Injection in Angular can be injected in the constructor, or you can get the injector to obtain the existing one through the get method Inject elements.

Angular supports adding decorators to mark when injecting,

  • @Host() to limit bubbling
  • @Self() to limit it to the element itself
  • @SkipSelf() Limit to the element itself
  • @Optional() Mark as optional
  • @Inject() Limit to the custom Token token

Here is an article "@Self or @Optional @Host? The visual guide to Angular DI decorators." which very vividly shows what will happen if different decorators are used between parent and child components. What is the difference between hit instances.

In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 3 Filtering results of different injection decorators

2.4.1 Supplement: Host view and @Host

Among these decorators, @Host may be the hardest to understand. Here are some specific instructions for @Host. The official explanation of the @Host decorator is

...retrieve a dependency from any injector until reaching the host element

Host here means host. The @Host decorator will limit the scope of the query to within the host element. What is a host element? If component B is a component used by component A's template, then component A's instance is the host element of component B's instance. The content generated by the component template is called a View. The same View may be different views for different components. If component A uses component B within its own template scope (see Figure 4), the view (red box part) formed by the template content of A is the embedded view of component A, and component B is within this view, so For B, this view is B's host view. The decorator @Host limits the search scope to the host view. If it is not found, it will not bubble up.

In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 4 Embedded view and host view

3 Cases and gameplay

Below we use real Let’s take a case to see how dependency injection works, how to troubleshoot errors, and how to play.

3.1 Case 1: The modal window creates a dynamic component, but the component cannot be found

The modal window component of the DevUI component library provides a service ModalService, which A modal box can pop up and can be configured as a custom component. Business students often report errors when using this component, saying that the package cannot find the custom component.

For example, the following error report:

In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 5 When using ModalService, the error report of creating a component that references EditorX cannot find the corresponding service provider

Analyze how ModalService creates custom components,ModalService source code Open functionLines 52 and 95. You can see that ifcomponentFactoryResolveris not passed in, thecomponentFactoryResolverinjected by ModalService will be used. In most cases, the business will introduce DevUIModule once in the root module, but will not introduce ModalModule in the current module. That is, the current situation in Figure 6 is like this. According to Figure 6, there is no EditorXModuleService in the injector of ModalService.

In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 6 Module service provision relationship diagram

According to the inheritance of the injector, there are four solutions:

  • Put EditorXModule where ModalModule is declared, so that the injector can find the EditorModuleService provided by EditorXModule - this is the worst solution. The lazy loading implemented by loadChildren itself is to reduce the loading of the homepage module, and the result is a subpage The content that needs to be used is placed in the AppModule. The large rich text module is loaded on the first load, which aggravates the FMP (First Meaningful Paint) and cannot be adopted.

  • Introduce ModalService in the module that introduces EditorXModule and uses ModalService - it is advisable. There is only one situation that is not advisable, that is, calling ModalService is another top-level public Service, which still puts unnecessary modules on the upper layer for loading.

  • When triggering the component that uses ModalService, inject thecomponentFactoryResolverof the current module and pass it to the open function parameter of ModalService - It is advisable to use it where it is actually used. Introducing EditorXModule.

  • In the module used, manually provide a ModalService - it is advisable to solve the problem of injected search.

The four methods are actually solving the problem of EditorXModuleService in the internal chain of the injector used by ModalService. By ensuring that the search chain is at two levels, this problem can be solved.

Summary of knowledge points

: Module injector inheritance and search scope.

3.2 Case 2: CdkVirtualScrollFor cannot find CdkVirtualScrollViewport

Usually when we use the same template in multiple places, we will extract the common part through template. The previous DevUI Select component During development, the developer wanted to extract the shared parts and reported an error.

In-depth understanding of the dependency injection pattern in Angular (play case)

In-depth understanding of the dependency injection pattern in Angular (play case)Figure 7 Code movement and injection error not found

This is because the CdkVirtualScrollFor instruction needs to inject a CdkVirtualScrollViewport. However, the element injection injector inheritance system inherits the DOM of the static AST relationship, and the dynamic one is not possible. Therefore, the following query behavior occurs, and the search fails.

In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 8 Element Injector Query Chain Search Range

Final solution: Either 1) keep the original code position unchanged, or 2) need to change the entire You can find it embedded in the template.

In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 9 Embed the whole module so that CdkVitualScrollFo can find CdkVirtualScrollViewport (Solution 2)

Summary of knowledge points: Element injection The query chain of the controller is the DOM element ancestor of the static template.

3.3 Case 3: The form verification component is encapsulated into a sub-component and cannot be verified

This case comes from this blog "Angular: Nested template driven form》.

We also encountered the same problem when using form validation. As shown in Figure 10, for some reasons we encapsulate the addresses of the three fields into a component for reuse.

1In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 10 Encapsulate the three address fields of the form into a sub-component

At this time we will find that an error is reported,ngModelGroupAControlContainerinside the host is required, which is the content provided by the ngForm directive.

1In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 11 ngModelGroup cannot find ControlContainer

Looking at the ngModelGroup code, you can see that it only adds the restriction of the host decorator.

1In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 12 ng_model_group.ts limits the scope of injected ControlContainer

Here you can use viewProvider with usingExisting to add the Provider of ControlContainer to the host view of AddressComponent

1In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 13 Use viewProviders to provide external Providers for nested components

Summary of knowledge points: The wonderful combination of viewProvider and usingExisting.

3.4 Case 4: The service provided by the drag and drop module is no longer a singleton due to lazy loading, resulting in the inability to drag and drop each other

The internal business platform involves cross-border Dragging of multiple modules involves lazy loading of loadChildren. Each module will separately package the DragDropModule of the DevUI component library, which provides a DragDropService. Drag-and-drop instructions are divided into Draggable instructions and Droppable instructions. The two instructions communicate through DragDropService. Originally, it was possible to communicate by introducing the same module and using the Service provided by the module. However, after lazy loading, the DragDropModule module was packaged twice, which also resulted in two isolated instances. At this time, the Draggable instruction in a lazy-loaded module cannot communicate with the Droppable instruction in another lazy-loaded module, because the DragDropService is not the same instance at this time.

1In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 14 Lazy loading of modules leads to services not being the same instance/single case

It is obvious that our statement requires a singleton, and the singleton approach UsuallyproviderIn: 'root'is fine, then why don't the DragDropService of the component library be provided at the module level and directly provide the root domain. But if you think about it carefully, there are other problems here. The component library itself is provided for use by a variety of businesses. If some businesses have two corresponding drag and drop groups in two places on the page, they do not want to be linked. At this time, the singleton destroys the natural isolation based on the module.

Then it would be more reasonable to implement singleton replacement by the business side. Remember the dependency query chain we mentioned earlier. The element injector is searched first. If it is not found, the module injector is started. So the replacement idea is that we can provide element-level providers.

1In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 15 Use the extension method to obtain a new DragDropService and mark it as provided at the root level

1In-depth understanding of the dependency injection pattern in Angular (play case)

1In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 16 You can use the same selector to superimpose repeated instructions, superimpose an additional instruction on the Draggable instruction and Droppable instruction of the component library, and replace the token of DragDropService with the DragDropGlobalService that has provided a singleton in the root

For example Figures 15 and 16, we use the element injector to superimpose instructions and replace the DragDropService token with an instance of our own global singleton. At this time, where we need to use the global singleton DragDropService, we only need to introduce the module that declares and exports these two extra instructions to enable the Draggable instruction Droppable instruction of the component library to communicate across lazy loading modules.

Summary of knowledge points: Element injectors have higher priority than module injectors.

3.5 Case 5: How to attach the drop-down menu to the local problem in the local theme function scenario

The theming of the DevUI component library uses CSS custom attributes (css variables ) declares: root’s css variable value to achieve theme switching. If we want to display previews of different themes at the same time in one interface, we can re-declare css variables locally in the DOM element to achieve the function of local themes. When I was making a theme dither generator before, I used this method to locally apply a theme.

1In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 17 Partial theme function

But just applying css variable values locally is not enough. There are some drop-down pop-up layers that are attached to the end of the body by default. , that is to say, its attachment layer is outside the local variable, which will cause a very embarrassing problem. The drop-down box of the local theme component displays the style of the external theme.

In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 18 The overlay drop-down box theme attached to the external component in the local theme is incorrect

What should I do at this time? We should move the attachment point back inside the local theme dom.

It is known that the Overlay of the DatePickerPro component of the DevUI component library uses the Overlay of Angular CDK. After a round of analysis, we replaced it with injection as follows:

1) First, we inherit OverlayContainer and implement our own ElementOverlayContainer is shown below.

2In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 19 Customize ElementOverlayContainer and replace _createContainer logic

2) Then directly provide our new ElementOverlayContainer on the component side of the preview, and provide new Overlay so that the new Overlay can use our OverlayContainer. Originally Overlay and OverlayContainer are provided on root, here we need to cover these two.

2In-depth understanding of the dependency injection pattern in Angular (play case)

##Figure 20 Replace OverlayContainer with a custom ElementOverlayContainer and provide a new Overlay

At this time, go to preview the website, and the DOM of the pop-up layer will be successfully Attached to the component-preview element.

2In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 21 The Overlay container of cdk is attached to the specified dom, and the partial theme preview is successful

There is also a custom OverlayContainerRef inside the DevUI component library Some components and modal box drawer benches also need to be replaced accordingly. Finally, pop-up layers and other pop-up layers can be realized to perfectly support local themes.

Summary of knowledge points: Good abstract patterns can make modules replaceable and achieve elegant aspect programming.

3.6 Case 6: CdkOverlay requires the CdkScrollable instruction to be added to the scroll bar, but it cannot be added to the outermost layer of the entry component. How to deal with it

The last one is reached As a case study, I would like to talk about a less formal approach to help everyone understand the essence of the provider. Configuring the provider is essentially to let it help you instantiate or map to an existing instance.

We know that if cdkOverlay is used, if we want the pop-up box to follow the scroll bar and be suspended in the correct position, we need to add the cdkScrollable instruction to the scroll bar.

It’s still the same scene as the previous example. Our entire page is loaded through routing. For simplicity, I wrote the scroll bar on the host of the component.

2In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 22 The content overflow scroll bar writes overflow:auto in the component:host

In this way, we encounter a more difficult problem. The module is specified by the router definition, that is,

is not explicitly called anywhere. So what about the cdkScrollable instruction? What about adding it? The solution is as follows. Some of the code is hidden here and only the core code is left.

2In-depth understanding of the dependency injection pattern in Angular (play case)

Figure 23 Create an instance through injection and manually call the life cycle

Here, an instance of cdkScrollable is generated through injection, and the life cycle is called synchronously during the component's life cycle stage.

This solution is not a formal method, but it does solve the problem. It is left here as an idea and exploration for the readers to taste.

Summary of knowledge points: Dependency injection configuration provider can create instances, but please note that instances will be treated as ordinary Service classes and cannot have a complete life cycle.

3.7 More ways to play: Customize the replacement platform to realize the interaction of Angular framework running on the terminal

You can refer to this blog post: "Rendering Angular applications in Terminal

2In-depth understanding of the dependency injection pattern in Angular (play case)

##Figure 24 Replace the RendererFactory2 renderer and other contents to let Angular run on the terminal

The author replaces the RendererFactory2 and so on Renderer allows Angular applications to run on the terminal. This is the flexibility of Angular design. Even the platform can be replaced. It is powerful and flexible. Detailed replacement details can be found in the original article and will not be expanded upon here.

Summary of knowledge points: The power of dependency injection is that the provider can configure it by itself and finally implement the replacement logic.

4 Summary

This article introduces the dependency injection mode of control inversion and its benefits. It also introduces how dependency injection in Angular finds dependencies and how to configure providers. How to use limited and filtering decorators to get the desired instance, and further analyze through N cases how to combine the knowledge points of dependency injection to solve problems encountered in development and programming.

With a correct understanding of the dependency search process, we can configure the provider at the exact location (Case 1 and 2), replace other instances as singletons (Case 4 and 5), and even cross-nest The constraints of the component package are connected to the provided instance (Case 3) or the provided method curve is used to implement the instruction instantiation (Case 6).

Case 5 seems to be a simple replacement, but to be able to write a code structure that can be replaced requires an in-depth understanding of the injection mode and a better and reasonable abstraction of each function. Abstraction must not be allowed When this happens, the maximum effect of dependency injection cannot be exerted. The injection mode provides more possible space for modules to be pluggable, plug-in, and part-based, reducing coupling and increasing flexibility, so that modules can work together more elegantly and harmoniously.

The dependency injection function is powerful. In addition to optimizing component communication paths, more importantly, it can also achieve control inversion, exposing encapsulated components to more aspects of programming and the implementation of some business-specific logic. You can also become flexible.

For more programming related knowledge, please visit:

Programming Video! !

The above is the detailed content of In-depth understanding of the dependency injection pattern in Angular (play case). For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete