Dima Dimochka
Dima Dimochka

Reputation: 31

Angular 8 - Service injection and factory pattern

I've gone through several articles and official Angular guides briefly but it seems that they couldn't help me to solve my task. And here is what I wanted and did.

Let's say I have Angular application with product listing page. Moreover this app will have category listing page and some N listing pages in future. As you can see they're quite similar and will have one component in common - data table.

<app-data-table [type]="'product'"></app-data-table>

Which is implemented like:

import {Component, Input, OnInit} from '@angular/core';
import {DataFactoryService} from "../data-factory.service";

@Component({
  selector: 'app-data-table',
  templateUrl: './data-table.component.html',
  styleUrls: ['./data-table.component.css']
})
export class DataTableComponent implements OnInit {

  @Input() type: string;

  private data: any[];

  constructor(private dataFactory: DataFactoryService) { }

  ngOnInit(): void {
    this.dataFactory.getServiceBy(this.type).selectAll();
  }

}

So as you might guess already I meant to make this component service type agnostic. That's why I've created and injected that DataFactory:

import { Injectable } from '@angular/core';
import {ProductService} from "./product.service";
import {CategoryService} from "./category.service";
import {DataService} from "./data.service";

@Injectable({
  providedIn: 'root'
})
export class DataFactoryService {

  private serviceTokenMapping = {
    "product": ProductService,
    "category": CategoryService
  };

  constructor() { }

  public getServiceBy(token: string): DataService {
    return new this.serviceTokenMapping[token];
  }
}

And at the end we have two services for products and categories with some simple basic abstract class:

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

@Injectable({
  providedIn: 'root'
})
export abstract class DataService {

  abstract selectAll(): any[];
}

import { Injectable } from '@angular/core';
import {DataService} from "./data.service";
import {Product} from "./product";

@Injectable({
  providedIn: 'root'
})
export class ProductService implements DataService {

  constructor() {}

  public selectAll(): Product[] {
    console.log(`ProductService...`);
    return [];
  }
}

import { Injectable } from '@angular/core';
import {DataService} from "./data.service";
import {Category} from "./category";

@Injectable({
  providedIn: 'root'
})
export class CategoryService implements DataService {

  constructor() {}

  public selectAll(): Category[] {
    console.log(`CategoryService...`);
    return [];
  }
}

The funny part here is that this implementation works exactly as a expected. So I'm passing type of table as product for product related page, category type is for category etc.

Question is did I do something incorrect from perspective of Angular style (providers, DI etc.) and do we have any way to implement such a requirement to be more Angular-ish?

Upvotes: 3

Views: 4636

Answers (1)

tedian_dev
tedian_dev

Reputation: 54

I think you actually instantiating ProductService and CategoryService every time you trigger this.dataFactory.getServiceBy(this.type) and not actually using the dependency injection in Angular.

You can use the providers property of @NgModule to specify token per dependency and fetch the dependency using @Inject or injector.get.

export interface DataService {
  selectAll(): any[];
}

@Injectable({
  providedIn: 'root'
})
export class ProductService implements DataService {
  public static TOKEN = new InjectionToken<DataService>('ProductService_TOKEN');
  constructor() {}

  public selectAll(): Product[] {
    console.log(`ProductService...`);
    return [];
  }
}

@Injectable({
  providedIn: 'root'
})
export class CategoryService implements DataService {
  public static TOKEN = new InjectionToken<DataService>('CategoryService_TOKEN');
  constructor() {}

  public selectAll(): Category[] {
    console.log(`CategoryService...`);
    return [];
  }
}

@NgModule({
    providers: [
     {
        provide: ProductService.TOKEN,
        useExisting: forwardRef(() => ProductService),
        multi: false
     },
     {
        provide: CategoryService.TOKEN,
        useExisting: forwardRef(() => CategoryService),
        multi: false
     }
   ]
})
export class YourModule {}

@Component({
  selector: 'app-data-table',
  templateUrl: './data-table.component.html',
  styleUrls: ['./data-table.component.css']
})
export class DataTableComponent implements OnInit {

  @Input() type: string;

  private data: any[];

  constructor(
     @Inject(ProductService.TOKEN) private dataService: DataService,
     private injector: Injector
   ) { }

  ngOnInit(): void {
    this.dataService.selectAll();
    this.injector.get<DataService>(CategoryService.TOKEN).selectAll();
  }

}

Upvotes: 1

Related Questions