Overriding providers in the injector hierarchy

We've seen so far how Angular's DI framework uses the dependency token to introspect the type required and return it right from any of the provider sets available along the component hierarchy. However, we might need to override the class instance corresponding to that token in certain cases where a more specialized type is required to do the job. Angular provides special tools to override the providers or even implement factories that will return a class instance for a given token, not necessarily matching the original type.

We will not cover all the use cases in detail here, but let's look at a simple example. In our example, we assumed that the Playlist object was meant to be available across the component tree for use in different entities of the application. What if our MusicAppComponent directive hosts another component whose child directives require a more specialized version of the Playlist object? Let's rethink our example:

MusicAppComponent
MusicChartsComponent
MusicPlayerComponent
MusicLibraryComponent
MusicPlayerComponent

This is a bit of a contrived example, but it will definitely help us to understand the point of overriding dependencies. The Playlist instance object is available right from the top component downwards. The MusicChartsComponent directive is a specialized component that caters only for music featured in the top seller's charts and hence its player must playback big hits only, regardless of the fact it uses the same component as MusicLibraryComponent. We need to ensure that each player component gets the proper playlist object, and this can be done at the MusicChartsComponent level by overriding the object instance corresponding to the Playlist token. The following example depicts this scenario, leveraging the use of the provide function:

import { Component } from '@angular/core';
import { Playlist } from './playlist';

import { TopHitsPlaylist } from './top-hits/playlist';

@Component({
selector: 'music-charts',
template: '<music-player></music-player>',
providers: [{ provide : Playlist, useClass : TopHitsPlaylist }]
})
export class MusicChartsComponent {}

The provide keyword creates a provider mapped to the token specified in the first argument (Playlist, in this example) and the property useClass essentially overwrites the playlist with TopHitsPlaylist from this component and downstream.

We could refactor the block of code to use viewProviders instead, so we ensure that (if required) the child entities still receive an instance of Playlist instead of TopHitsPlaylist. Alternatively, we can go the extra mile and use a factory to return the specific object instance we need, depending on other requirements. The following example will return a different object instance for the Playlist token, depending on the evaluation of a Boolean condition variable:

function playlistFactory() {
if(condition) {
return new Playlist();
}

else {
return new TopHitsPlaylist();
}

}

@Component({
selector: 'music-charts',
template: '<music-player></music-player>',
providers: [{ provide : Playlist, useFactory : playlistFactory }]
})
export class MusicChartsComponent {}

So, you can see how powerful this is. We could, for example, make sure that our data service suddenly would be replaced by a mock data service when testing. The point is it is really easy to tell the DI mechanism to change its behavior based on a condition. 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset