Master the dependency injection of angular2

Time:2021-1-16

For a better reading experience, seeoriginal text

Before you read this article, you need to know what it isDependency injectionThere are a lot of explanations about this on the Internet. You can do it yourselfGoogle.

Master the dependency injection of angular2

Our article is based onQuickStartBased on the project, explain how to use it in angular2 from the beginningDependency injectionIf you follow the examples in this article, you will be able to use them in angular2Dependency injectionWell, no more nonsense. Let’s start our trip today!

We first replace the inline template in the project with a template file and use thetemplateUrlreplacetemplate:

@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>',
    templateUrl: 'app/templates/app.html'
})

Next, we add some display data to our page. First, we create a new fileapp/classes/User.tsTo create ourUserexample:

export class User{
    constructor(
        private name:string,
        private age:number,
        private email:string
    ){}
}

Then we introduce this class into the component and create our display data

import {User} from "./classes/User";
// ...
export class AppComponent {
    users: User[] = [
        new User('dreamapple', 22, '[email protected]'),
        new User('dreamapplehappy', 18, '[email protected]')
    ]
}

Don’t forget to add some HTML code to the template to display the data

<h1>Dependency injection</h1>
<ul>
    <li *ngFor="let user of users">
        User's name:{{ user.name }}; user's age:{{ user.age }}; user's email address:{{ user.email }}
    </li>
</ul>

Then we will see that the data we want is displayed in the page:
Master the dependency injection of angular2

Dependency injection of angular2

In general, in web applications, the data we want to show is dynamically obtained from the background server, so let’s simulate this process; we will use it hereDependency injection of servicesNow, let’s create the file firstuser-data.mock.tsThe path isapp/mock/user-data.mock.ts;

import {User} from "../classes/User";

export var Users:User[] = [
    new User('dreamapple1', 21, '[email protected]'),
    new User('dreamapple2', 22, '[email protected]'),
    new User('dreamapple3', 23, '[email protected]'),
    new User('dreamapple4', 24, '[email protected]'),
    new User('dreamapple5', 25, '[email protected]'),
    new User('dreamapple6', 26, '[email protected]')
]

We used itUserClass to create our data, and then export the created data

Next, we need to create a database to get user dataservice, we create a new fileuser.service.ts, pathapp/services/user.service.ts:

import {Injectable} from '@angular/core';
import {Users} from "../mock/user-data.mock";


@Injectable()
export class UserService {
    getUsers() {
        return Users;
    }
}

You will have some questions about the above code. Let’s explain to you: first, we used the simulation data we just createdUsersThen we start from@angular/coreExported inInjectableLike we derive from itComponentThe same;@Injectable()Indicates that a class can be instantiated by an injector; generally speaking, when trying toinstantiation One is not identified as@Injectable()Class, the injector will report an errorIt doesn’t matter if we don’t understand the above explanation now. Let’s learn how to use it first;Just like you don’t understand the principle of computer, you can play computer very well

What we’re going to do next isAppComponentComponentUserServiceNow, what we need to pay attention to is: we need to@ComponentIs used in the metadata ofprovidersDeclare the dependencies we need and introduce themUserClass to help us declare the type of data

import {UserService} from "./services/user.service";
import {User} from "./classes/User";
//...
@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>',
    templateUrl: 'app/templates/app.html',
    providers: [
        UserService
    ]
})
export class AppComponent {
    users: User[];

    constructor(private userService: UserService) {
        this.users = userService.getUsers();
    }
}

Some explanations for the above code:We useproviders: [UserService]To declare the dependency of our component. If we don’t have this option, our program will report an error. Then we add a property to this classusersAnd declare that the type of this property is aUserClass instance; finally, we declare a private property in the constructoruserServiceIt isUserServiceAn instance of a service class, which we can use to obtainusersData

Run it, and then we will see the following page, indicating that everything is successful
Master the dependency injection of angular2

If at this time you try touser.service.tsOf@InjectableNote out, the entire program is no error, but we recommend adding for each service class@Injectable(), including those that do not need it because they are not dependent(1) Facing the future, there is no need to remember to add a dependency later@Injectable()(2) consistency, all services follow the same rules, and we don’t need to consider why we lack a decorator

It’s because ourUserServiceThere is no dependency on the service now, if we give it to youUserServiceIf you add a dependency, if you@Injectable()Note out, the program will report an error; let’s have a try

Many web applications will need a log service, so let’s create a new oneLoggerThe path is as follows:app/services/logger.service.ts:

import {Injectable} from '@angular/core';

@Injectable()
export class Logger{
    logs: string[] = [];

    log(msg) {
        this.logs.push(msg);
        console.warn('From logger class: ' + msg);
    }
}

