Web Programming TutorialsLearn about the Multi Providers feature in Angular2

Learn about the Multi Providers feature in Angular2

Multi Providers

The concept of dependency injection in Angular 2 makes it a very unique front end technology as it comes with an awesome feature known as “Multi Providers”. This feature helps us to hook into certain operations in order to plug in custom functionalities such as validations which could be used during building an application in Angular2. In the earlier articles associated with the concept of Providers, we have discussed about the provider which is nothing but an instruction which describes the way an object for a certain token is generated.

As a revision of an earlier article of the Map Literals example, we had used an EmployeeService class to inject an instance of it when we ask for a dependency of that type in the application. In this app for the employee, the EmployeeService class is used as a Provider where we are importing the type of the dependency that is asked for and annotating our dependency argument with it in our component’s constructor as shown below (in bold). By doing so, the angular knows how to create and inject an object of type EmployeeService after configuring its provider. Providers can be implemented in two ways i.e. either on the application module, which does the app bootstrapping or within the component itself. Such a provider token could be either a string or a type.

import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Employee } from './employee';
import { EmployeeService } from './employee.service';

@Component({
  selector: 'my-employee-detail',
  templateUrl: 'resource/employee-detail.component.html',
  styleUrls: ['assets/employee-detail.component.css'],
  providers: [  
  	{ provide: EmployeeService, 
        useClass: EmployeeService 
      }
  ] // creates a provider for EmployeeService
})
export class EmployeeDetailComponent implements OnInit {

  @Input() employee: Employee;
  
  constructor(
  	private employeeService: EmployeeService,
  	private route: ActivatedRoute) {
  	
	}
  ngOnInit(): void {
  	this.route.params.forEach((params: Params) => {
    	let id = +params['id'];
    	this.employeeService.getEmployee(id)
      	.then(employee => this.employee = employee);
  		});
	}
  goBack(): void {
  	window.history.back();
  }
}

In the above example, the token matches the dependency as instance-type and ‘useClass’ is the provider strategy. Alternatively, the shorthand version can also be used as shown below. This should be noted that the Angular 2 has many such shorthand versions for dependency Injection, annotations, etc.

@Component({
  selector: 'my-employee-detail',
  templateUrl: 'resource/employee-detail.component.html',
  styleUrls: ['assets/employee-detail.component.css'],
  providers: [EmployeeService] 
// creates a provider for EmployeeService
})

As long as, we are representing the providers as classes (or types) there won’t be any naming collisions. But, there are the occasions when we require to create objects without a class representation in order to inject dependency into our application. For example, the configuration object (shown below) could be the simple object literal where no type is involved.

const CONFIGURATION = {
  Url: 'http://mywebsite.com',
  theme: 'cool-blue',
  title: 'My Website app',
  platform: ‘Angular 2’
};

Sometimes, we may require to inject a primitive value such as a string or a Boolean value for dependency injection into an application as shown below.

const ENABLE_FLAG = true;

In such cases, the use of String or Boolean type are not recommended, as this is going to set a default value for place where we are asking for dependencies of these types in our application. Actually, there is no need to introduce a new type for the representation of these values. This is where the string tokens come into the picture which allows us to make the objects available through dependency injection without the introduction of an actual type as shown below.

let enabledFlagToken = 'enabled';
let configurationToken = 'config';

@Component({
  selector: 'my-employee-detail',
  templateUrl: 'resource/employee-detail.component.html',
  styleUrls: ['assets/employee-detail.component.css'],

  providers: [
  { provide: enabledFlagToken, useValue: ENABLE_FLAG },
  { provide: configurationToken, useValue: CONFIGURATION }]
})

In the above example, we are using a simple string as a token in the place of a type. We can inject this dependency using the ‘@Inject ()’ decorator as shown below. In the below example, we have used the ‘@Inject (enabledFlagToken) private enabled’ without any typing information such as ‘private enabled: Boolean’.

import { Inject } from '@angular/core';

let enabledFlagToken = 'enabled';
let configurationToken = 'config';

@Component({
  selector: 'my-employee-detail',
  templateUrl: 'resource/employee-detail.component.html',
  styleUrls: ['assets/employee-detail.component.css'],

  providers: [
  { provide: enabledFlagToken, useValue: ENABLE_FLAG },
  { provide: configurationToken, useValue: CONFIGURATION }]
})

class MyComponent {

  constructor(
    @Inject(enabledFlagToken) private enabled,
    @Inject(configurationToken) private config
  ) {...}
}

This concludes that we can use either types or strings as tokens in order to inject dependencies into our application. But, use of string tokens are prone to the naming collisions. For example, we had used ‘config’ as the string token which happens to be very commonly used word in the programming world. If someone has used the same named string token then it will cause naming collision. Under such situation, the Providers are flattened. It means that, if there are multiple providers with the same token, then the last token will win.

Opaque Tokens to counter naming collisions
Angular 2 team was aware of the situation of naming collision for string tokens. Therefore, they come up with the concept of OpaqueToken, which allows us to create string-based tokens without letting our application run into any type of naming collision. The following example demonstrate the creation of an OpaqueToken:

import { OpaqueToken } from '@angular/core';

