Steffn
Steffn

Reputation: 295

dynamic, multiple provider for abstract class in angular 4

I am using an abstract class to show some data from different sources. Every source is injected in the abstract class to show the data in a component.

This is my Component, which is using the abstract class to get the data:

    import {AbstractclassService} from '../../../../abstractclass.service';
    import {Source2-Service} from '../../../../Source2.service';
    import {Source1-Service} from '../../../../Source1.service';

    @Component({
      selector: 'app-gauge',
      templateUrl: './gauge.component.html',
      providers: [
        {
          provide: AbstractclassService,
          useValue: Source1-Service , Source2-Service 
          multi: true
        }
    ],
     styleUrls: ['./gauge.component.css']
    })

    export class GaugeComponent implements  OnInit {

    data = [
        {
          name: 'test',
          value: 'test'
        }
      ];

      constructor( public abstractclassService: AbstractclassService  ) {}


      ngOnInit () {

        this.abstractclassService.onMessage = (msg: string) => {
          this.data = [{name: 'test', value: msg}];
      };


    }

And this is my abstract-class as service:

@Injectable()
export abstract class AbstractclassService {


  public onMessage(msg: string): void {
    console.log("Testing");
  }

}

Now, i didn't get it how to inject in useValue the different sources?

Upvotes: 1

Views: 3351

Answers (2)

Camille
Camille

Reputation: 2531

For a similar problematic, I solve it by having an interface and a list of InjectionToken. That's a bit overkill, but that's allow a lot of flexibility and can by apply to other issues.

After setup a default search service in a shared module, multiple custom implementation can be provided (component/module/service). And that without having to explicitly inject (so reference) all possible implementation.

Interface and default implementation

export interface ISearch {
    searchByTerm(textInput: string);
    searchByObject(objInput: Object);
}

export class DefaultSearch implements ISearch {
    searchByTerm(textInput: string) { console.log("default search by term"); }
    searchByObject(objInput: Object) { console.log("default search by object"); }
}

Create list of service implementation with InjectionToken

 // Keep list of token, provider will give a implementation for each of them
 export const SearchServiceTokens: Map<string, InjectionToken<ISearch>> = new Map();
 // Add File service implementation token
 SearchServiceTokens.set('default', new InjectionToken<ISearch>('default'));

Provider for default service implementation

   providers: [
      ...
      // Default implementation service
      {
         provide: SearchServiceTokens.get('default'),
         useClass: DefaultSearch
      }
   ]

Custom implementation (could be on another module)

export class Component1Search implements ISearch {
    searchByTerm(textInput: string) { console.log("component1 search by term"); }
    searchByObject(objInput: Object) { console.log("component1 search by object"); }
}

Add token for custom implementation

SearchServiceTokens.set('component1', new InjectionToken<ISearch>('component1'));

Add provider

   providers: [
      ...
      // Other implementation service
      {
         provide: SearchServiceTokens.get('component1'),
         useClass: Component1Search
      }
   ]

Finally, in a component, global service, ...

    searchService: ISearch;

    constructor(private injector: Injector) {
       // Use default
       this.searchService = this.injector.get(SearchServiceTokens.get('default'));
    }

    setCustomServiceServiceByToken(customSearchServiceToken) {
        // Use custom service
        this.searchService = this.injector.get(SearchServiceTokens.get(customSearchServiceToken));
    }

Upvotes: 1

mickaelw
mickaelw

Reputation: 1513

Use value in provider is not the good way for your use case.

Source1-Service and Source2-Service must be classes which extends the abstract class. After that you inject into two distinct providers your services.

When a class extends an abstract class, the extended classes have to define the method onMessage (here).

So, your component can be look like:

import { Component, Inject } from '@angular/core';
import { Observable } from "rxjs"

abstract class AbstractClassService{
  abstract onMessage(msg: string): {name: string, value: string}
}

class Source1-Service extends AbstractClassService{
  onMessage(msg: string) {
    return {name: "test", value: msg}
  }
}

class Source2-Service extends AbstractClassService{
  onMessage(msg: string) {
    return {name: "test2", value: msg}
  }
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ],
  providers: [
    {provide: "Impl1", useClass: Source1-Service},
    {provide: "Impl2", useClass: Source2-Service}
  ]
})
export class AppComponent  {

  msg1: {name: string,value:string}[]
  msg2: {name: string,value:string}[]

  constructor(@Inject("Impl1") private service1:AbstractClassService,
              @Inject("Impl2") private service2:AbstractClassService) {

      this.msg1 = [this.service1.onMessage("msg1")]
      this.msg2 = [this.service2.onMessage("msg2")]
  }

}

For the full code: https://stackblitz.com/edit/angular-e8sbg8?file=app%2Fapp.component.ts

For futher

I think it's not a good idea to use the abstract class in this case. You should prefer to use an interface

And, I invite to you to read the DI angular documentation here: https://angular.io/guide/dependency-injection#providers to have more information

Upvotes: 2

Related Questions