And then we’re inUserServiceUse this service in the service:

import {Injectable} from '@angular/core';
import {Users} from "../mock/user-data.mock";
import {Logger} from "./logger.service";


@Injectable()
export class UserService {
    constructor(private logger: Logger) {

    }

    getUsers() {
        this.logger.log('get users');
        return Users;
    }
}

As you can see, we putLoggertreat asUserServiceA dependency of service, because we areUserServiceClass is declared in its constructorloggerProperty, which isLoggerClass; and don’t forget toAppComponentAdd this toLoggerDependence:

@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>',
    templateUrl: 'app/templates/app.html',
    providers: [
        Logger, // add a logger dependency
        UserService
    ]
})

Then we can see in the page:
Master the dependency injection of angular2

If at this time, we comment it outUserServiceOf@Injectable()The program will report an error:
Master the dependency injection of angular2

So, as mentioned above, let’s add a service class to each service class@Injectable()To avoid unnecessary trouble

Now let’s talk aboutAngular2Service inProvidersIf you’re right about the so-calledproviderIf we don’t understand it, it doesn’t matter; it can be understood that whenever we use a service,Angular2All will passproviderTo create or get the instance of the service we want

The way we mentioned above to provide services is actually the simplest way. Next, we will discuss different ways to registerService providerFirst of all, the first method is the one we mentioned above. In fact, it is a simplified way. The detailed way should be as follows:

[{ provide: Logger, useClass: Logger }]

amongprovideAs key valuekeyUse, which is used to locate the dependency and register the provider. This is actually the name of the service used in the following program;useClassIt indicates which service class we use to create instances. Of course, we can use different service classes as long as these service classes meet our corresponding needs

We can try to replace itLoggerClass isBetterLoggerClass, we first create theBetterLoggerClass:

import {Injectable} from '@angular/core';

@Injectable()
export class BetterLogger{
    logs: string[] = [];

    log(msg) {
        this.logs.push(msg);
        console.warn('From better logger class: ' + msg);
    }
}

And then in theAppComponentUse this inBetterLoggerClass:

@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>',
    templateUrl: 'app/templates/app.html',
    providers: [
        //Logger,
        [{provide: Logger, useClass: BetterLogger}],
        UserService
    ]
})

We can see that the output of the console is:
Master the dependency injection of angular2

As you can see, we useBetterLoggerClass replacedLoggerIf our provider needs some dependencies, what should we do? Don’t be afraid, we can use the following form:

[ LoggerHelper,{ provide: Logger, useClass: Logger }]

Next, let’s create oneLoggerHelperClass, whose path isapp/services/logger-helper.service.ts:

import {Injectable} from '@angular/core';

@Injectable()
export class LoggerHelper {
    constructor() {
        console.warn('Just a logger helper!');
    }
}

We are hereAppComponentRegistered provider:

@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>',
    templateUrl: 'app/templates/app.html',
    providers: [
        //Logger,
        //[{provide: Logger, useClass: BetterLogger}],
        [loggerhelper, {provide: logger, useclass: betterlogger}], // with a dependent registry
        UserService
    ]
})

And then we’re inBetterLoggerUse this dependency in the service:

import {Injectable} from '@angular/core';
import {LoggerHelper} from "./logger-helper.service";

@Injectable()
export class BetterLogger{
    logs: string[] = [];

    constructor(private loggerHelper: LoggerHelper) {
    }

    log(msg) {
        this.logs.push(msg);
        console.warn('From better logger class: ' + msg);
    }
}

Then we can see that the output of our console is as follows:
Master the dependency injection of angular2

It shows that we have used dependency correctly; we can also use alias to use the same provider, which can solve some problems; especially when we want an old component to use a new service, it’s just like we want an old component to use a new serviceAppComponentuseBetterLoggerClass to print logs instead of using theLoggerClass, if we can’t change itAppComponentClass, and we also want other components to be newBetterLoggerClass, then we can register the provider as follows

[{ provide: BetterLogger, useClass: BetterLogger}],
[{ provide: Logger, useExisting: BetterLogger}]

See, we useuseExistinginstead ofuseClass; because of the use ofuseClassOr lead to two problems in our applicationBetterLoggerClass. We can experiment with itAppComponentMedium:

@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>',
    templateUrl: 'app/templates/app.html',
    providers: [
        //Logger,
        //[{provide: Logger, useClass: BetterLogger}],
        [LoggerHelper, {provide: BetterLogger, useClass: BetterLogger}],
        [LoggerHelper, {provide: Logger, useExisting: BetterLogger}],
        UserService
    ]
})

And then we’re inBetterLoggerAdd a print statement to the constructor of the class

console.warn('BetterLogger Constructor');

