Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 35

Microsoft and Apple Training

Dependency Injection and Providers


Agenda
 Terminology
 Dependency Injection Basics
 Hierarchical Injection
 Providers
 Dependency Injection Tokens
Dependency Injection
 Dependency injection is the act of decoupling a piece of code of its
dependencies.
– It is a design principle
– Improves maintainability
– Improves testability
– Improves overall design

 You can overdo it sometimes, make sure you don’t overdo it 


Dependency Injection
 Dependent object
– Depends on another object
 Dependencies are components the dependent object depends upon
 The injector injects those dependencies into the dependent object
 Eg. Account object depends on a Logger object
Dependency Injection
 Decouple the creation of the dependency from the dependent object
– By passing in the dependency as a parameter

class Account {
//tight coupling
logger: Logger = new Logger();
}

class Account {
//loose coupling
constructor(public logger: Logger) {}
}
Dependency Injection Framework
 The Injector
– Gets a list of registered classes
– Manages the responsibility of dependency creation
– Supplies the dependency to the dependent object

let logger = injector.get('Logger');

 This makes sure that Logger is created and injected into the Account
constructor
Dependency Injection in Angular
 Angular's Dependency Injection mechanism
– A Component simply has to ask for a service
– An Injector is responsible for delivering the service and managing its life cycle
– The Injector relies on Providers
– Providers can create or return a service
Three Steps
 The service

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


Create the
@Injectable() Service
export class AuthenticationService { ... }

 The client

import {AuthenticationService} from './authentication.service';

@Component({
providers: [AuthenticationService] Register Service
})
export class Login {
constructor(private _authenticationService: AuthenticationService){}
}

Use Service
Service Registration (Angular 6+)
 Registration can be handled inside the @Injectable decorator

@Injectable({
providedIn: 'root'
})

 Provider is no longer needed


 providedIn:
– ‘root’: singleton injection for your application
– ‘{modulename}’: injected into a specific module
Creating a Service
 Create a new class and export it
– Like with creating a component
– No special requirements

export class GameService {


getGames() { return GAMES; }
}

 Best practice: add @Injectable() before service definition


– Note the parentheses "()" with Injectable!

@Injectable()
export class GameService {
getGames() { return GAMES; }
}
Register the Service
 Register the service with the injector
– In a module  application-wide
– In a component  locally

// In a module // In a component
@NgModule({ @Component({
providers: [GameService] selector: 'my-games',
}) template: `
export class AppModule { } <h2>Games</h2>
<game-list></game-list>
`,
providers: [GameService]
})
Using the Service
 Create a constructor in the component in-need
– Make sure that it has a correct type annotation!

@Component({
...
})
export class GameListComponent {
games : Game[];
constructor( private _gameService: GameService) {
this.games = _gameService.getGames();
}
}
Optional Service
 By default every service needs to be registered

EXCEPTION: No provider for LoggerService! (GameListComponent ->


GameService -> LoggerService)

 Optional dependencies can be used


– Need the Optional() parameter decorator in dependent object
– Passes a null reference when there's no provider

constructor(@Optional() private _logger: LoggerService) { }


Dependency Injection
 Where can I use DI?
– Services
– Directives
– Pipes

 DI is pervasive throughout Angular

 Angular requires a decorator because it needs constructor metadata in


order to inject the Service
– Metadata is only generated for classes with a decorator
– That's why a service with dependencies requires @Injectable()
Hierarchical Injection
 Each service is created as a singleton
– In the scope of the injector

 A hierarchy of components leads to a hierarchy of injectors (kinda)


– For performance: Injectors get shared if possible

C DI

C C DI DI

C C C DI DI DI
Hierarchical Injection
 When registered in a component, the singleton can be injected into the
component and all its children

C1 Register here

C2 C3

Use here
C4 C4 C4

 C3 and all instances of C4 now use the same singleton


Example
 There is a different injector with every providers array
// GameListComponent
@Component({
providers: [ RestoreService, LoggerService ]
})
// GameDetailComponent
@Component({
providers: [ RestoreService ]
})

 The GameList component shares a LoggerService with all GameDetail


components
 The GameList component and each GameDetail component has its own
RestoreService
Application-Wide Injection
 Services can be registered in an NgModule
– Singleton available to the entire application
– Can still be overridden locally in a Component

@NgModule({
imports: [BrowserModule],
declarations: [AppComponent, GameListComponent, GameDetailComponent],
bootstrap: [AppComponent],
providers: [LoggerService]
})
export class AppModule { }
Where to Register?
 Stateless Services can easily be shared
– One instance for all!
– Remember that you can
register when bootstrapping

 Stateful could get messy when shared


– Components can override each other's state
Providers
 Providers inform the Injector how to create a runtime version of the
