编程知识 cdmana.com

Theory + cases, which will help you master the application of angular dependency injection mode

This article is shared from Huawei cloud community 《
Angular Application and play cases of dependency injection mode
》, author :DevUI .

Inject , A component tree level communication mode  &  Design patterns

Component communication mode


stay Angular In engineering development , Usually we use Input Property binding and Output Event binding for component communication , However Input and Output But information can only be passed in parent-child components . Components form a component tree according to the calling relationship , If only attribute binding and event binding , Then two components that are not directly related need to communicate , You need to go through each connection point itself , A middleman needs to constantly process and pass on information that he does not need to know ( Pictured 1 Left ). and Angular Provided in Injectable Of Service, It can be in the module 、 Components or instructions provide , Collocation in constructor Injection , Just can solve this problem ( chart 1 Right ).

null
chart 1  Component communication mode

In the left figure, information is transmitted only through parent-child components , node a And nodes b Communication needs to go through many nodes ; If node c You want to control the node through some configuration b, The nodes among them must also set additional properties or events to transparently transmit the corresponding information . The dependency injection pattern node in the right figure c You can provide one for the nodes a、b Communication services , node a Direct and node c Provide   Service communications , node b Also directly with the node c Service communication provided , Finally, communication is simplified , The intermediate node is not coupled with this part , There is no obvious perception of the communication between the upper and lower components .

Use dependency injection to achieve control inversion


Dependency injection (DI) Not at all Angular Peculiar , It is to realize control inversion (IOC) The means of designing patterns , The emergence of dependency injection solves the problem of manual instantiation over coupling , All resources are not managed by both parties using the resources , Provided by a third party without using the resource center , This can bring many benefits . First of all , Centralized resource management , To realize resource configuration and easy management . second , Reduce the dependence of both sides of using resources , That's what we call coupling .

Analogy with the real world is , We go shopping for things like a pencil , We just need to find a shop to buy a pencil , We don't care where this pencil comes from , How wood and pencil lead are bonded , We only need it to complete the writing function of the pencil , We will not contact specific pencil manufacturers or factories . And for stores , It can purchase pencils through appropriate channels , Realize the configurability of resources .

Combined with coding scenarios , More specifically , Users do not need to explicitly create instances (new operation ), You can inject and use instances , The instance is created by the provider (providers) decision . Resources are managed through tokens (token), Because you don't care about the provider , Do not care about instance creation , The user can use some local injection methods ( Yes token Perform secondary configuration ), Finally, the replacement instance , Application of dependency injection pattern and aspect programming (AOP) Exist side by side and play a part together .

Angular Dependency injection in


Dependency injection is Angular One of the most important core modules of the framework ,Angular Not only provide Service Type of Injection , The component tree itself is an injection dependency tree ,  Functions and values can also be injected . That is to say Angular In the frame , A child component can pass through a parent component token( Usually the class name ), Injected into the parent component instance . There are a lot of cases in component library development by injecting parent components , To achieve interaction and communication , Including parameter mounting , State sharing , Even get the node of the parent component DOM wait .

Analytical dependence


To use Angular The injection of , First of all, we must understand the process of injection and resolution . Be similar to node_modules Analytic process , When a dependency cannot be found, it will always bubble to the parent layer to find the dependency . Old edition (v6 front ) Of Angular The injection parsing process is divided into multi-level module injectors , Multi level component injector and element injector . new edition (v9 after ) Reduced to a two-level model , The first query chain is static DOM Level element injector 、 Component injectors are collectively referred to as element injectors , Another query chain is the module injector . The sequence of parsing and the default value after parsing failure. The official code annotation document (
provider_flag
) It is quite clear in .

null
chart 2  The two-level injector looks for dependent procedures  (  picture source )

That is, components / Instructions as well as in components / The instruction level provides the injection content, and will first look for the dependency in the element in the component view until the root element , If it is not found, the module in which the element is currently located , quote ( Contains module references and route lazy load references ) The parent module of the module is searched up to the root module and the platform module at a time .

Note that the injector here has inheritance , The element injector can create and inherit the lookup function of the parent element's injector , The module injector is similar . After continuous inheritance , It's kind of like js Object's prototype Chain .

Configuration provider


Understand the order and priority of dependency resolution , We can provide content at the right level . We already know that it has two types : Module injection and element injection .

  • Module injector : stay @NgModule Can be configured in the metadata attribute of providers, You can also use v6 Later on @Injectable Statement provideIn Declared as module name 、'root’ etc. .( In fact, root There are also two injectors above the root module ,Platform and Null, They are not discussed here .)
  • Element injector : In components @Component Can be configured in the metadata attribute of providers,viewProviders,  Or in the order of @Directive In metadata providers.

