Dependency injection is a software design pattern that allows someone to remove hard-coded dependencies and makes it possible to change them. Dependencies can be injected to the object via the constructor or via defined method or a setter property.
Faiz Mohamed Haneef is currently the CEO and Chief Technology Architect at Neoito. He's a former Architect at Lowe's, Hallmark and Infosys.
2. Agenda
• Dependency Injection - Design Pattern
• Dependency Injection/DI - Framework
• Create DI Framework with custom compiler like Angular (Hands-On)
• Angular 2+ DI Framework
3. Before We Start
• Stop Me if Something Isn’t clear.
• Raise your hands / Dance / Wave/ Clap - Grab my attention
• Ground Rules are set.. Lets Start!!!
5. Different Patterns
var myCode = {
name: 'Faiz',
sayName: function(){
console.log(this.name);
}
}
myCode.sayName();
var name = "Faiz";
function sayName(name) {
console.log(name);
}
sayName(name);
6. Design Patterns
• A general reusable solution to a commonly occurring problem
• Are formalized best practices
• Obtained by trial and error
7. 1 Creational Patterns
These design patterns provide a way to create objects while hiding
the creation logic, rather than instantiating objects directly using
new operator. This gives program more flexibility in deciding which
objects need to be created for a given use case.
2 Structural Patterns
These design patterns concern class and object composition. Concept
of inheritance is used to compose interfaces and define ways to
compose objects to obtain new functionalities.
3 Behavioral Patterns
These design patterns are specifically concerned with
communication between objects.
9. Class in Javascript vs Typescript
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
var Greeter = function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
var greeter = new Greeter("world");
10. Quick Recap
• So far good?
• We understand
• Design Patterns
• Design Principles
• TS Class
Lets JUMP into DI !!!!
11. What's the problem?
export class Car {
public engine: Engine;
public tires: Tires;
public description = 'No DI';
constructor() {
this.engine = new Engine();
this.tires = new Tires();
}
}
What if the Engine class evolves and its
constructor requires a parameter?
What if you want to put a different
brand of tires on your Car?
You have no control over the car's hidden
dependencies. When you can't control the
dependencies, a class becomes difficult to
test.
12. DI in Javascript vs Typescript
With DI - TS
class Car {
public engine: Engine;
public tires: Tires;
public description = ’With DI';
constructor(engine: Engine, tires: Tires) {
this.engine = engine;
this.tires = tires;
}
}
WITHOUT DI - JS
function Car() {
this.description = 'No DI';
this.engine = new Engine();
this.tires = new Tires();
}
WITH DI - JS
function Car(engine, tires) {
this.description = ’With DI';
this.engine = engine;
this.tires = tires;
}
let car = new Car(new Engine(), new Tires());
13. Dependency Injection Pattern (Theory)
• The core concept of DI is to invert the control of managing
dependencies so that instead of the client having to manage its own
dependencies, you instead delegate this responsibility to the code
which actually calls your client, typically passing in dependencies as
arguments to that client.
• This is where the name “dependency injection” comes from –
you inject the dependencies into your client code during the
execution of that code.
14. Inversion Of Control Design Principle
• In object-oriented programming, there are several basic techniques to
implement inversion of control. These are:
• Using a service locator pattern
• Using dependency injection, for example
• Constructor injection
• Parameter injection
• Setter injection
• Interface injection
• Using a contextualized lookup
• Using template method design pattern
• Using strategy design pattern
15. Benefits of DI
• Loose coupling.
• Testing is very simple.
class MockEngine extends Engine { cylinders = 8; }
class MockTires extends Tires { make = 'YokoGoodStone'; }
// Test car with 8 cylinders and YokoGoodStone tires.
let car = new Car(new MockEngine(), new MockTires());
17. Consumer Issue
• The consumer of Car has the problem. The consumer must update the car
creation code to something like this:
class Engine2 {
constructor(public cylinders: number) { }
}
// Super car with 12 cylinders and Flintstone tires.
let bigCylinders = 12;
let car = new Car(new Engine2(bigCylinders), new Tires());
18. How about let car = injector.get(Car);
• The Car knows nothing about creating an Engine or Tires.
• The consumer knows nothing about creating a Car
• This is what a dependency injection framework is all about.
19. Quick Hands On
• Demo to create an injection Framework like Angular 1
• Demo on Custom Template Compiler as well.
20. Angular 2 DI
• @NgModule, @Component
Metadata: Providers - A service provider provides the concrete, runtime
version of a dependency value.
@Injectable - decorator identifies a service class that might require
injected dependencies.
21. injector.get(Car) in Angular 2+ : Injector
• Who?
Angular injector is responsible for creating service instances and injecting
them. The injector maintains an internal token-provider map that it
references when asked for a dependency.
• How?
Register providers with an injector before the injector can create that
service. Angular decorators - @Component and @NgModule accept
metadata with a providers property.
Inject a dependency in the component's constructor by specifying
a constructor parameter with the dependency type
23. Angular 2+ DI - Characteristics
• Singleton services
• Hierarchical injection system – i.e. if @Component.providers exists, it
also creates a new child injector
24. The provide object literal
• Responsibility
Locating a dependency value and registering the provider
• Syntax
providers: [Logger] same as
[{ provide: Logger, useClass: Logger }]
26. Class provider with dependencies
@Injectable()
export class EvenBetterLogger extends Logger {
constructor(private userService: UserService) { super(); }
log(message: string) {
let name = this.userService.user.name;
super.log(`Message to ${name}: ${message}`);
}
}
27. Aliased class providers
[ NewLogger,
// Not aliased! Creates two instances of `NewLogger`
{ provide: OldLogger, useClass: NewLogger}]
The solution: alias with the useExisting option.
[ NewLogger,
// Alias OldLogger w/ reference to NewLogger
{ provide: OldLogger, useExisting: NewLogger}]
28. Value providers
• // An object in the shape of the logger service
• export function SilentLoggerFn() {}
• const silentLogger = {
• logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
• log: SilentLoggerFn
• };
• Then you register a provider with the useValue option, which makes this
object play the logger role.
• [{ provide: Logger, useValue: silentLogger }]
30. Factory providers
src/app/heroes/hero.service.ts
constructor(
private logger: Logger,
private isAuthorized: boolean) { }
)
export let heroServiceProvider = {
provide: HeroService,
useFactory: heroServiceFactory,
deps: [Logger, UserService]
};
src/app/heroes/hero.service.provider.ts (excerpt)
let heroServiceFactory = (logger: Logger, userService: UserService) => {
return new HeroService(logger, userService.user.isAuthorized);
};
The useFactory field tells Angular
that the provider is a factory
function.
The deps property is an array
of provider tokens.
The Law of Demeter (LoD) or principle of least knowledge is a design guideline for developing software, particularly object-oriented programs. In its general form, the LoD is a specific case of loose coupling. The guideline was proposed by Ian Holland at Northeastern University towards the end of 1987, and can be succinctly summarized in each of the following ways:[1]
Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
Each unit should only talk to its friends; don't talk to strangers.
Only talk to your immediate friends.
https://en.wikipedia.org/wiki/Law_of_Demeter
https://en.wikipedia.org/wiki/Single_responsibility_principle
The single responsibility principle is a computer programming principle that states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility. Robert C. Martinexpresses the principle as, "A class should have only one reason to change."[1]
https://www.typescriptlang.org/play/
Instead of asking for them, the Car constructor instantiates its own copies from the very specific classes Engine and Tires.
Forcing me to know how to create Engine class and this is tightly coupled
With DI, your code is by default more loosely coupled which makes it easier to “plug-and-play” throughout your application; for example, you don’t have to worry about using a dependency that was potentially declared in an external scope compared to where you’re actually using it.
https://en.wikipedia.org/wiki/Dependency_injection#Three_types_of_dependency_injection
When you’re debugging code that’s using DI, if the error stems from a dependency, then you may need to follow your stack trace a little bit further to see where the error actually occurs. Because dependencies no longer exist in the same file and/or class as where your logic is happening, you need to know exactly what called the code in question to understand where the problem may lie.
https://www.journaldev.com/1827/java-design-patterns-example-tutorial
https://sourcemaking.com/design_patterns
https://gist.github.com/demisx/9605099
https://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html#dependency-injection-as-a-pattern
Vojta Jina gave a great talk on dependency injection at ng-conf 2014
https://docs.angularjs.org/api/auto/service/$injector#annotate
To be precise, Angular module providers are registered with the root injector unless the module is lazy loaded. In this sample, all modules are eagerly loaded when the application starts, so all module providers are registered with the app's root injector.