DEV Community

Raúl Julián López Caña
Raúl Julián López Caña

Posted on • Originally published at Medium on

Angular Tips | Combine Abstract Factory Pattern & Injector to inject a service depends on parameter 👷 📐

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
    // . . .
  }
}
Enter fullscreen mode Exit fullscreen mode

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]
]);
Enter fullscreen mode Exit fullscreen mode

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'
      }
    ])
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Example

rjlopezdev-injector - StackBlitz

Top comments (0)