dependency

 The injector can be configured with alternative providers


– The Service class itself
– A Substitute class
– A Factory function
– ...
Provide Function
 This
providers: [LoggerService]

 Is short-hand expression for

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


Provide Function
 The provide function needs two arguments
– A token serving as the key for registering the provider
– Provider definition object

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

Here the token is


the class itself

 The provider definition object


– Is like a recipe for creating the dependency
– Can have alternatives
Alternative Class Providers
 Asking a different class to provide the service
providers: [{provide: LoggerService,
useClass: BetterLoggerService }]

 Somebody asking for a LoggerService will now get an instance of


BetterLoggerService

 BetterLoggerService can have dependencies of its own


– Make sure to register those dependencies
Value Providers
 Sometimes it's easier to provide object
– Instead of asking injector to create it from a class

// An object in the shape of the logger service


let silentLogger = {
logs: ['Silent logger. Provided via "useValue"'],
log: () => { }
}

// providers in @Component metadata


providers: [{provide: LoggerService, useValue: silentLogger }]
Aliased Class Providers
 Suppose following scenario
– Old Component depends on OldLogger
– OldLogger has same interface as NewLogger
– Old Component can't be updated

 Old component needs to use the instance of NewLogger, when talking to


OldLogger (alias)
Aliased Class Providers
 Let's try with useClass

providers: [NewLogger,
// Not aliased! Creates two instances of
`NewLogger`
{ provide: OldLogger, useClass: NewLogger
})],
 Hmm, doesn't seem to work as requested... Solution?

providers: [NewLogger,
// Alias OldLogger w/ reference to
NewLogger
{ provide: OldLogger, useExisting:
NewLogger })],
Factory Provider
 What if the right providers needs to be decided at runtime?
– And even needs to switch at runtime
 Example
– GameService hides the alpha games from normal users, but people can switch roles in
one browser session

 Solution
– Use a factory provider
Factory Provider
TS
let gameServiceFactory = (logger: LoggerService, userService: UserService) => {
return new SecureGameService(logger, userService.user.isAuthorized);
};

@Component({
providers: [{
provide: GameService,
useFactory: gameServiceFactory,
deps: [LoggerService, UserService]
//deps are here as provider tokens to be able to inject
//the correct services
}]
})
Providers
 For the sake of encapsulation, you can separate the Provider declaration from
the component
export let GameServiceProvider: FactoryProvider = {
provide: GameService,
useFactory: gameServiceFactory,
deps: [LoggerService, UserService]
};

 And import in the component

@Component({
providers: [GameServiceProvider, UserService, ...]
})
Providing Multiple Dependencies
 Using multi extends a value rather than overriding it
– Receive array instead of one item

var injector = ReflectiveInjector.resolveAndCreate([


{provide: TOKEN, useValue: 'dep1', multi: true },
{provide: TOKEN, useValue: 'dep2', multi: true }
]);

let deps = injector.get(TOKEN);


// deps == ['dep1', 'dep2']

 Example
– Return a collection of validator functions
Dependency Injection Tokens
 Registering a provider needs a token
– Injector maintains internal token-provider map with token as key

 Most of the time Class Dependency Tokens are used

 Alternatives
– String tokens
– Injectiontokens
Interfaces as a Token?
 Injecting configuration objects with a value provider
– A token is needed, but what to use?

export interface Config {


apiEndpoint: string,
title: string
}

export const CONFIG: Config = {


apiEndpoint: 'api.heroes.com',
title: 'Dependency Injection'
};

 Let's use the interface!

providers: [Config, ...


// Doesn't work because Interfaces don't exist in JavaScript!
String Tokens
 Instead of an interface token
– Use a string to represent the provider

// Use string as provider token


Providers: [{provide: 'app.config', useValue: CONFIG }]

 Using this dependency?


– Extra metadata is needed in the form of @Inject(<token>)

// @Inject(token) to inject the dependency


constructor(@Inject('app.config') private _config: Config){ }
Injection Token
 Some people really hate strings
– And they're right... Just imagine Pavarotti in one...

 Instead use an object to contain these tokens

//1. Create token


export let APP_CONFIG = new InjectionToken('app.config');
//2. Register token
providers: [{provide: APP_CONFIG, useValue: CONFIG }]
//3. Use token
constructor(@Inject(APP_CONFIG) private _config: Config){ }

 Angular uses InjectionToken objects to register all non-class dependencies


Summary
 Terminology
 Dependency Injection Basics
– Create, Register and Inject Service
 Hierarchical Injection
– NgModel for application-wide
– Component for itself and children
 Providers
– Class, provide, new Provider
– useClass, useExisting, useValue, useFactory, multi
 Dependency Injection Tokens
– Class, String, InjectionToken

You might also like