In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article mainly introduces how to use dependency injection in Angular, has a certain reference value, interested friends can refer to, I hope you can learn a lot after reading this article, let the editor take you to understand it.
Application scenarios for different types of provider of useFactory, useClass, useValue and useExisting
Next, we use practical examples to illustrate the usage scenarios of several providers.
UseFactory factory provider
One day, we received a request to implement a local storage function and inject it into the Angular application so that it can be used globally in the system.
First of all, write the service class storage.service.ts to realize its storage function.
/ / storage.service.tsexport class StorageService {get (key: string) {return JSON.parse (localStorage.getItem (key) | |'{}') | {};} set (key: string, value: ITokenModel | null): boolean {localStorage.setItem (key, JSON.stringify (value)); return true;} remove (key: string) {localStorage.removeItem (key);}}
If you try to use it in user.component.ts right away
/ / user.component.ts@Component ({selector: 'app-user', templateUrl:'. / user.component.html', styleUrls: ['. / user.component.css']}) export class CourseCardComponent {constructor (private storageService: StorageService) {.}.}
You should see a mistake like this:
NullInjectorError: No provider for StorageService!
Obviously, we didn't add StorageService to Angular's dependency injection system. Angular cannot get the Provider of the StorageService dependency, so it cannot instantiate the class, let alone call methods in the class.
Next, we manually add a Provider based on the concept of missing and replenishing. Modify the storage.service.ts file as follows
/ / storage.service.tsexport class StorageService {get (key: string) {return JSON.parse (localStorage.getItem (key) | |'{}') | {};} set (key: string, value: any) {localStorage.setItem (key, JSON.stringify (value));} remove (key: string) {localStorage.removeItem (key);}} / add factory function, instantiate StorageServiceexport storageServiceProviderFactory (): StorageService {return new StorageService ();}
With the above code, we already have Provider. So the next question is, if you let Angular scan the dependency StorageService each time, let it execute the storageServiceProviderFactory method to create an instance.
This leads to the next knowledge point, InjectionToken.
In a service class, we often need to add multiple dependencies to ensure that the service is available. InjectionToken is the unique identification of each dependency, which enables Angular's dependency injection system to accurately find the Provider of each dependency.
Next, we manually add an InjectionToken
/ / storage.service.tsimport {InjectionToken} from'@ angular/core';export class StorageService {get (key: string) {return JSON.parse (localStorage.getItem (key) | |'{}') | {};} set (key: string, value: any) {localStorage.setItem (key, JSON.stringify (value));} remove (key: string) {localStorage.removeItem (key);}} export storageServiceProviderFactory (): StorageService {return new StorageService () } / / add InjectionTokenexport const STORAGE_SERVICE_TOKEN of StorageServiced = new InjectionToken ('AUTH_STORE_TOKEN')
Ok, we already have StorageService's Provider and InjectionToken.
Next, we need a configuration so that Angular's dependency injection system can recognize it, when scanning StorageService (Dependency), find the corresponding storageServiceProviderFactory (Provider) according to STORAGE_SERVICE_TOKEN (InjectionToken), and then create an instance of this dependency. As follows, we configure it in the @ NgModule () decorator in module:
/ / user.module.ts@NgModule ({imports: [...], declarations: [...], providers: [{provide: STORAGE_SERVICE_TOKEN, / / InjectionToken associated with the dependency, used to control the call to the factory function useFactory: storageServiceProviderFactory, / / when the dependency needs to be created and injected Call the factory function deps: [] / / if StorageService has other dependencies, add}]}) export class UserModule {}
At this point, we have completed the implementation of the dependency. Finally, you need to let Angular know where to inject it. Angular provides the @ Inject decorator to identify
/ user.component.ts@Component ({selector: 'app-user', templateUrl:'. / user.component.html', styleUrls: ['. / user.component.css']}) export class CourseCardComponent {constructor (@ Inject (STORAGE_SERVICE_TOKEN) private storageService: StorageService) {.}.}
At this point, we can call the methods in StorageService in user.component.ts.
UseClass class provider
Emm... Do you think the above writing is too complicated, and in actual development, most of our scenarios do not need to create Provider and InjectionToken manually. As follows:
/ / user.component.ts@Component ({selector: 'app-user', templateUrl:'. / user.component.html', styleUrls: ['. / user.component.css']}) export class CourseCardComponent {constructor (private storageService: StorageService) {.}.} / storage.service.ts@Injectable ({providedIn: 'root'}) export class StorageService {} / / user.module.ts@NgModule ({imports: [...] Declarations: [...], providers: [StorageService]}) export class UserModule {}
Next, let's analyze the above abbreviated mode.
At user.component.ts, we abandoned the @ Inject decorator and directly added the dependency private storageService: StorageService, thanks to Angular's design of InjectionToken.
An InjectionToken does not have to be an InjectionToken object, as long as it recognizes the corresponding unique dependencies in the runtime environment. So, here, you can use the class name, the name of the constructor in the runtime, as the InjectionToken of the dependency. Omit the step of creating an InjectionToken.
/ / user.module.ts@NgModule ({imports: [...], declarations: [...], providers: [{provide: StorageService, / / use the constructor name as InjectionToken useFactory: storageServiceProviderFactory, deps: []}]}) export class UserModule {}
Note: because Angular's dependency injection system does dependency injection based on InjectionToken identification in the runtime environment. So you can't use the interface name as the InjectionToken here, because it only exists at compile time of the Typescript language, not at run time. Class names, on the other hand, are represented as constructor names in the runtime environment and can be used.
Next, we can replace useFactory with useClass, which can also achieve the effect of creating an instance, as follows:
... providers: [{provide: StorageService, useClass: StorageService, deps: []}]...
When using useClass, Angular treats the following value as a constructor and instantiates it by executing the new instruction directly in the runtime environment, which eliminates the need for us to create the Provider manually
Of course, we can make it easier to write dependency injection designs based on Angular
... providers: [StorageService]...
Writing the class name directly into the providers array, Angular recognizes it as a constructor, then examines the inside of the function, creates a factory function to find dependencies in its constructor, and finally instantiates
Another feature of useClass is that Angular automatically looks up Provider as its runtime InjectionToken based on the type definition of the dependency in Typescript. So, we don't need to use the @ Inject decorator to tell Angular where the injection is.
You can abbreviate it as follows
. / / No manual injection is required: constructor (@ Inject (StorageService) private storageService: StorageService) constructor (private storageService: StorageService) {...}.
This is the most common way of writing in our usual development.
UseValue value provider
After completing the implementation of the local storage service, we received a new requirement, and the R & D boss wanted to provide a configuration file to store some of the default behaviors of StorageService.
Let's first create a configuration
Const storageConfig = {suffix: 'app_' / / add a prefix to store key expires: 24 * 3600 * 100 / / expiration time, millisecond stamp}
UseValue, on the other hand, can cover this scene. It can be a normal variable or in the form of an object.
The configuration method is as follows:
/ / config.tsexport interface STORAGE_CONFIG = {suffix: string; expires: number;} export const STORAGE_CONFIG_TOKEN = new InjectionToken ('storage-config'); export const storageConfig = {suffix:' app_' / / add a prefix to store key expires: 24 * 3600 * 100 / / expiration time, millisecond stamp} / / user.module.ts@NgModule ({. Providers: [StorageService, {provide: STORAGE_CONFIG_TOKEN, useValue: storageConfig}],...}) export class UserModule {}
In the user.component.ts component, use the configuration object directly:
/ user.component.ts@Component ({selector: 'app-user', templateUrl:'. / user.component.html', styleUrls: ['. / user.component.css']}) export class CourseCardComponent {constructor (private storageService: StorageService, @ Inject (STORAGE_CONFIG_TOKEN) private storageConfig: StorageConfig) {.} getKey (): void {const {suffix} = this.storageConfig; console.log (this.storageService.get (suffix + 'demo')) }} useExisting alias provider
If we need to create a new provider based on an existing provider, or if we need to rename an existing provider, we can use the useExisting attribute to handle it. For example: create an angular form control, which will exist in a form, and each form control stores a different value. We can create based on the existing form control provider
/ / new-input.component.tsimport {ControlValueAccessor, NG_VALUE_ACCESSOR} from'@ angular/forms';@Component ({selector: 'new-input', exportAs:' newInput', providers: [{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef (() = > NewInputComponent)), / / the NewInputComponent here has been declared, but not yet defined. Cannot be used directly, you can create an indirect reference using forwardRef, which Angular will parse later on in multi: true}]}) export class NewInputComponent implements ControlValueAccessor {.} ModuleInjector and ElementInjector level injector meaning
There are two injector hierarchies in Angular
ModuleInjector-use @ NgModule () or @ Injectable () to inject into the module
ElementInjector-- configured in the providers attribute of @ Directive () or @ Component ()
We use a practical example to explain the application scenarios of the two injectors, such as designing a card component that displays user information.
ModuleInjector module injector
We use user-card.component.ts to display components and UserService to access the user's information
/ / user-card.component.ts@Component ({selector: 'user-card.component.ts', templateUrl:'. / user-card.component.html', styleUrls: ['. / user-card.component.less']}) export class UserCardComponent {.} / / user.service.ts@Injectable ({providedIn: "root"}) export class UserService {.}
The above code is added to the root module through @ Injectable, where root is the alias of the root module. It is equivalent to the following code
/ / user.service.tsexport class UserService {.} / / app.module.ts@NgModule ({. Providers: [UserService], / / add} via providers) export class AppModule {}
Of course, if you think UserService will only be used under the UserModule module, you don't have to add it to the root module, just add it to your module.
/ / user.service.ts@Injectable ({providedIn: UserModule}) export class UserService {...}
If you are careful enough, you will find that in the above example, we can define provider either through @ Injectable ({provideIn: xxx}) in the current service file or @ NgModule ({providers: [xxx]}) in the module to which it belongs. So, what's the difference between them?
In addition to the different ways to use @ Injectable () and @ NgModule (), there is a big difference:
Using the providedIn attribute of @ Injectable () is better than the providers array of @ NgModule (), because when using the providedIn of @ Injectable (), the optimization tool can optimize Tree Shaking by shaking the tree, thereby removing unused services from your application to reduce the size of the bundle.
Let's use an example to explain the overview above. With the growth of our business, we have expanded UserService1 and UserService2 services, but for some reason, UserService2 has not been used.
If we introduce dependencies through the providers of @ NgModule (), we need to introduce the corresponding user1.service.ts and user2.service.ts files in the user.module.ts file, and then add UserService1 and UserService2 references to the providers array. Because the file where the UserService2 is located is referenced in the module file, the tree shaker in Angular mistakenly thinks that the UserService2 has been used. Unable to optimize the shaking tree. The code example is as follows:
/ / user.module.tsimport UserService1 from'. / user1.service.ts';import UserService2 from'. / user2.service.ts';@NgModule ({. Providers: [UserService1, UserService2], / / add} via providers) export class UserModule {}
So, if we use @ Injectable ({providedIn: UserModule}), we actually reference use.module.ts in the service class itself file and define a provider for it. There is no need to repeat the definition in UserModule, so there is no need to introduce the user2.service.ts file. Therefore, when UserService2 is not dependent, it can be optimized. The code example is as follows:
/ / user2.service.tsimport UserModule from'. / user.module.ts';@Injectable ({providedIn: UserModule}) export class UserService2 {...} ElementInjector component injector
After learning about ModuleInjector, let's move on to ElementInjector through the example we just gave.
At first, there was only one user in our system, and all we needed was a component and a UserService to access that user's information.
/ / user-card.component.ts@Component ({selector: 'user-card.component.ts', templateUrl:'. / user-card.component.html', styleUrls: ['. / user-card.component.less']}) export class UserCardComponent {.} / / user.service.ts@Injectable ({providedIn: "root"}) export class UserService {.}
Note: the above code adds UserService to the root module and it will only be instantiated once.
If there are multiple users in the system at this time, the UserService in each user card component needs to access the corresponding user's information. If you follow the above method, UserService will generate only one instance. Then it may appear. After Zhang San saved the data, Li Si went to get the data and got the result of Zhang San.
So, is there a way to instantiate multiple UserService to separate each user's data access operations?
The answer is yes. We need to use ElementInjector in the user.component.ts file to add the provider of UserService. As follows:
/ / user-card.component.ts@Component ({selector: 'user-card.component.ts', templateUrl:'. / user-card.component.html', styleUrls: ['. / user-card.component.less'], providers: [UserService]}) export class UserCardComponent {.}
With the above code, each user card component instantiates a UserService to access its own user information.
If you want to explain the above phenomenon, you need to talk about Angular's Components and Module Hierarchical Dependency Injection.
When using a dependency in a component, Angular first looks in the component's providers to determine whether the dependency has a matching provider. If so, instantiate directly. If not, look for the providers of the parent component, and if there is none, continue to find the parent of the parent until the root component (app.component.ts). If a matching provider is found in the root component, it will first determine whether it has an instance, and if so, it will be returned directly. If not, the instantiation operation is performed. If the root component is still not found, the search starts with the module where the original component is located, and if the module of the original component does not exist, continue to look for the parent module until the root module (app.module.ts). Finally, if it is still not found, an error No provider for xxx is reported.
The use of @ Optional (), @ Self (), @ SkipSelf (), @ Host () modifiers
In Angular applications, when dependencies look for provider, we can use some modifiers to fault-tolerant the search results or limit the scope of the search.
@ Optional ()
Decorate the service with @ Optional () to indicate that the service is optional. That is, if the provider matching the service is not found in the program, the program will not crash and report an error No provider for xxx, but return null.
Export class UserCardComponent {constructor (@ Optional private userService: UserService) {}} @ Self ()
Use @ Self () to have Angular view only the ElementInjector of the current component or instruction.
As follows, Angular will only search for a matching provider in the providers of the current UserCardComponent. If it does not match, an error will be reported directly. No provider for UserService .
/ / user-card.component.ts@Component ({selector: 'user-card.component.ts', templateUrl:'. / user-card.component.html', styleUrls: ['. / user-card.component.less'], providers: [UserService],}) export class UserCardComponent {constructor (@ Self () private userService?: UserService) {}} @ SkipSelf ()
@ SkipSelf () is the opposite of @ Self (). Using @ SkipSelf (), Angular starts searching for services in the parent ElementInjector instead of the current ElementInjector.
/ / Child component user-card.component.ts@Component ({selector: 'user-card.component.ts', templateUrl:'. / user-card.component.html', styleUrls: ['. / user-card.component.less'], providers: [UserService], / / not work}) export class UserCardComponent {constructor (@ SkipSelf () private userService?: UserService) {}} / / parent component parent-card.component.ts@Component ({selector: 'parent-card.component.ts') TemplateUrl:'. / parent-card.component.html', styleUrls: ['. / parent-card.component.less'], providers: [{provide: UserService, useClass: ParentUserService, / / work},],}) export class ParentCardComponent {constructor () {}} @ Host ()
@ Host () allows you to specify the current component as the last stop in the injector tree when searching for provider. This is similar to @ Self (), where even if the higher level of the tree has a service instance, Angular will not continue to look.
Multi multi-service provider
In some scenarios, we need an InjectionToken to initialize multiple provider. For example, when using the interceptor, we want to add a JWTInterceptor for token verification before default.interceptor.ts
Const NET_PROVIDES = [{provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: JWTInterceptor, multi: true}];...
Multi: when false, the value of provider is overridden; if set to true, multiple provider will be generated and associated with a unique InjectionToken HTTP_INTERCEPTORS. Finally, you can get the values of all provider through HTTP_INTERCEPTORS.
Thank you for reading this article carefully. I hope the article "how to use dependency injection in Angular" shared by the editor will be helpful to you. At the same time, I also hope you will support us and follow the industry information channel. More related knowledge is waiting for you to learn!
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.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.