


In-depth understanding of the dependency injection pattern in Angular (play case)
This article will give you an in-depth understanding of the dependency injection mode in Angular, and share application and gameplay cases of the dependency injection mode. I hope it will be helpful to everyone!
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"]
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 (before v6) divides the injection parsing process into multi-level module injectors, multi-level component injectors and element injectors. The new version (after v9) 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).
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<myclass>('my-inject-token', { providedIn: 'root', factory: () => { return new MyClass(); } });</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.
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.
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:
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 function Lines 52 and 95. You can see that if componentFactoryResolver
is not passed in, the componentFactoryResolver
injected 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.
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 the
componentFactoryResolver
of 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.
: Module injector inheritance and search scope.
3.2 Case 2: CdkVirtualScrollFor cannot find CdkVirtualScrollViewportUsually 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.
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.
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.
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.
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, ngModelGroup
A ControlContainer
inside the host is required, which is the content provided by the ngForm directive.
Figure 11 ngModelGroup cannot find ControlContainer
Looking at the ngModelGroup code, you can see that it only adds the restriction of the host decorator.
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
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.
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 Usually providerIn: '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.
Figure 15 Use the extension method to obtain a new DragDropService and mark it as provided at the root level
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.
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.
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.
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.
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. 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.
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》
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!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

This article will take you to continue learning angular and briefly understand the standalone component (Standalone Component) in Angular. I hope it will be helpful to you!

Angular.js is a freely accessible JavaScript platform for creating dynamic applications. It allows you to express various aspects of your application quickly and clearly by extending the syntax of HTML as a template language. Angular.js provides a range of tools to help you write, update and test your code. Additionally, it provides many features such as routing and form management. This guide will discuss how to install Angular on Ubuntu24. First, you need to install Node.js. Node.js is a JavaScript running environment based on the ChromeV8 engine that allows you to run JavaScript code on the server side. To be in Ub

Do you know Angular Universal? It can help the website provide better SEO support!

This article will give you an in-depth understanding of Angular's state manager NgRx and introduce how to use NgRx. I hope it will be helpful to you!

How to use monaco-editor in angular? The following article records the use of monaco-editor in angular that was used in a recent business. I hope it will be helpful to everyone!

This article will share with you an Angular practical experience and learn how to quickly develop a backend system using angualr combined with ng-zorro. I hope it will be helpful to everyone!

With the rapid development of the Internet, front-end development technology is also constantly improving and iterating. PHP and Angular are two technologies widely used in front-end development. PHP is a server-side scripting language that can handle tasks such as processing forms, generating dynamic pages, and managing access permissions. Angular is a JavaScript framework that can be used to develop single-page applications and build componentized web applications. This article will introduce how to use PHP and Angular for front-end development, and how to combine them

This article will take you through the independent components in Angular, how to create an independent component in Angular, and how to import existing modules into the independent component. I hope it will be helpful to you!