We’re going to have to work on it againUserServiceClass to declare a property in its constructorbetterLoggerIt isBetterLoggerClass:

constructor(private logger: Logger, private betterLogger: BetterLogger) {

    }

Finally, we can see that the print result of the console is as follows:

Just a logger helper!
BetterLogger Constructor
From better logger class: get users 

But once we use ituseClassinstead ofuseExistingThen the print result of the console becomes:

Just a logger helper!
BetterLogger Constructor
BetterLogger Constructor
From better logger class: get users

So we created twoBetterLoggerSo when our multiple services want to use the same provider, we should useuseExistingInstead ofuseClass.

[August 20, 2016: continuation]

Value provider: we can use a simpler way to register a provider, that is to usevalueThe so-called value can be any valid valueTypeScriptLet’s start with aobjectLet’s start by creating a new filelogger.value.tsThe path isapp/values/logger.value.tsLet’s write a basicloggerValueThe objects are as follows:

let loggerValue = {
    logs: ['Hello', 'World'],
    log: (msg) => {
        console.warn('From values: ' + msg);
    },
    hello: () => {
        console.log('Just say hello!');
    }
};

export {loggerValue};

How do we register this provider? We useuseValueOption to register our provider as follows:

// ...
providers: [
        //Logger,
        //[{provide: Logger, useClass: BetterLogger}],
        [LoggerHelper, {provide: BetterLogger, useClass: BetterLogger}],
        //[LoggerHelper, {provide: Logger, useClass: BetterLogger}],
        {provide: Logger, useValue: loggerValue},
        //{provide: logger, usevalue: loggervalue1}, // we use the usevalue option
        UserService
    ]
// ...

And remember tologgerValueImport in; then let’s modify it a little bituser.service.tsCode:

// ...
getUsers() {
        this.logger.log('get users');
        //noinspection TypeScriptUnresolvedFunction
        this.logger.hello();
        return Users;
    }
// ...

Then we will see that the output of the console is:

// ...
From values: get users
Just say hello!
// ...

It indicates that we have successfully registered the provider in this way. Of course, we can also use a string, and these readers can try it or watch itthisExamples

Factory provider: sometimes we need to create this dependency value dynamically, because we can’t determine the information it needs until the last moment. How can we register a factory provider? Don’t worry, let’s do it step by step: first, let’s create a file to verify permissions,authorize.tsThe path is:app/services/authorize.tsFor the time being, let’s put some simple logic in it to determine whether the current user has obtained itUsersPermission for:

import {Injectable} from '@angular/core';

@Injectable()
export class Authorize {
    isAuthorized: boolean;
    constructor(){
        this.isAuthorized = Math.random() > 0.5 ? true: false;
    }
    getIsAuthorized() {
        return this.isAuthorized;
    }
}

Well, I admit that it’s a bit arbitrary to write like this. Let’s do it for the time being. Our purpose is to tell you how to use the factory provider and simplify the permission verification for the time being. From the above code, we can roughly understand that this service is to obtain the permission of the current user. Then we will configure ourUserService2ProviderFor the convenience of us, we are going toapp.component.tsWrite our configuration in:

// ...
let UserService2Provider = (logger: Logger, authorize: Authorize) => {
    return new UserService2(logger, authorize.getIsAuthorized());
};
// ...

As you can see, ourUserService2ProviderIn fact, it is a function that returns an instance of a class. We pass two parameters to this function, which areLoggerandAuthorizeClass, and then we create our new service instance based on these two instances. Oh, I forgot to tell you that we need to create a new service instanceUserService2Class, path:app/services/user-service2.ts:

import {Injectable} from '@angular/core';
import {Users} from "../mock/user-data.mock";
import {Logger} from "./logger.service";


@Injectable()
export class UserService2 {
    isAuthorized: boolean;
    constructor(private logger: Logger, isAuthorized: boolean) {
        this.isAuthorized = isAuthorized;
    }

    getUsers() {
        if(this.isAuthorized){
            this.logger.log('get users');
            return Users;
        }
        else {
            this.logger.log('not isAuthorized!');
            return [];
        }
    }
}

You can see the service class andUserServiceAlmost, there is an additional condition validation, if the current user has accessUsersAnd we’ll return theseUsersIf not, we will return an empty arrayapp.component.tsOfprovidersUsed inUserService2Provider:

// ...
providers: [
        //Logger,
        //[{provide: Logger, useClass: BetterLogger}],
        [LoggerHelper, {provide: BetterLogger, useClass: BetterLogger}],
        //[LoggerHelper, {provide: Logger, useClass: BetterLogger}],
        {provide: Logger, useValue: loggerValue},
        //{provide: Logger, useValue: loggerValue1},
        UserService,
        Authorize, // indispensable
        {
            provide: UserService2,
            useFactory: UserService2Provider,
            deps: [Logger, Authorize]
        }
    ]