in addition , actually @Injectable The decorator uses a declarative module injector , It can also be declared as an element injector . More often, it is declared as in root Provide , To implement the singleton . It integrates metadata by the class itself to avoid explicit declaration of modules or components provider, In this way, if the class does not have any component instruction service or other classes injected into it , There is no code to link to the type declaration , Can be ignored by the compiler , Thus, the shaking tree is realized .

Another way to provide this is to declare InjectionToken Give the value directly when .

Here are the sketch templates of these methods :


@NgModule({

providers: [

//  Module injector

]

})

export class MyModule {}


@Component({

providers: [

//  Element injector  -  Components

],

viewProviders: [

//  Element injector -  Component view

]

})

export class MyComponent {}


@Directive({

providers: [

//  Element injector  -  Instructions

]

})

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();

}

});

Different choices about where to provide dependencies will make some differences , It ultimately affects the size of the package , The scope of the dependency that can be injected and the lifecycle of the dependency . For different scenes , Such as single example (root), Service isolation (module), Multiple edit windows (component) And so on have different applicable schemes , A reasonable location should be selected , Avoid sharing inappropriate information , Or code packaging redundancy .

A variety of value function tools


If you only provide instance Injection , That doesn't show Angular The flexibility of framework dependency injection .Angular Provides many flexible injection tools ,useClass  Automatically create new instances ,useValue  Use static values , useExisting  You can reuse existing instances ,useFactory  Construct by function , Collocation specifies  deps  Specify constructor parameters , These combinations can be very versatile . You can cut your beard halfway token Replace the token with another self prepared instance , You can make one token Save values or instances first , Then it will be replaced when it is needed later , You can even use factory functions to return local information of an instance to map to another object or attribute value . The playing method here will be explained through the following cases , We're not going to start here . There are also many examples on the official website .

Inject consumer and decorator


Angular The injection in can be in the constructor constructor Internal injection , You can also get the injector injector adopt get Method to get an existing injection element .

Angular It supports adding decorators for marking during injection ,

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

Here's an article 《@Self still @Optional @Host?《 It vividly shows that if different decorators are used between parent and child components , What is the difference between the instances that will hit in the end .

null
chart 3  Screening results of different injection decorators
Add : Host views and @Host

Inside these ornaments , The most difficult thing to understand is @Host 了 , Here's some more @Host Specific description of .
The official response to @Host The explanation of decorator is

... Retrieve dependencies from any injector , Until the host element is reached

Host Here it means host ,@Host This decorator will restrict the scope of the query to the host element (host element) within . What is a host element ? If B Components are A Components used by component templates , that A Component instances are B The host element of the component instance . The content generated by the component template is called View( View ), The same View It may be different views for different components . If A Components are used within their own templates B Components ( See the picture 4),A A view formed by the content of the template ( The red box part ) Yes A Components are A Embedded view of ,B Components are in this view , So for B This view is B Host view . Decorator @Host Is to limit the search scope to the host view , No more bubbling if you can't find it .

null
chart 4  Embedded view and host view

Case and play


Let's take a real case , Let's see how dependency injection works , How to check for errors , And how to play .

Case a :  Modal windows create dynamic components , Component problem not found


DevUI The modal window component of the component library provides a service ModalService, The service can pop up a modal box , And it can be configured as a custom component . Business students often report errors when using this component , Package cannot find custom component .
For example, the following error reports :

null
chart 5  Use ModalService Create a reference when EditorX The corresponding service provider cannot be found for the error message of the component of

analysis ModalService How to create a custom component ,
ModalService Source code Open function
&nbsp; The first 52 Xing He 95 That's ok . Can see ,componentFactoryResolver If not, use ModalService Injected componentFactoryResolver. And most of the time , The business will be introduced once in the root module DevUIModule, But it will not be introduced in the current module ModalModule. That is, the status quo 6 That's true . According to the figure 6,ModalService Of injector There is no EditorXModuleService Of .

null
chart 6  Module service provision diagram

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

  • hold  EditorXModule  Put it in  ModalModule  Place of declaration , So the injector can find EditorXModule Provided EditorModuleService ——  This is the worst solution , In itself loadChildren The lazy loading of the implementation is to reduce the loading of the home page module , The result is that the content needed in the subpage is placed in AppModule, The large rich text module is loaded for the first time , It's aggravating FMP(First Meaningful Paint), Do not take .
  • In the introduction of  EditorXModule  And the use of  ModalService  In the module of  ModalService ——  Take . There is only one case that is not advisable , It's called  ModalService  Is another top-level public  Service, In this way, unnecessary modules are loaded on the upper layer .
  • Use... When triggering ModalService The components of , Inject the current module componentFactoryResolver, And to the ModalService Of open Function parameter  ——  Take ,  It can be introduced where it is actually used EditorXModule.
  • In the module used , Manually provide a ModalService ——  Take , Solved the problem of injection search .

