0

A while ago we started creating internal NPM packages with angular components in them so we can reuse our common angular components between our different sites. With some new work we we are doing we're trying to support AOT compilation as well as having the components in the NPM package compatible with both angular 2.x and 4.x. We have a few configurations that need to be passed into the npm package/library (api keys/urls, environment, etc) so I would like to have the implementing site provide a config object that can be injected into the components that need those values. I am having issues getting the providers to work with AOT. So far, the only way I've been able to get this to work is to use an explicit string token, but this is far from ideal in my eyes.

Working:

In the consuming AOT site:

import { environment } from './../environments/environment';

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
  ],
  providers: [
      {
          provide: 'ConfigToken',
          useValue: environment
      }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

In the library components:

constructor(@Inject('ConfigToken') private config: Configuration) { }

Interface exported from library:

export interface Configuration {
    someValue: string;
    someOtherValue: string;
}

I have tried:

  • exporting an OpaqueToken from the library

  • exporting a class to use as a token

    With both of those strategies, I end up with an error ERROR in Can't resolve all parameters for SomeComponent in C:/Development/path/to/bundle

Community
  • 1
  • 1

2 Answers2

1

Well, I found the answer to my own question eventually... The issue was from using an interface as the type for the injected value. I was looking through the generated java script and noticed this:

SomeComponent.ctorParameters = function () { return [
    { type: undefined, decorators: [{ type: _angular_core.Inject, args: [MyProviderToken,] },] },
]; };

Once I changed the type to be a class, instead of an interface, the type was no longer undefined and angular was able to inject the value provided by the consuming app.

  • Good to know , and that makes sense... the injector can only inject instances of things, and interfaces by themselves cannot be an instance. – diopside Aug 25 '17 at 06:22
  • The other possibility, is it was an issue from using barrels for imports. I made too many little changes at once. I have a feeling it was a combination of the two though. However, The injector should be passing in the object that is given in the NgModule declaration since I was using `useValue`, rather than trying to create an instance of the data type it's injecting. But, either way, I certainly learned something, which is always good. Sometimes just looking at the compiled code is the best way to find your issue. ¯\_(ツ)_/¯ – InfedelCastro Aug 25 '17 at 14:05
0

You can use the 'useValue' option on an injectable class to redefine it at runtime. This is how I provide config for my app. Just create a simple injectable placeholder like

 import { Injectable } from '@angular/core' 

 @Injectable() export class MyAppConfig {} 

Then in your module you can import some constant from any file. Let's say the const looked like this

 export const MY_APP_CONFIG = { 
     configOption1 = 'test1';
     configOption2 = 123;
 }

Now in your module, you can import the injectable class and the constant

 import { MyAppConfig } from './wherever/it/is'
 import { MY_APP_CONFIG } from './wherever/it/is'

and then in providers

 providers: [
       {
          provide: MyAppConfig,
          useValue: MY_APP_CONFiG
       }
   ],

There's supposedly a better way to do this with InjectionToken https://angular.io/api/core/InjectionToken

But this is simpler and has worked for me. From my understanding, the downside of this method is it will do nothing to prevent namespace collisions.

EDIT -

As for how to consume it in components...

import the injectable config class

import { MyAppConfig } from './wherever'

and then in the constructor

constructor(private config: MyAppConfig) { 
     console.log(config);
}

You'll see it prints the value of the const you assigned to it rather than an empty object.

diopside
  • 2,981
  • 11
  • 23
  • I had tried exporting a class to use as the provide token, but didn't use `@Injectable()` so I gave it a shot adding that, but am still getting the same error. Are you using AOT Compilation? – InfedelCastro Aug 24 '17 at 20:24
  • Yeah I am, hmm. Odd. I read that you tried that but assumed you meant you had tried 'useClass'. My bad. – diopside Aug 24 '17 at 20:26
  • I'm not sure if this matters, but in my app, I essentially have the entire app as a separate module (a module that imports a bunch of its own child modules) and I import that module into the main app.module.ts. I did it like this because I want to be able to take my app and include it in other apps. But for now, my app.module.ts is essentially the default boilerplate app.module.ts other than the import statement for my custom app's module. And then its in my custom app's module where I am using the method I described to you to provide runtime config. Hope that makes sense... – diopside Aug 24 '17 at 20:31
  • In other words, I'm not using this method in the app.module.ts that is being bootstrapped at runtime to start the app. But they are all being loaded at basically the same time, so i dunno why it would matter. – diopside Aug 24 '17 at 20:33
  • I have several modules in my library/npm package that declare/export components which are imported into my main AppModule in the Angular CLI project. What's strange, is the provider works if I just use a string provider token. Also, I was able to inject a service into those imported components from a non-angular npm package using `{ provide: SomeOtherService, useClass: SomeOtherService}` in my AppModule. – InfedelCastro Aug 24 '17 at 20:39
  • I did another test... I injected the environment config using a class from the npm package into a component in my consuming application and it worked... But when I tried to inject it into the component from the library compile started to fail. So, it's something specific to injecting into a component from an imported module maybe? – InfedelCastro Aug 24 '17 at 21:05