🤯
There are so many ways to incorporate providers in Angular!
How do we start?!?
You don't need to be an expert. Let's get practical!
A provider is an instruction to the Dependency Injection system on how to obtain a value for a dependency. Most of the time, these dependencies are services that you create and provide.
Decouples object creation from using the object - promotes loose coupling
When we use dependency injection, we are taking advantage of a software design principle called Dependency Inversion
SOLID
By decoupling dependency creation from the consumer, you can change the dependency code without changing the consuming code.
injector is the main mechanism for handling DI in Angular. It creates the dependencies, maintains
them, and hands you the dependency you're looking for. Very snazzy.injector, we need to register our dependenciesBut what if there are multiple baristas?
As much as possible, ideally always
@Injectable() decoratorproviders array
@Injectable({
providedIn: 'root'
})
export class DramaService {
}
Recommended in Angular v6+ to support tree-shaking
providers array
@NgModule({
providers: [DramaService]
})
export class AppModule {
}
@Component({
selector: 'app-kdramas'
})
export class KDramasComponent {
constructor(private dramaService: DramaService) {
// singleton instance of DramaService
// is injected into this component
}
}
@Component({
selector: 'app-kdramas'
})
export class KDramasComponent {
constructor() {
const dramaService: DramaService = inject(DramaService);
}
}
Available in Angular v14+
You can register providers
The injector you provide to affects the scope and lifetime of the provider.
@NgModule({
providers: [DramaService] // provide to ModuleInjector
})
export class DramasModule { }
@Component({
selector: 'app-drama',
providers: [DramaService] // provides to ElementInjector
})
export class DramasComponent { }
const routes: Routes = [{
path: 'dramas',
loadChildren: () => import('./dramas/dramas.module').then(m => m.DramasModule),
providers: [DramaService] // provides to EnvironmentInjector
}];
Available in Angular v14+
@Injectable({
providedIn: 'root' // specify injector - a module, 'platform', or 'any'
})
Depends on scope and lifetime desired
Angular resolves which dependency you get via a resolution process
Angular team announced new debugging capabilities that help you identify where a dependency comes from
Prefer simple
Injection tokens are a way we can add dependencies to values such as text, numbers, and objects.
Allows for injectable dependencies to config objects and global objects, such as Web APIs!
You can also add dependencies to abstractions that don't have a runtime definition, such as interfaces.
interface Drama {
// members here
}
class DramaComponent {
constructor(private drama: Drama) { }
}
⛔️
No runtime representation
With injection tokens we can have interfaces as dependencies
Source from angular/packages/core/src/di/injection_token.ts
APP_INITIALIZER, DOCUMENTWe can unleash the full power of injection tokens and Angular's DI system by configuring the providers array
providers arrayproviders array
@Component({
selector: 'app-kdrama'
providers: [
KDramaService
]
})
export class KDramaComponent { }
providers array
@Component({
selector: 'app-kdrama'
providers: [
{ provide: KDramaService, howToProvide?: SpecialDependency }
]
})
export class KDramaComponent { }
providers arrayuseClass- Associate a new instance of a class to the provideruseExisting- Alias an existing instance to the provideruseValue- Associate a fixed valueuseFactory- Create a dependency using a factory methodLet's add features to the K-Drama app!
Streamline the calls made to drama service for the dashboard view.
The suggestion engine for suggested K-Dramas should take the user's streaming service into account.
Streamline drama service for the dashboard
Original implementation
@Injectable({
providedIn: 'root'
})
export class DramaService {
public getDramas(): Drama[] {
// returns all dramas with a subset of details
}
public getDrama(id: number): DramaDetail|undefined {
// returns the requested drama details
}
}
@Component({
selector: 'app-dashboard'
template: ``
})
export class DashboardComponent {
constructor(private dramaService: DramaService) { }
}
export abstract class DashboardService {
abstract getDramas: () => [];
}
@NgModule({
declarations: [ ],
imports: [ ],
providers: [
{ provide: DashboardService, useExisting: DramaService },
],
bootstrap: [AppComponent]
})
export class AppModule { }
@Component({
selector: 'app-dashboard'
template: ``
})
export class DashboardComponent {
constructor(private dashboardService: DashboardService) { }
}
The useExisting configuration option helped us narrow the API surface of an existing service.
Take streaming platform into account within the suggestion engine
Original implementation
@Injectable({
providedIn: 'root'
})
export class SuggestionService {
public getSuggestions(id: number): Drama[] {
// return suggested dramas based on the drama id passed in
}
}
@Injectable({
providedIn: 'root'
})
export class MyFaveKDramasSuggestionService implements SuggestionService {
public getSuggestions(id: number): Drama[] {
// return suggested dramas based on the id of the drama
// available on MyFaveKDramas streaming service
}
}
@Injectable({
providedIn: 'root'
})
export class NetflixSuggestionService implements SuggestionService {
public getSuggestions(id: number): Drama[] {
// return suggested dramas based on the id of the drama
// available on Netflix streaming service
}
}
export function suggestedDramasFactory() {
return (config: UserConfig) =>
config.streamingService === 'myfavekdramas' ?
new MyFaveKDramasSuggestionService() :
new NetflixSuggestionService();
}
@Component({
selector: 'app-suggested-drama',
template: ``,
providers: [{
provide: SUGGESTED_DRAMAS_TOKEN,
useFactory: suggestedDramasFactory(),
deps: [USER_CONFIG_TOKEN]
}]
})
export class SuggestedDramasComponent implements OnInit {
public suggestedDramas: Drama[] = [];
constructor(
@Inject(SUGGESTED_DRAMAS_TOKEN) private svc: SuggestionService
) { }
public ngOnInit(): void {
const id = 123; // from ActivatedRoute
this.suggestedDramas = this.svc.getSuggestions(id);
}
}
The useFactory configuration option allows us to define a factory method.
This is where the instance to use at runtime is determined, based on a dynamic value.
We did it!
Post about practical DI uses in Angular
bit.ly/ADAngularDI-okta
Angular DI docs
angular.io/guide/dependency-injection-overview