All four methods are actually solving  ModalService  Used componentFactoryResolver Of injector There are... On the internal chain EditorXModuleService problem . Ensure that you are on the two-tier search chain , This problem can be solved .
Summary of knowledge points : Module injector inheritance and lookup scope .

Case 2 :CdkVirtualScrollFor  Can't find  CdkVirtualScrollViewport


Usually when we use the same template in multiple places , Will pass  template  Extract public part , Before  DevUI Select During component development, developers want to extract the shared parts and report errors .

null
null
chart 7  Code movement and injection error not found

This is because  CdkVirtualScrollFor The instruction needs to inject a CdkVirtualScrollViewport, However, element Injection injector An inheritance system is an inheritance static AST Relational DOM, Dynamic doesn't work , So the following query behavior occurs , Failed to find the post alarm .

null
chart 8  Element injector query chain lookup range

The final solution :: or 1) Keep the original code location unchanged , or 2) You need to embed the entire template to find it .

null
chart 9  The embedded whole module enables CdkVitualScrollFo Can find CdkVirtualScrollViewport( Solution 2 )

Summary of knowledge points : The query chain of the element injector is a static template DOM Element ancestors .

Case three :  The component of form verification is encapsulated in the sub component, and the problem cannot be verified


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

We also encountered the same problem when using form verification . Pictured 10 Shown , For some reason, we encapsulate the addresses of the three fields into a component for reuse .

null
chart 10  Encapsulate the address three fields of the form into a sub component

At this time, we will find that the report is wrong ,ngModelGroup Need one host Inside ControlContainer, That is to say ngForm What the Directive provides .

null
chart 11 ngModelGroup  Can't find ControlContainer

see ngModelGroup The code can see that it only adds host Limitations of decorators .

null
chart 12 ng_model_group.ts Defines the injection ControlContainer The scope of the

Here you can use viewProvider collocation usingExisting to AddressComponent The host view of is added ControlContainer Of Provider

null
chart 13  Use viewProviders Provide external to nested components Provider

Summary of knowledge points :viewProvider  and  usingExisting  The wonderful use of collocation .

Case four : Drag and drop the services provided by the module , Due to lazy loading , Not a single case , This makes it impossible to drag and drop each other


The internal business platform involves drag and drop across multiple modules , Because it involves loadChildren Lazy loading , Each module is packaged separately DevUI Component library DragDropModule, The Module Provides a DragDropService. Drag and drop instructions are divided into drag and drop instructions Draggable And placeable instructions Droppable, Two instructions pass DragDropService communicate . Originally, the same module was introduced to use the services provided by the module to communicate , But after lazy loading DragDropModule The module is packaged twice , It also generates two isolated instances . At this time, you are in a lazy loading module Draggable The instruction cannot interact with another lazy load module Droppable Instructions are communicated , Because at this time DragDropService Not the same instance .

null
chart 14  Lazy loading of modules causes the service to be different from the same instance / Single case

It is obvious here that our statement requires a single example , The singleton method is usually providerIn: 'root' Just fine , So let the component library DragDropService Do not provide modules at level , Provide directly root How are you from this sector . But think about it carefully , There will be other problems . The component library itself is provided for a variety of business use , In case some businesses have two corresponding drag and drop groups in two places on the page and do not want to be linked . In this case, the singleton will destroy the natural isolation based on modules .
It would be more reasonable for the business side to replace the singleton . Remember the dependency query chain we mentioned earlier , The element injector is the first to be searched , The module injector cannot be found until it is found . So the replacement idea is that we provide element level provider that will do .

null
chart 15  Use the extension method to get a new DragDropService And mark it as in root Level provides

null
null
chart 16  Use the same selector Repeated instructions can be superimposed , For the component library Draggable Instructions and Droppable Instruction superimposes an additional instruction and puts DragDropService Of token Replace with already in root Provide a single example of DragDropGlobalService

Pictured 15 and 16,  We use the element injector , Superimposed instructions , hold DragDropService This token is replaced by an instance of our own global singleton . In this case, you need to use the global singleton DragDropService The place of , We just need to introduce the declaration and export these two extra The module of the instruction makes the component library Draggable Instructions Droppable Instructions can communicate across lazy load modules .

Summary of knowledge points : Element injector has priority over module injector .

Case 5 :  How to attach a drop-down menu to a local problem in a local topic function scenario


DevUI The theming of the component library uses CSS Custom properties (css Variable ) Statement :root Of css Variable values to enable topic switching . If we want to show previews of different topics in one interface , We can do it in DOM Element local redeclare css Variables to achieve the function of local topics . Previously, we used this method to apply a theme locally when making a theme color imitation generator .

null
chart 17  Local theme function

But only partial application css Variable values are not enough , There are some drop-down pop-up layers that are attached by default body The last one , That is, its attachment layer is outside the local variable , This will lead to a very embarrassing problem . The drop-down box of the component of the local theme is the style of the external theme .

