Oh my gosh this talk on Angular 2's DI by @PascalPrecht blows my mind! #techtalks_NS
"Dependency Injection
...as design pattern
...as framework."
a.k.a. the 5-cent concept
class Car {
constructor() {
this.engine = new Engine();
this.tires = Tires.getInstance();
this.doors = app.get('doors');
this.milesDriven = 0;
}
drive(miles) {
this.milesDriven = this.milesDriven + miles;
}
}
class Car { constructor() { this.engine = new Engine(); this.tires = Tires.getInstance(); this.doors = app.get('doors'); this.milesDriven = 0; } drive(miles) { this.milesDriven = this.milesDriven + miles; } }
class Car { constructor() { this.engine = new Engine(); this.tires = Tires.getInstance(); this.doors = app.get('doors'); this.milesDriven = 0; } drive(miles) { this.milesDriven = this.milesDriven + miles; } }
class Car { constructor() { this.engine = new Engine(); this.tires = Tires.getInstance(); this.doors = app.get('doors'); this.milesDriven = 0; } drive(miles) { this.milesDriven = this.milesDriven + miles; } }
class Car { constructor() { this.engine = new Engine(); this.tires = Tires.getInstance(); this.doors = app.get('doors'); this.milesDriven = 0; } drive(miles) { this.milesDriven = this.milesDriven + miles; } }
class Car {
constructor() {
this.engine = new Engine();
this.tires = Tires.getInstance();
this.doors = app.get('doors');
this.milesDriven = 0;
}
drive(miles) {
this.milesDriven = this.milesDriven + miles;
}
}
class Car {
constructor(engine, tires, doors) {
this.engine = engine;
this.tires = tires;
this.doors = doors;
this.milesDriven = 0;
}
drive(miles) {
this.milesDriven = this.milesDriven + miles;
}
}
class Car { constructor(engine, tires, doors) { this.engine = engine; this.tires = tires; this.doors = doors; this.milesDriven = 0; } drive(miles) { this.milesDriven = this.milesDriven + miles; } }
var car = new Car(
new Engine(),
new Tires(),
new Doors()
);
var mockCar = new Car(
new MockEngine(),
new MockTires(),
new MockDoors()
);
function main() {
var engine = new Engine();
var tires = new Tires();
var doors = new Doors();
var car = new Car(engine, tires, doors);
car.drive(10);
}
function main() { var engine = new Engine(); var tires = new Tires(); var doors = new Doors(); var car = new Car(engine, tires, doors); car.drive(10); }
function main() {
var yetAnother = new YetAnotherDependency();
var engine = new Engine(yetAnother);
var tires = new Tires(yetAnother);
var doors = new Doors(yetAnother);
var car = new Car(engine, tires, doors, yetAnother);
car.drive(10);
}
function main() { var yetAnother = new YetAnotherDependency(); var engine = new Engine(yetAnother); var tires = new Tires(yetAnother); var doors = new Doors(yetAnother); var car = new Car(engine, tires, doors, yetAnother); car.drive(10); }
function main() {
var yetAnother = new YetAnotherDependency();
var engine = new Engine(yetAnother);
var tires = new Tires(yetAnother);
var doors = new Doors(yetAnother);
var car = new Car(engine, tires, doors, yetAnother);
car.drive(10);
}
function main() {
var injector = new Injector();
var car = injector.get(Car);
car.drive(10);
}
var app = angular.module('myApp', []);
function Car(Engine, Tires, Doors) {
...
}
app.service('Car', Car);
var app = angular.module('myApp', []); function Car(Engine, Tires, Doors) { ... } app.service('Car', Car);
var app = angular.module('myApp', []); function Car(Engine, Tires, Doors) { ... } app.service('Car', Car);
var app = angular.module('myApp', []); function Car(Engine, Tires, Doors) { ... } app.service('Car', Car);
var app = angular.module('myApp', []);
function Car(Engine, Tires, Doors) {
...
}
app.service('Car', Car);
var app = angular.module('myApp', []); function Car(Engine, Tires, Doors) { ... } app.service('Car', Car);
"function Car(Engine, Tires, Doors) { ... }"
A RegEx extracts the parameters names.
var app = angular.module('myApp', []);
function Car(Engine, Tires, Doors) {
...
}
Car.$inject = ['Engine', 'Tires', 'Doors'];
app.service('Car', Car);
var app = angular.module('myApp', []); function Car(Engine, Tires, Doors) { ... } Car.$inject = ['Engine', 'Tires', 'Doors']; app.service('Car', Car);
var app = angular.module('myApp', []);
...
app.service('OtherService', function (Car) {
...
});
var app = angular.module('myApp', []); ... app.service('OtherService', function (Car) { ... });
var app = angular.module('myApp', []); ... app.service('OtherService', function (Car) { ... });
import { Injector } from 'angular2/core';
var injector = Injector.resolveAndCreate([
Car,
Engine,
Tires,
Doors
]);
var car = injector.get(Car);
import { Injector } from 'angular2/core'; var injector = Injector.resolveAndCreate([ Car, Engine, Tires, Doors ]); var car = injector.get(Car);
import { Injector } from 'angular2/core'; var injector = Injector.resolveAndCreate([ Car, Engine, Tires, Doors ]); var car = injector.get(Car);
class Car {
constructor(
engine,
tires,
doors
) {
...
}
}
import { Inject } from 'angular2/core';
class Car {
constructor(
@Inject(Engine) engine,
@Inject(Tires) tires,
@Inject(Doors) doors
) {
...
}
}
import { Inject } from 'angular2/core'; class Car { constructor( @Inject(Engine) engine, @Inject(Tires) tires, @Inject(Doors) doors ) { ... } }
function Inject(dependencies) {
return function (target) {
target.parameters = dependencies;
};
}
// so what basically happens is...
Inject(Engine)(Car)
import { Inject } from 'angular2/core';
class Car {
constructor(
@Inject(Engine) engine,
@Inject(Tires) tires,
@Inject(Doors) doors
) {
...
}
}
function Car(engine, tires, doors) {
...
}
Car.parameters = [
[Engine],
[Tires],
[Doors]
];
What about testability?
import { Injector } from 'angular2/core'; var injector = Injector.resolveAndCreate([ Car, Engine, Tires, Doors ]); var car = injector.get(Car);
import { Injector } from 'angular2/core'; var injector = Injector.resolveAndCreate([ Car, Engine, Tires, Doors ]); var car = injector.get(Car);
import { Injector, provide } from 'angular2/core'; var injector = Injector.resolveAndCreate([ provide(Car, {useClass: Car}), provide(Engine, {useClass: Engine}), provide(Tires, {useClass: Tires}), provide(Doors, {useClass: Doors}) ]); var car = injector.get(Car);
import { Injector, provide } from 'angular2/core'; var injector = Injector.resolveAndCreate([ provide(Car, {useClass: Car}), provide(Engine, {useClass: Engine}), provide(Tires, {useClass: Tires}), provide(Doors, {useClass: Doors}) ]); var car = injector.get(Car);
import { Injector, provide } from 'angular2/core'; var injector = Injector.resolveAndCreate([ provide(Car, {useClass: Car}), provide(Engine, {useClass: Engine}), provide(Tires, {useClass: Tires}), provide(Doors, {useClass: Doors}) ]); var car = injector.get(Car);
import { Injector, provide } from 'angular2/core'; var injector = Injector.resolveAndCreate([ provide(Car, {useClass: Car}), provide(Engine, {useClass: MockEngine}), provide(Tires, {useClass: Tires}), provide(Doors, {useClass: Doors}) ]); var car = injector.get(Car);
import { Injector, provide } from 'angular2/core'; var injector = Injector.resolveAndCreate([ provide(Car, {useClass: Car}), provide(Engine, {useClass: MockEngine}), provide(Tires, {useClass: Tires}), provide(Doors, {useClass: Doors}) ]); var car = injector.get(Car);
Let's say we have an API endpoint...
'http://api.myapp.com'
How can we make this an injectable?
provide('APIEndpoint', {
useValue: 'http://api.myapp.com'
})
constructor(@Inject('APIEndpoint') api) {
...
}
Okay cool. But what if we have dependencies that aren't (and shouldn't be) available for DI?
class ExceptionHandler {
constructor(rethrowException: boolean) {
}
}
class ExceptionHandler { constructor(rethrowException: boolean) { } }
We don't want to introduce a provider for boolean
here.
provide(ExceptionHandler, {
useFactory: () => {
return new ExceptionHandler(false);
}
})
Of course, a factory can have dependencies
provide(ExceptionHandler, {
useFactory: (logger) => {
return new ExceptionHandler(logger, false);
},
deps: [Logger]
})
Of course, a factory can have dependencies
provide(ExceptionHandler, { useFactory: (logger) => { return new ExceptionHandler(logger, false); }, deps: [Logger] })
Of course, a factory can have dependencies
We can have optional dependencies too!
class Car {
constructor(@Optional @Inject(Engine) engine) {
...
}
}
engine
can be null
Dependencies are still singletons.
var inj = Injector.resolveAndCreate([
Engine
]);
var childInj = inj.resolveAndCreateChild([
Engine
]);
childInj.get(Engine) !== inj.get(Engine)
var inj = Injector.resolveAndCreate([ Engine ]); var childInj = inj.resolveAndCreateChild([ Engine ]); childInj.get(Engine) !== inj.get(Engine)
var inj = Injector.resolveAndCreate([ Engine ]); var childInj = inj.resolveAndCreateChild([ Engine ]); childInj.get(Engine) !== inj.get(Engine)
@Component({
selector: 'app',
template: 'Hello {{name}}!
'
})
class App {
constructor() {
this.name = 'World';
}
}
@Component({ selector: 'app', template: '<h1>Hello {{name}}!</h1>' }) class App { constructor() { this.name = 'World'; } }
@Component({ selector: 'app', template: '<h1>Hello {{name}}!</h1>' }) class App { constructor() { this.name = 'World'; } }
@Component({
selector: 'app',
template: 'Hello {{name}}!
'
})
class App {
constructor() {
this.name = 'World';
}
}
@Component({
selector: 'app',
template: 'Hello {{name}}!
'
})
class App {
constructor() {
this.name = 'World';
}
}
bootstrap(App);
@Component({ selector: 'app', template: '<h1>Hello {{name}}!</h1>' }) class App { constructor() { this.name = 'World'; } } bootstrap(App);
class NameService {
constructor() {
this.name = 'Pascal';
}
getName() {
return this.name;
}
}
@Component({ selector: 'app', template: '<h1>Hello {{name}}!</h1>' }) class App { constructor() { this.name = 'World'; } } bootstrap(App);
@Component({ selector: 'app', template: '<h1>Hello {{name}}!</h1>' }) class App { constructor() { this.name = 'World'; } } bootstrap(App, [NameService]);
@Component({ selector: 'app', template: '<h1>Hello {{name}}!</h1>' }) class App { constructor(@Inject(NameService) nameService) { this.name = nameService.getName(); } } bootstrap(App, [NameService]);
@Component({ selector: 'app', template: '<h1>Hello {{name}}!</h1>' }) class App { constructor(nameService: NameService) { this.name = nameService.getName(); } } bootstrap(App, [NameService]);
What if a child component needs a different NameService
?
Every component in Angular 2 has its own Injector!
@Component({
selector: 'child-component'
template: 'Hello other {{name}}!
'
})
class ChildComponent {
constructor(nameService: NameService) {
this.name = nameService.getName();
}
}
@Component({
selector: 'child-component',
providers: [
provide(NameService, {
useClass: OtherNameService
})
],
template: 'Hello other {{name}}!
'
})
class ChildComponent {
constructor(nameService: NameService) {
this.name = nameService.getName();
}
}
@Component({ selector: 'child-component', providers: [ provide(NameService, { useClass: OtherNameService }) ], template: '<h1>Hello other {{name}}!>/h1>' }) class ChildComponent { constructor(nameService: NameService) { this.name = nameService.getName(); } }