// ...

Also remember to addAuthorizeWe depend on; we depend onapp.component.tsUse new services in:

// ...
export class AppComponent {
    users: User[];

    constructor(private userService: UserService, private userService2: UserService2) {
        this.users = userService.getUsers();

        console.log(this.userService2.getUsers());
    }
}

Refresh the browser and you will see that sometimes it will output:

From values: not isAuthorized!
[]

Sometimes it outputs:

From values: get users
[User, User, User, User, User, User]

So, we need to register in this wayFactory providerIt’s also a success

Maybe you will have some questions, we use it in the class constructorprivate userService2: UserService2How can I get an example of this service,Angular2How do we know what we wantUserService2Class, how does it get instances of this classAngular2In this paper, we use a syringe to implement dependency injection. In fact, the above form is just a abbreviation

import {Component, Injector} from '@angular/core';

Let’s start withNg2Get fromInjectorThen use this syringe to instantiate the services we need

// ...
export class AppComponent {
    users: User[];
    private userService2: UserService2;
    
    //constructor(private userService: UserService, private userService2: UserService2) {
    //    this.users = userService.getUsers();
    //
    //    console.log(this.userService2.getUsers());
    //}

    constructor(private userService: UserService, private injector: Injector) {
        this.users = userService.getUsers();
        this.userService2 = this.injector.get(UserService2);
        console.log(this.userService2.getUsers());
    }
}

So it can be seen that all of these cumbersome work let us goinjectorTo do it, we just need to provide some simple instructions, smartNg2You know how to do dependency injection

Nonclass dependency

All of our explanations above are about the dependency injection of a class as a dependency. But if what we want is not a class, but some values or objects, what should we do? Let’s write such a file firstapp-config.tsThe path is:app/config/app-config.ts:

export interface AppConfig {
    title: string,
    apiEndPoint: string
}

export const AppConf: AppConfig = {
    title: 'Dreamapple',
    apiEndPoint: 'https://hacking-with-angular.github.io/'
};

Use as abovevalueWe should be able to do this:

// ...
{provide: AppConfig, useValue: AppConf}
// ...
constructor(private userService: UserService, private userService2: UserService2, private appConf: AppConfig) {
        this.users = userService.getUsers();

        console.log(this.userService2.getUsers());

        console.log(this.appConf);
    }
// ...    

However, we did not achieve the desired effect

Error: ReferenceError: AppConfig is not defined(…)

Because of the interfaceinterfaceCannot be treated as a classclassSo we need a new way to useOpaqueToken(opaque token)

//First, import opaquetoken and inject
import {Component, Injector, OpaqueToken, Inject} from '@angular/core';
//Introduce appconf and use opaquetoken
import {AppConf} from "./config/app-config";
let APP_CONFIG = new OpaqueToken('./config/app-config');
//Configure in providers
{provide: APP_CONFIG, useValue: AppConf}
//Use in classes
constructor(private userService: UserService, private userService2: UserService2, @Inject(APP_CONFIG) appConf: AppConfig) {
        this.users = userService.getUsers();

        console.log(this.userService2.getUsers());

        console.log(appConf);
    }

For some explanation of the above code, let’s useOpaqueTokeanObject registration depends on the provider, and then we@InjectWith the help of, we inject this configuration object into the constructor that needs it, and finally we can use the original objectAppConfigInterface has no role in dependency injection, but it provides strong type information for configuration objects in this class

Finally, let’s talk about itOptional dependencies(optional), some services may not have to rely on them; we can use them@Optional()To mark these parameters; remember, when using@Optional()If we want to markloggerThis service is optional; so if we don’t register one in the component or parent componentloggerIf so, the injector will set theloggerThe value of is emptynullOur code has to prepare for a null valueuser.service.tsMake some changes in the

//Import optonal
import {Injectable, Optional} from '@angular/core';
import {Users} from "../mock/user-data.mock";
import {Logger} from "./logger.service";
import {BetterLogger} from "./better-logger.service";


@Injectable()
export class UserService {
    constructor(private logger: Logger,
                //Use @ optional tag
                @Optional()private betterLogger: BetterLogger) {
    }

    getUsers() {
        this.logger.log('get users');
        //noinspection TypeScriptUnresolvedFunction
        this.logger.hello();

        //Processing when there is a betterlogger
        if(this.betterLogger) {
            this.betterLogger.log('optional');
        }

        //console.log(this.logger);
        return Users;
    }
}

At this point, the whole article is over; if you insist on reading here, it shows that you are also a very patient person; if you have any questions, you can answer themherePut forward