Services & DI
export class Car {
public engine: Engine;
public wheels: Wheel[];
private readonly description: 'without DI';
constructor() {
this.engine = new Engine();
this.wheels = new Array(4).fill(null).map(() => new Wheel());
}
drive() {
return `${this.description}: car with ` +
`${this.engine.volume} engine and ` +
`${this.wheels[0].material} wheels`;
}
}
export class CarWithDI {
private readonly description: 'with DI';
constructor(public engine: Engine, public wheels: Wheels) {}
drive() {
return `${this.description}: car with ` +
`${this.engine.volume} engine and ` +
`${this.wheels[0].material} wheels`;
}
}
let car = new CarWithDI(getInstance(Engine), getInstance(Wheels))
getInstance - gets existing instance of creates a new one
Must be provided in order to use it
import { Injectable } from '@angular/core';
@Injectable()
export class FooService {
bar() {
// Some magic happens
}
}
import { Component } from '@angular/core';
import { FooService } from '../services/foo-service';
@Component({ ... })
export class FooComponent {
constructor(private fooService: FooService) {}
}
import { NgModule } from '@angular/core';
@NgModule({
declarations: [ ... ],
imports: [ ... ],
providers: [ FooService ],
bootstrap: [ FooComponent ]
})
export class FeatureModule { }
@NgModule({
providers: [ s1, s2 ]
})
Module A
@NgModule({
imports: [ ModuleA ]
providers: [ s3 ],
})
Module B
@NgModule({
imports: [ ModuleB ],
providers: [ s4 ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
AppModule
Root Injector
s1, s2, s3, s4
s1, s2, s3
Scope
s1, s2
providedIn
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class UserService {
}
import { Injectable } from '@angular/core';
import { UserModule } from './user.module';
@Injectable({
providedIn: UserModule,
})
export class UserService {
}
The example above shows the preferred way to provide a service in a module. This method is preferred because it enables tree-shaking of the service if nothing injects it.
import { NgModule } from '@angular/core';
import { FooService } from 'services/foo-service';
@NgModule({
declarations: [ ... ],
imports: [ ... ],
providers: [
FooService,
{ provide: 'SecondInstance', useClass: FooService }
],
bootstrap: [ FooComponent ]
})
export class FeatureModule { }
import { Component, Inject } from '@angular/core';
import { FooService } from '../services/foo-service';
@Component({ ... })
export class FooComponent {
constructor(private fooService: FooService,
@Inject('SecondInstance') private secondFooService: FooService) {}
}
import { NgModule } from '@angular/core';
import { FooService } from '../services/foo-service';
@NgModule({
declarations: [ ... ],
imports: [ ... ],
providers: [
FooService,
{ provide: 'SecondToken', useExisting: FooService }
],
bootstrap: [ FooComponent ]
})
export class FeatureModule { }
import { Component, Inject } from '@angular/core';
import { FooService } from '../services/foo-service';
@Component({ ... })
export class FooComponent {
constructor(@Inject('SecondToken') private secondFooService: FooService) {}
}
import { NgModule } from '@angular/core';
import { FooService } from 'services/foo-service';
import { AnotherFooService } from 'services/another-foo-service';
import { AnotherImplementationService} from 'services/another-service';
function myFactory(IS_PROD: boolean) {
return IS_PROD ? new FooService() : new AnotherImplementationService();
}
@NgModule({
declarations: [ ... ],
imports: [ ... ],
providers: [
{ provide: 'IS_PROD', useValue: true },
{ provide: FooService, useFactory: myFactory, deps: ['IS_PROD'] },
{ provide: FooService, useClass: AnotherFooService },
],
bootstrap: [ FooComponent ]
})
export class FeatureModule { }
import { NgModule, InjectionToken } from '@angular/core';
import { FooService } from 'services/foo-service';
import { AnotherImplementationService} from 'services/another-service';
const IS_PROD = new InjectionToken<boolean>('isProd');
function myFactory(IS_PROD: boolean) {
return IS_PROD ? new FooService() : new AnotherImplementationService();
}
@NgModule({
declarations: [ ... ],
imports: [ ... ],
providers: [
{ provide: IS_PROD, useValue: true },
{ provide: FooService, useFactory: myFactory, deps: ['IS_PROD']}
],
bootstrap: [ FooComponent ]
})
export class FeatureModule { }
import { Inject, Injectable, InjectionToken } from '@angular/core';
export const BROWSER_STORAGE = new InjectionToken<Storage>('Browser Storage', {
providedIn: 'root',
factory: () => localStorage
});
@Injectable({
providedIn: 'root'
})
export class BrowserStorageService {
constructor(@Inject(BROWSER_STORAGE) public storage: Storage) {}
get(key: string) {
return this.storage.getItem(key);
}
set(key: string, value: string) {
this.storage.setItem(key, value);
}
remove(key: string) {
this.storage.removeItem(key);
}
clear() {
this.storage.clear();
}
}
@Injectable()
class Door {
lock: Lock;
// Door attempts to inject Lock, despite it not being defined yet.
// forwardRef makes this possible.
constructor(@Inject(forwardRef(() => Lock)) lock: Lock) {
this.lock = lock;
}
}
// Only at this point Lock is defined.
class Lock {}
const injector =
Injector.create({
providers: [
{ provide: Lock, deps: [] },
{ provide: Door, deps: [Lock] }
],
});
expect(injector.get(Door) instanceof Door).toBe(true);
expect(injector.get(Door).lock instanceof Lock).toBe(true);
Allows to refer to references which are not yet defined.
import { Injectable, Optional } from '@angular/core';
import { FooService } from './data-service';
@Injectable()
export class MyDataService {
public data: any;
constructor(@Optional() private dataService: DataService) {
this.data = this.dataService ? this.dataService.getData() : 'local';
}
getData() {
return this.data;
}
}
class OtherService {}
class HostService {}
@Directive({selector: 'child-directive'})
class ChildDirective {
logs: string[] = [];
constructor(@Optional() @Host() os: OtherService, @Optional() @Host() hs: HostService) {
// os is null: true
this.logs.push(`os is null: ${os === null}`);
// hs is an instance of HostService: true
this.logs.push(`hs is an instance of HostService: ${hs instanceof HostService}`);
}
}
@Component({
selector: 'parent-cmp',
viewProviders: [HostService],
template: '<child-directive></child-directive>',
})
class ParentCmp {
}
@Component({
selector: 'app',
viewProviders: [OtherService],
template: '<parent-cmp></parent-cmp>',
})
class App {
}
Parameter decorator on a view-provider parameter that tells the DI framework to resolve the view by checking injectors of child elements, and stop when reaching the host element of the current component.
class Dependency {}
@Injectable()
class NeedsDependency {
constructor(@Self() public dependency: Dependency) {}
}
let inj = Injector.create({
providers: [
{provide: Dependency, deps: []},
{provide: NeedsDependency, deps: [[new Self(), Dependency]]}
]
});
const nd = inj.get(NeedsDependency);
expect(nd.dependency instanceof Dependency).toBe(true);
const child = Injector.create({
providers: [{provide: NeedsDependency, deps: [[new Self(), Dependency]]}],
parent: inj
});
expect(() => child.get(NeedsDependency)).toThrowError();
Parameter decorator to be used on constructor parameters, which tells the DI framework to start dependency resolution from the local injector.
class Dependency {}
@Injectable()
class NeedsDependency {
constructor(@SkipSelf() public dependency: Dependency) {}
}
const parent = Injector.create({providers: [{provide: Dependency, deps: []}]});
const child =
Injector.create({providers: [{provide: NeedsDependency, deps: [Dependency]}], parent});
expect(child.get(NeedsDependency).dependency instanceof Dependency).toBe(true);
const inj = Injector.create(
{providers: [{provide: NeedsDependency, deps: [[new Self(), Dependency]]}]});
expect(() => inj.get(NeedsDependency)).toThrowError();
Parameter decorator to be used on constructor parameters, which tells the DI framework to start dependency resolution from the parent injector. Resolution works upward through the injector hierarchy, so the local injector is not checked for a provider.