The good practices guide of Angular proposes that the transfer and storage of information is done through services.
In many occasions, we will want to create generic components (to manage different resources of a family) that will have to handle different services depending on how they were created.
The challenge to be solved in this context (and the topic of the article) is to manage this dynamic injection of services in a c lean, scalable and efficient way.
In this article we will use the Abstract Factory pattern in combination with the Angular Injectors to solve the mentioned problem.
A poor and ugly solution
One way to solve this problem is to inject each service and use whatever is necessary:
@Component({
. . .
})
export class GenericComponent implements OnInit {
public resource: any;
constructor(
private service1: Service1,
private service2: Service2, // Rest of services
. . .
) {}
ngOnInit() {
// Get parameter to resolve Service to use
const serviceType = this.route.snapshot.data['type'];
// Resolve service to use
if (serviceType === 'SERV1') {
this.foods = this.service1.get();
}
if (serviceType === 'SERV2') {
this.foods = this.service1.get();
}
// Everything else
// . . .
}
}
However, it is not a good solution , since the logic and the constructor of our component will grow as we need to add more services. What about if you have to solve the injection of 50 services?
Applying method
If you are not acquainted with the Abstract Factory pattern, I recommend this reading before applying the method: Abstract Factory Pattern.
To implement this creational pattern, we should consider coding the following classes and interfaces:
- AbstractFactoryInterface: interface that will implement each class.
- AbstractFactoryProvider: will solve a ConcreteFactory upon request.
- ConcreteFactory(s): create an instance of a class that implements the Abstract Factory Interface.
Keep in mind that implementation doesn’t strictly follow the principles of the Abstract Factory pattern, this is an adaptation combined with the Angular Injectors.
AbstractFactoryInterface & AbstractFactoryProvider
The first step is to create the common interface (methods that will implement the concrete classes) and the provider responsible for resolving which class to instantiate.
// food.ts
import { PastaService } from './pasta.service';
import { PizzaService } from './pizza.service';
// AbstractFactoryInterface
export interface Food {
get(): Observable<any>;
}
// AbstractFactoryProvider as a HashMap
export const foodMap = new Map([
['PASTA', PastaService],
['PIZZA', PizzaService]
]);
ConcreteFactory
Once our interfaces and the provider are created, we will create our concrete classes.
// pasta.service.ts
import { Injectable } from '@angular/core';
import { Food } from './food.interface';
. . .
// ConcreteFactory
@Injectable()
export class PastaService implements Food {
constructor() {}
public get(): Observable<any> {
return Observable.of([
{
name: 'Carbonara'
},
{
name: 'Pesto'
}
])
}
}
Note that the PizzaService class implements the methods of the Food interface.
Angular Injector
Once our ConcreteFactories are implemented, we will proceed to solve the one that proceeds and inject it using the Angular Injector.
// generic.component.ts
import { Component, OnInit, Injector, Input } from '@angular/core';
import { foodMap } from './food.interface';
@Component({
selector: 'generic-food',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class GenericFoodComponent implements OnInit {
@Input() type: string; // 'PASTA' or 'PIZZA'
public foods: Array<any>;
public service: any;
constructor(private injector: Injector) {}
ngOnInit() {
// Resolve AbstractFactory
const injectable = foodMap.get(this.type);
// Inject service
this.service = this.injector.get(injectable);
// Calling method implemented by Food interface
this.service.get().subscribe((foods) => {
this.foods = foods;
})
}
}
Considerations
Please consider not applying this method when it is not strictly necessary and only when you need to instantiate services in a generic and dynamic way.
Conclusions
We have seen how to apply the AbstractFactory pattern in combination with the Angular Injectors, which can resolve the coding of bad smells when you need to inject a service depending on a parameter instance of inject all of them.
Top comments (0)