4

I'm using angular cli and angular 4. I've made some classes from which my components inherit behavivour! However during development their constructores grow and in the current phase I need to be able to allow other to use them whithout the need of knowing their constructors!

So after seeing Petr sugestion in dependency injection I tried, but had to make some adjustments due to errors the compiler was giving, I suppose due to diferences betwween angular 4 and angular 5!

This is my code:

Service Locator:

import {ReflectiveInjector} from "@angular/core";

export class ServiceLocator {
  static injector: ReflectiveInjector;
}

module which imports the serviceLocator:

ServiceLocator.injector = ReflectiveInjector.resolveAndCreate(
      Object.keys(services).map(key => ({
        provide: services[key].provide,
        useClass: services[key].provide,
        deps: services[key].deps
      }))
    );

serviceList:

import {SysFwDatesService} from '../sysDates/sysFwDates.service';
import {MatSnackBar} from '@angular/material';
import {SysFwFormSpecBuilder} from '../uiValidation/SysFwFormSpecBuilder';
import {SysFwHttpApi} from '../http/SysFwHttpApi';


export const services: {[key: string]: {provide: any, deps: any[], useClass?: any}} = {
 'SysFwDatesService': {
    provide: SysFwDatesService,
    deps: []
  },
  'MatSnackBar': {
    provide: MatSnackBar,
    deps: []
  },
  'SysFwFormSpecBuilder': {
    provide: SysFwFormSpecBuilder,
    deps: []
  },
  'SysFwHttpApi': {
    provide: SysFwHttpApi,
    deps: []
  }
}

It seems to be working, however it seems to have lost the other providers and to be expecting all providers to be passed this way!

This is the error I'm getting:

No provider for HttpClient! (SysFwDatesService -> SysFwHttpApi -> HttpClient)

Do I need to put everything in the servicesList? What am I doing wrong?

Before I use the injector it all worked fine!

Thanks for your help!

2 Answers 2

1

HttpClient is defined in HttpClientModule and has a hierarchy of dependencies that is not very easy to list by hand as single providers array. ReflectiveInjector and Injector don't support Angular modules, and it's impractical to parse a module to get a hierarchy providers manually - this is what Angular already does internally.

The module that initializes service locator should import dependency modules, too, and resulting injector should inherit from module injector:

@NgModule({ imports: [HttpClientModule], ... })
export class AppModule {
  constructor(injector: Injector) {
    ServiceLocator.injector = ReflectiveInjector.resolveAndCreate(
      Object.keys(services).map(key => ({
        provide: services[key].provide,
        useClass: services[key].provide,
        deps: services[key].deps
      })),
      injector
    );
  }
}

By the way, services object keys aren't used for anything and are redundant. services likely should be an array.

I would strongly advise against using this home-grown service locator in real-world application. It is a proof of concept that does what it does, but also a hack that isn't idiomatic to the framework and may have a lot of negative consequences that may not be obvious at this moment, e.g. lazy loaded modules.

The recommendation is to use the means offered by the framework wherever possible, this way the application has most chances to be trouble-free in future.

It is:

@Injectable() // needed for JIT compilation
abstract class Injective {
  constructor(protected injector: Injector) {}
}

@Injectable() // potentially needed for AOT compilation
class FooService extends Injective {
  baz = this.injector.get(Baz);
}

@Component(...)
class BarComponent extends Injective {
  foo = this.injector.get(FooService);

  // just passes injector to parent constructor if there's a need for own constructor
  constructor(injector: Injector, public baz: Baz) {
    super(injector);
    // ...
  }
}

Notice that to be on safe side, @Injectable() is needed on both parent and child classes (this may vary between Angular and Angular CLI versions).

Sign up to request clarification or add additional context in comments.

7 Comments

Thanks! I'm going to try this! However I need some clarifications... What's the purpose of the abstract class Injective? From what I understood my base class should inherit from it! Is that it? My child classes are components should I make @Injectable() @component(...) class child extends parent(){}? Is that it?
Injective is supposed to be base class (that's why it's abstract). If you have another base class in mind that should contain something else, then yes, Base should inherit from Injective. @Component and @Directive are already injectable, so you need @Injectable() only on service and base classes.
Passing the injector in the resolveAndCreate call did the trick! Thank you very much!
You're welcome. But again, if you didn't do that before, I wouldn't suggest to do that for anything but experimenting. ServiceLocator recipe is faulty by design and will go badly sooner or later. Not so convenient if there's already a hundred classes that rely on it.
But... Is there any other way to avoid my coworkers from having to know the constructors of the classes they are extending? I need to simplify our future work and wont/need to make the classes in a way they only need to put in their constructor their specific services and inherit the others. Also it would solve the problem of having to refactor all the subclasses if another service is added to the parent class!
|
0

have you tried explicitly specifying HttpClient as a dependency if SysFwHttpApi ? Like

'SysFwHttpApi': { provide: SysFwHttpApi, deps: [HttpClient] }

8 Comments

Yes! It gives the same error! I also tried to add it to the list 'HttpClient': { provide: HttpClient, deps: [] } This solves, but gives error in another service and so on!
Did you import the HttpClientModule into your AppModule?
No! I did it in the SysFwHttpApiModule
Already tried to import it in the baseClasses module. Didn't work.
It should be imported into the module in which you declare the service with the ^above declaration.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.