Angular 5 service replacement/override
Asked Answered
B

1

12

I created a core library for my project containing some components and services. I built the library with ng-packagr. In the consuming project which references the library I built my webapp containing components provided by the library. Nothing special so far. But sometimes I want a component (coming from my lib) calling a method from a Service outside of the lib. Is this possible? Can I somehow inject a service to a component which is defined inside a library?

Cheers

Bountiful answered 21/4, 2018 at 19:35 Comment(4)
Are you asking if a consumer of your library can provide a different service for your library to use?Treytri
Yes, as we know Angular is all built out of modules. To have your component speak to another service/component declared in another module (library) that module needs to be imported in your module (that contains the component you wish to speak to the external service/module). That is exactly how you are using the standard Angular services like httpClient and so on.Ross
@Daniel W Strimpel. Basically yes. I want to outsource core components and services in a lib. I reference this lib and therefore the components in another project. Sometimes I want the referenced component to use a customized service instead of the original referenced service (also part of the lib).Bountiful
Research the use of forRoot methods on your module to provide config information.Treytri
A
24

I've achieved this before with something like this:

Your library's service(s) should be defined as an interface rather than as a concrete implementation (as is done in OO languages quite often). If your implementing application will only sometimes want to pass in its own version of the service then you should create a Default service in your library, and use it as so:

import { Component, NgModule, ModuleWithProviders, Type, InjectionToken, Inject, Injectable } from '@angular/core';

export interface ILibService {
  aFunction(): string;
}

export const LIB_SERVICE = new InjectionToken<ILibService>('LIB_SERVICE');

export interface MyLibConfig {
  myService: Type<ILibService>;
}

@Injectable()
export class DefaultLibService implements ILibService {
  aFunction() {
    return 'default';
  }
}

@Component({
  // whatever
})
export class MyLibComponent {
  constructor(@Inject(LIB_SERVICE) libService: ILibService) {
    console.log(libService.aFunction());
  }
}

@NgModule({
  declarations: [MyLibComponent],
  exports: [MyLibComponent]
})
export class LibModule {
  static forRoot(config?: MyLibConfig): ModuleWithProviders {
    return {
      ngModule: LibModule,
      providers: [
        { provide: LIB_SERVICE, useClass: config && config.myService || DefaultLibService }
      ]
    };
  }
}

Then in your implementing application you have the ability to pass in the optional config via your library's forRoot method (note that forRoot should only be called once per application and at the highest level possible). Note that I've marked the config parameter as optional, so you should call forRoot even if you have no config to pass.

import { NgModule, Injectable } from '@angular/core';
import { LibModule, ILibService } from 'my-lib';

@Injectable()
export class OverridingService implements ILibService {
  aFunction() {
    return 'overridden!';
  }
}

@NgModule({
  imports: [LibModule.forRoot({ myService: OverridingService })]
})
export class ImplementingModule {

}

This was from memory as I don't have the code to hand at the moment so if it doesn't work for any reason let me know.

Avis answered 21/4, 2018 at 22:14 Comment(8)
Hey I tried it and it works like a charm. I only had one issue: the default service had no provider :) I just added it to the provides list of the library module. The rest worked and therefore I set this answer as solution to my question. Thanks again.Bountiful
You shouldn't add it to the providers list, forRoot takes care of that. I missed the @Injectable() decorator on the services - that should be the issue.Avis
Unfortunately I receive the same issue, even with @Injectable() on both services :(Bountiful
Could you show me where you import the module and the exact error message?Avis
Sure. This is the library repo: github.com/baerree/ng-package-pos-shell and this is the consuming project: github.com/baerree/ng-package-pos-shell-consumer the error message is:NullInjectorError: No provider for PosShellService!Bountiful
Check out MyLibComponent in my answer - see that it expects ILibService to be injected, NOT the concrete class DefaultLibService That's your problem in CustomFlowAComponentAvis
I see, it was referenced in the app.component.ts :) I removed the old direct references to PosShellService, and now it is working as you described it. Thanks again for taking time to help me out!Bountiful
this is crazy and works excelente, thank you.Floats

© 2022 - 2024 — McMap. All rights reserved.