const CONFIGURATION_TOKEN = new OpaqueToken('config');

export const THIRD_PARTY_LIBRARY_PROVIDERS = [
  { provide: CONFIGURATION_TOKEN, useClass: ThirdPartyConfiguration }
];

• Firstly, we need to import ‘OpaqueToken’ from ‘@angular/core’ and use it.
• The above example demonstrates the third-party provider’s collection by using the OpaqueToken.
• Now, let us create our application by using an OpaqueToken for our ‘config’ token as shown below.

import { OpaqueToken } from '@angular/core';
import THIRD_PARTY_LIBRARY_PROVIDERS from './third-party-library-pack';

const MY_CONFIGURATION_TOKEN = new OpaqueToken('config');

...
providers = [
  EmployeeService,
  THIRD_PARTY_LIBRARY_PROVIDERS,
  { provide: MY_CONFIGURATION_TOKEN, useValue: CONFIG } 
]

In the above scenario, there appears the situation of the naming collision between the third party library and our application as both are using same string token ‘config’. Since, we have created these string tokens as OpaqueToken. Therefore, the Angular 2 is smart enough to resolve the runtime dependencies and prevent the naming collisions.

Multi Providers in Angular 2
When we want to provide multiple dependencies for a single token, the Angular 2 supports this features and it looks like as follows.

import { OpaqueToken } from '@angular/core';

const MY_CONFIGURATION_TOKEN = new OpaqueToken('config');

...

var injector = ReflectiveInjector.resolveAndCreate([
  { provide: MY_CONFIGURATION_TOKEN, useValue: 'dependency x', multi: true },
  { provide: MY_CONFIGURATION_TOKEN, useValue: 'dependency y', multi: true }
]);

var dependencies = injector.get(MY_CONFIGURATION_TOKEN);
// dependencies == ['dependency x', 'dependency y']

The above code manually creates an injector by using the multi: true option. Here, we are using OpaqueToken in order to mitigate the possibility of the naming collision as discussed before. When we use multi: true then it informs the Angular 2 that the type of provider used is multi provider where multiple values can be provided for a single token in Dependency Injection. Therefore, when the dependency of this token is asked then it will provide us a list of all registered and provided values. As explained before, when we are registering multiple providers with the single token then the last one wins as shown in the below example.

class Cycle { }
class MotorCycle { }

var injector = ReflectiveInjector.resolveAndCreate([
  { provide: Cycle, useClass: Cycle },
  { provide: Cycle, useClass: MotorCycle }
]);

var cycle = injector.get(Cycle);
// cycle instanceof MotorCycle

It means that with the help of the multi providers, we are mainly extending the thing that is being injected for a particular token. Angular 2 is using the same mechanism in order to provide the pluggable hooks. Use of Validators is one of an example of such hooks. When we create a validator, then we need to add it to the NG_VALIDATORS multi provider, so that the Angular 2 can pick it up when needed as shown below. Since, we either extend or override a provider for a token therefore, multi providers cannot be mixed with the normal providers.

import { Component, OnInit, forwardRef, Input, OnChanges } from '@angular/core';
import { FormControl, ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';

@Component({
  selector: 'counter-value-app',
  templateUrl: 'resource/model-form-component.html',
  styleUrls:  ['assets/styles.css'],
  providers: [
    { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CounterControlComponent), multi: true },
    { provide: NG_VALIDATORS, useExisting: forwardRef(() => CounterControlComponent), multi: true }
  ]
})

export class CounterControlComponent implements ControlValueAccessor, OnChanges {

  propagateChange:any = () => {};
  validateFn:any = () => {};
  
  @Input('currentCounterValue') _currentCounterValue = 0;
  @Input() counterRangeMaxValue;
  @Input() counterRangeMinValue;

  get currentCounterValue() {
    return this._currentCounterValue;
  }
  
  set currentCounterValue(val) {
    this._currentCounterValue = val;
    this.propagateChange(val);
  }

  ngOnChanges(inputs) {
    if (inputs.counterRangeMaxValue || inputs.counterRangeMinValue) {
      this.validateFn = maxMinCountValueRangeValidator(this.counterRangeMaxValue, this.counterRangeMinValue);
    }
  }

  writeValue(value) {
    if (value) {
      this.currentCounterValue = value;
    }
  }

  registerOnChange(fn) {
    this.propagateChange = fn;
  }

  registerOnTouched() {}

  increment() {
    this.currentCounterValue++;
  }

  decrement() {
    this.currentCounterValue--;
  }

  validate(c: FormControl) {
    return this.validateFn(c);
  }
}

export function maxMinCountValueRangeValidator(endValue, startValue) {
  return (c: FormControl) => {
    let err = {
      rangeError: {
        given: c.value,
        max: endValue || 10,
        min: startValue || 0
      }
    };

  return (c.value > +endValue || c.value < +startValue) ? err: null;
  }
}

Conclusion: –
In this article, we discussed about the multi providers and their use. In the example, we used multi providers along with the OpaqueToken for string tokens in Angular 2 in order to prevent the naming collisions.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Exclusive content

- Advertisement -

Latest article

21,501FansLike
4,106FollowersFollow
106,000SubscribersSubscribe

More article

- Advertisement -