Reputation: 1587
I am currently learning myself Angular 2. I found this video which I found particularly helpful in this: https://www.youtube.com/watch?v=_-CD_5YhJTA
However, with regards to dependency injection, a topic Mosh starts on 36:40, I do not seem to fully understand his arguments.
Namely, he states (at 41:50) that by writing the constructor like this
export class CoursesComponent {
title = "The title of courses page";
courses;
constructor (courseService: CourseService) {
this.courses = courseService.getCourses();
}
}
"we wrote a component that is no longer tightly coupled to the service" (quote).
Now I tried to convince myself that it is, but I cannot see how it is. Indeed, we could create a mock service CourseService for other usages of this class. However, there is explicit reference in the constructor to a method which is defined in the service CourseService. In my mind, this makes it clearly not a reusable (and testable) class in this way.
Wouldn't it make much more sense to write something like
constructor (courseService) {
this.courses = courseService;
}
and then to let us worry about what we pass to the constructor somewhere else. Then, in my mind, we are honestly decoupled from the service.
But am I right? Or am I missing the point here? And also, as I am a novice, but in case that I have a point, perhaps somebody could propose and alternative for writing this class along the lines of my concerns?
Thanks a lot.
Willem
Upvotes: 0
Views: 215
Reputation: 657298
In addition to @Piccis answer.
Hollywood principle (loose coupling)
The main thing here is similar to the Hollywood principle. "Don't call us, we call you"
If you would use new Xxx()
in your class it would be very tight coupling:
export class CoursesComponent {
title = "The title of courses page";
courses;
constructor () {
let courseService = new CourseService();
this.courses = courseService.getCourses();
}
}
but with the original code from your question any class that extends CourseService
can be passed in and it is also possible to define how this injected instance should be initialized (see next code snipped below).
any class that extends CourseService
is because of a TypeScript limitation where interfaces are not available at runtime.
In Dart for example it would be any class that implements CourseService
configure a dependency
class CourseServiceConfiguration {
...
}
class CourseService {
constructor(private config:CouseServiceConfiguration) {}
getCourses() { ... }
}
bootstrap(AppComponent, [
CourseServiceConfiguration,
CourseService]);
Would provide an instance of CourseService
configured with an instance of CourseServiceConfiguration
.
Use other key types for DI
If this is still too tight you can use other keys like string
or OpaqueToken.
bootstrap(AppComponent, [provide('courseService', {useClass: CourseService})]);
this way you can inject it like
export class CoursesComponent {
title = "The title of courses page";
courses;
constructor (@Inject('courseService') courseService) {
this.courses = courseService.getCourses();
}
}
but this way you loose type checking and autocompletion support because courseService
is not types.
This way would allow you to use interfaces in TypeScript as well
export interface CourseService {
getCourses();
}
export class CoursesComponent {
title = "The title of courses page";
courses;
constructor (@Inject('courseService') courseService:CourseService) {
this.courses = courseService.getCourses();
}
}
allows you to inject any type that implements CourseService
and still get static type checking and proper autocompletion.
OpaqueToken
is similar to a string key but it prevents two keys with colliding strings would still be distinguishable. In OpaqueToken
the string is only informal.
Upvotes: 1
Reputation: 46
I'm fairly new to Angular 2 but my understanding is that Angular 2 has hierarchical injectors, I can certainly see the constructor typing being useful in telling Angular to find the nearest ancestor instance of the injectable to use. There could be benfits in using sub-classes of courseService so you can have different behaviour depending on the component context.
More on hierarchical injectors: https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html
Upvotes: 1
Reputation: 17762
I think the secret is that you can inject a different implementation of CourseService
from your main.ts (or whatever file defines your initial bootstrap).
This is an example that could work
import {provide} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
import {CourseService} from './course.service';
import {MyCourseService} from '../myImplementation/myCourse.service';
bootstrap(AppComponent,
[provide(CourseService, {useClass: MyCourseService})]);
I hope this helps
Upvotes: 2
Reputation: 15271
Coupling would assume excessive dependency on Service. I guess it is not the case here since Service has clean interface - getCourses()
. We could make it even less coupled injecting interface with opaque token like IService{getCourses()}
and it would make the code complicated and less convinient to use, but...
JavaScript allows us to mock or provide different implementations at runtime without casting errors(duck typing). Check this article.
Upvotes: 1