ng-book Notes: Dependency Injection

Posted by limao on Tue, 14 May 2019 15:50:08 +0200

Dependency injection is a system that enables one part of a program to access another part and control its behavior through configuration.

"Injection" can be understood as an alternative to the "new" operator, which no longer requires the use of the "new" operator provided by the programming language and relies on the generation of injection system management objects.

The greatest benefit of dependency injection is that components no longer need to know how to build dependencies. They only need to know how to interact with dependencies.

In Angular's dependency injection system, instead of directly importing and creating instances of classes, we use Angular to register dependencies, then describe how to inject dependencies, and finally inject dependencies.

Dependency Injection Components

To register a dependency, you need to bind it with a token. For example, if you register an API's URL, you can use the string API_URL as its tag; if you register a class, you can use the class itself as the tag.

Dependency injection system in Angular is divided into three parts:

  • Provider (also known as a binding) maps a tag to a series of dependencies that tell Angular how to create an object and give it a tag.
  • Injector holds a series of bindings and is responsible for parsing dependencies and injecting them when creating objects.
  • Dependency is the object injected.

Dependency Injection

Manual mode

It is not common to parse and create objects through the resolveAndCreate method of ReflectiveInjector.

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

@Injectable()
export class UserService {
  user: any;

  setUser(newUser) {
    this.user = newUser;
  }

  getUser(): any {
    return this.user;
  }
}
import {
  Component,
  ReflectiveInjector
} from '@angular/core';

import { UserService } from '../services/user.service';

@Component({
  selector: 'app-injector-demo',
  templateUrl: './user-demo.component.html',
  styleUrls: ['./user-demo.component.css']
})
export class UserDemoInjectorComponent {
  userName: string;
  userService: UserService;

  constructor() {
    // Create an _injector_ and ask for it to resolve and create a UserService
    const injector: any = ReflectiveInjector.resolveAndCreate([UserService]);

    // use the injector to **get the instance** of the UserService
    this.userService = injector.get(UserService);
  }

  signIn(): void {
    // when we sign in, set the user
    // this mimics filling out a login form
    this.userService.setUser({
      name: 'Nate Murray'
    });

    // now **read** the user name from the service
    this.userName = this.userService.getUser().name;
    console.log('User name is: ', this.userName);
  }
}

* Note the @Injectable() decorator on the UserService class, which shows that this class can be used as an injection object.

NgModule approach

Register the dependencies to be used (in providers) using NgModule, and specify which ones are in use with a decorator (usually a constructor).

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

// imported here
import { UserService } from '../services/user.service';

@NgModule({
  imports: [
    CommonModule
  ],
  providers: [
    UserService // <-- added right here
  ],
  declarations: []
})
export class UserDemoModule { }
import { Component, OnInit } from '@angular/core';

import { UserService } from '../services/user.service';

@Component({
  selector: 'app-user-demo',
  templateUrl: './user-demo.component.html',
  styleUrls: ['./user-demo.component.css']
})
export class UserDemoComponent {
  userName: string;
  // removed `userService` because of constructor shorthand below

  // Angular will inject the singleton instance of `UserService` here.
  // We set it as a property with `private`.
  constructor(private userService: UserService) {
    // empty because we don't have to do anything else!
  }

  // below is the same...
  signIn(): void {
    // when we sign in, set the user
    // this mimics filling out a login form
    this.userService.setUser({
      name: 'Nate Murray'
    });

    // now **read** the user name from the service
    this.userName = this.userService.getUser().name;
    console.log('User name is: ', this.userName);
  }
}

Providers

Class mark

Providers: [UserService] is short for the following:

providers: [
  { provide: UserService, useClass: UserService }
]

provide is the tag, and useClass is the object on which it depends. The two are mapping relations.

Value mark

providers: [
  { provide: 'API_URL', useValue: 'http://my.api.com/v1' }
]

You need to add @Inject:

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

export class AnalyticsDemoComponent {
  constructor(@Inject('API_URL') apiUrl: string) {
    // works! do something w/ apiUrl
  }
}

Factory mode

When binding dependencies, you can also implement more complex binding logic in factory mode, and in this way you can pass in the necessary parameters to create the required objects.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
  Metric,
  AnalyticsImplementation
} from './analytics-demo.interface';
import { AnalyticsService } from '../services/analytics.service';

// added this ->
import {
  HttpModule,
  Http
} from '@angular/http';

@NgModule({
  imports: [
    CommonModule,
    HttpModule, // <-- added
  ],
  providers: [
    // add our API_URL provider
    { provide: 'API_URL', useValue: 'http://devserver.com' },
    {
      provide: AnalyticsService,

      // add our `deps` to specify the factory depencies
      deps: [ Http, 'API_URL' ],

      // notice we've added arguments here
      // the order matches the deps order
      useFactory(http: Http, apiUrl: string) {

        // create an implementation that will log the event
        const loggingImplementation: AnalyticsImplementation = {
          recordEvent: (metric: Metric): void => {
            console.log('The metric is:', metric);
            console.log('Sending to: ', apiUrl);
            // ... You'd send the metric using http here ...
          }
        };

        // create our new `AnalyticsService` with the implementation
        return new AnalyticsService(loggingImplementation);
      }
    },
  ],
  declarations: [ ]
})
export class AnalyticsDemoModule { }

Topics: Javascript angular Programming