null
chart 18  The theme of the overlay drop-down box outside the component attached in the local theme is incorrect

Now what? ? We should move the attachment point back to the local topic dom Inside .

It is known that DevUI Component library DatePickerPro Component's Overlay It uses Angular CDK Of Overlay, After a round of analysis, we replace the following with injection :

1) First we inherit OverlayContainer And realize their own ElementOverlayContainer Here's the picture .

null
chart 19  Customize ElementOverlayContainer And replace _createContainer Logic

2) Then on the component side of the preview , Directly provide our new ElementOverlayContainer, And provide new Overlay, So that new Overlay Can use our OverlayContainer. Original Overlay and OverlayContainer Are available at root On , Here we need to cover these two .

null
chart 20  Replace OverlayContainer For custom ElementOverlayContainer, Offer a new Overlay

Then go to preview the website , Pop up layer DOM Is successfully attached to component-preview This element contains .

null
chart 21 cdk Of Overlay The container is attached to the specified dom Inside ,  Partial theme preview succeeded

DevUI There are also custom components in the component library OverlayContainerRef Used for some components and modal frame drawer bench , It also needs to be replaced accordingly . Finally, the pop-up layer can perfectly support local themes .

Summary of knowledge points : Good abstract patterns make modules replaceable , Achieve elegant aspect programming .

Case 6 : CdkOverlay It is required to add... To the scroll bar CdkScrollable Instructions , However, it is impossible to add this instruction to the outermost layer of the entry component


To the last case , I want to talk about some informal practices , So that we can understand provider The essence of , To configure provider Essentially, let it help you instantiate or map to an existing instance .

We know that if cdkOverlay, If we want the pop-up box to float in the correct position even if it scrolls with the scroll bar , We need to add... To the scroll bar cdkScrollable Instructions .

Or the scenario of the previous example . Our entire page is loaded through the route , For simplicity, I wrote the scroll bar on the component host 了 .

null
chart 22  Content overflow scroll bar handle overflow:auto  It is written in the component :host in

In this way, we have encountered a difficult problem , The module is router Define the specified , That is, there is no place to explicitly call <app-theme-picker-customize></app-theme-picker-customize>, that cdkScrollable How to add the instructions ? The solution is as follows , Part of the code is hidden here, leaving only the core code .

null
chart 23  Create instances through injection and manually invoke the lifecycle

Here we generate a by injection cdkScrollable Example , And invoke the life cycle synchronously in the life cycle phase of the component .
This solution is not a formal means , But it did solve the problem , Here, as a way of thinking and exploration, it is left to the readers to taste .
Summary of knowledge points :  Dependency injection configuration providers can create instances , Note, however, that instances will be treated as normal Service Class treatment , Cannot have a full lifecycle .

More ways to play :  Custom replace platform, Let's realize Angular The interaction of the framework running on the terminal


You can refer to this post :《 Render the angle application in the terminal 》

null
chart 24  Replace RendererFactory2 Renderer, etc ,  Give Way Angular Run on the terminal

By substituting RendererFactory2 Wait for the render , Give Way Angular Applications can run on the terminal . This is it. Angular Design flexibility , even platform Can be replaced by powerful flexibility . See the original article for detailed replacement details , It's not going to unfold here .
Summary of knowledge points : The power of dependency injection , The point is that the provider can configure , Finally, the replacement logic is implemented .

summary


This paper introduces the dependency injection mode of control inversion and its benefits , It introduces Angular How dependency injection finds dependencies in , How to configure providers , How to get the desired instance with the decorator of limiting and filtering function , Further adoption N A case study on how to combine the knowledge points of dependency injection to solve the problems encountered in development programming .

Correctly understand the dependency lookup process , Then we can configure the supplier in the exact location ( Case 1 and 2 ), Cut the beard and replace other instances with a single instance ( Case four 、 5、 ... and ), It can even wrap constraints across nested components to link up the provided instances ( Case three ) Or use the provided method curve to implement instruction instantiation ( Case 6 ).

Case 5 seems to be a simple replacement , But to be able to write code structures that can be replaced requires a deep understanding of injection patterns , And each function has a good and reasonable abstraction , Improper abstraction , The maximum effect of dependency injection cannot be achieved . The injection mode is module pluggable , pluggable , Parts provide more possibilities , Reduce coupling , Increase flexibility , It is more elegant between modules 、 Work together harmoniously .

The power of dependency injection , In addition to optimizing component communication paths , More importantly, it can also realize control inversion , Expose more aspects of programming to encapsulated components , The implementation of some special business logic can also become flexible .

Click to follow , The first time to learn about Huawei's new cloud technology ~

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://cdmana.com/2022/174/202206231832548167.html

Scroll to Top