What is the recommended best practice for loading environment specific configuration during runtime of an Angular application? The Angular documentation mentions the use of APP_INITIALIZER, but that is still not early enough in the load process for things such as runtime configuration of imported modules that make use of the .forRoot() convention.
In my use case, I have an authentication service built and imported via a Core module, which is imported by the App module. The authentication library I am using (the angular-oauth2-oidc library) allows for configuration of the automatic appending of access tokens during when importing the module (see this segment). Since there are constraints in the build environment I am working with that only allows me to produce one common build package to deploy to all environments, I am unable to dynamically set values by using different environment.ts files.
One initial idea is to use the fetch API on the index.html page to load a JSON file containing the configuration onto a global variable, but since the call is asynchronous, there is a chance the configuration will not be fully loaded when the import of the Core module occurs.
This was part of my config setup to bring my app through the build pipeline and took me days. I ended up in a solution using the APP_INITIALIZER loading a REST service and build a AppConfigService for my App. I am using the same angular-oauth2-oidc library.
My Solution for this issue was not to setup the OAuthModule in its forRoot() method. It is called before any configs via APP_INITIALIZER are available - this results in undefined values when applied to the config object given to the forRoot() Method.
But we need a token in the http header. So I used a http interceptor for the attaching of the token like described here. The trick is to setup the OAuthModuleConfig in the factory. Obviously this is called after the app is initialized.
Configure Module
#NgModule({
imports: [
// no config here
OAuthModule.forRoot(),
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useFactory: authenticationInterceptorFactory,
deps: [OAuthStorage, AuthenticationErrorHandler, OAuthModuleConfig],
multi: true
}
]
})
Factory for interceptor
const authenticationInterceptorFactory = (oAuthService: OAuthStorage, authenticationErrorHandler: AuthenticationErrorHandler, oAuthModuleConfig: OAuthModuleConfig) => {
const config = {
resourceServer: {
allowedUrls: [
// Include config settings here instead
AppConfigService.settings.apiURL,
AppConfigService.settings.anotherApiURL,
]
sendAccessToken: true
},
}
return new AuthenticationInterceptor(oAuthService, authenticationErrorHandler, config);
};
I have created a library angular-runtime-config for runtime configuration loading for Angular.
Simple usage example
Your custom Configuration class:
export class Configuration {
readonly apiUrl!: string; // only example
readonly apiKey?: string; // only example
// add some other configuration parameters
}
Registering angular-runtime-config module with declaring which configuration files to load. For example, you can determine it by application URL or you can even use Angular injector in the factory or make the factory asynchronous.
import { AngularRuntimeConfigModule } from 'angular-runtime-config';
#NgModule({
...
imports: [
...
AngularRuntimeConfigModule.forRoot(Configuration, {
urlFactory: () => [ 'config/config.common.json', 'config/config.DEV.json' ]
})
],
}
Then request your Configuration class in any injection context.
#Injectable({...})
export class SomeService {
constructor(private readonly config: Configuration) {}
}
Related
I am trying to implement a global module in nest.js
I have created a service like below
export interface ConfigData {
DB_NAME: string;
}
#Injectable()
export class ConfigManager {
private static _instance?: ConfigManager;
public static configData:ConfigData | null;
private constructor() {
console.log(ConfigManager)
if (ConfigManager._instance)
ConfigManager._instance = this;
else{
ConfigManager._instance = new ConfigManager()
ConfigManager.configData = <ConfigData|null><unknown>ConfigManager.fetchSecretData()
}
}
private static async fetchSecretData():Promise<ConfigData|null>{
// some data from db
}
// static get instance() {
// return ConfigManager._instance ?? (ConfigManager._instance = new ConfigManager());
// //return ConfigManager._instance ?? (ConfigManager._instance = ConfigManager.fetchSecretData()) //new ConfigManager());
// }
}
configuration.module.ts
#Global()
#Module({
providers: [ConfigManager],
exports: [ConfigManager],
})
export class ConfigurationModule {}
and in app.module.ts added ConfigurationModule in imports.
Also adding private constructor on service unable it to add in module.ts file.
I am expecting that I should be able to configData anywhere without importing the ConfigManager. but it's not working...
ConfigManager is not available without import.
You've only marked your module with #Global decorator, but the NestJS needs to somehow initialize that module and make it globally available.
What this means is that you have to add this module to your core application module and NestJS will do the rest for you, so something like this (or however your root module is named):
#Module({
imports: [ConfigurationModule],
})
export class AppModule {}
From the documentation
The #Global() decorator makes the module global-scoped. Global modules
should be registered only once, generally by the root or core module.
Global modules in nest only means you don't have to include that module in the imports: [] of every other module that requires it's providers. The providers in the global module still behaves as as normal providers i.e. you need to inject it where you need it.
So in your case since you have already added #Global() to ConfigManager and imported ConfigurationModule in app.module.ts, you will not need to add ConfigurationModule in imports for any other modules who want to use ConfigManager. You would, however, still need to inject the ConfigManager provider - that's what #Injectable() means :).
To do the injection, you need a constructor(private configManager: ConfigManager) {} in the class that needs to use ConfigManager, and since you need access to the type of the class, you'll need import { ConfigManager } from '../path/to/ConfigManager' as well.
this is 'per-design' of es6/ts: you cant use the class without importing it.
you are mixing the concepts of di (instantiation/composition) with importing (defining which classes are available in the module scope)
I have two Angular projects using these versions:
9.0.0-next.6
8.1.0
In the version 9 I used this to provide and inject the window obhject:
#NgModule({
providers: [
{
provide: Window,
useValue: window
},
]
})
export class TestComponent implements OnInit {
constructor(#Inject(Window) private window: Window)
}
Which works fine.
Taking this approach to version 8 throwed warnings and errors during compilation:
Warning: Can't resolve all parameters for TestComponent …
I solved it by using single quotes, like this:
#NgModule({
providers: [
{
provide: 'Window',
useValue: window
},
]
})
export class TestComponent implements OnInit {
constructor(#Inject('Window') private window: Window)
}
What is the difference between both version?
What is the difference in Angular 8 and 9 that causes this thing?
In order for your app to work with Server Side Rendering I suggest you not only use window through token, but also create this token in SSR friendly manner, without referencing window at all. Angular has built-in DOCUMENT token for accessing document. Here's what I came up with for my projects to use window through tokens:
import {DOCUMENT} from '#angular/common';
import {inject, InjectionToken} from '#angular/core';
export const WINDOW = new InjectionToken<Window>(
'An abstraction over global window object',
{
factory: () => {
const {defaultView} = inject(DOCUMENT);
if (!defaultView) {
throw new Error('Window is not available');
}
return defaultView;
},
},
);
Edit: Since this is something people often need, we've used this technique to create a tiny open-source library with injection tokens for global objects, so you can use it:
https://github.com/ng-web-apis/common
It has a sister library for mocks to be used with SSR in Angular Universal:
https://github.com/ng-web-apis/universal
Overall, check out our hub for native APIs in Angular:
https://ng-web-apis.github.io/
Considering the ValueProvider interface:
export declare interface ValueProvider extends ValueSansProvider {
/**
* An injection token. Typically an instance of `Type` or `InjectionToken`, but can be `any`.
*/
provide: any;
/**
* When true, injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*/
multi?: boolean;
}
The provide property is of type any. That means any object (included the Window constructor) can go inside it. The object actually doesn't matter, only the reference matters to identify which provider should be used to inject a parameter in a constructor.
It should not be considered as a good practice to use the native Window constructor as an injection token. It fails at compile time because Window exists at run time in a browser environment, it also exists as a TypeScript declare but the Angular 8 compiler cannot do static code analysis to correlate the Window in the providers and the Window in a constructor's parameters, since the assignment of Window is done by the browser, not by the code. Not sure why it works in Angular 9, though...
You should create your own injection token that represents the dependency provider. This injection token should be either:
A dedicated string (like you did with 'Window')
A dedicated InjectionToken. For example export const window = new InjectionToken<Window>('window');
Moreover, the Angular code should be platform agnostic (should be executable in a browser and on a Node.js server as well) so it would be better to use a factory that returns window or undefined/null, then handle the undefined/null case in the components.
Is it possible to use JSON Schema faker as a third party dependency in Angular. I tried to use the dependency injection to Angular however in the providers I am not able to import jsonSchemaFaker.
angular.json
"scripts": [
"./node_modules/json-schema-faker/dist/json-schema-faker.bundle.min.js"
]
jsonSchemaFaker.service.ts
import { InjectionToken } from '#angular/core';
export const JSF_Token = new InjectionToken ('jsonSchemaFaker');
app.module.ts
providers: [
{ provide: JSF_Token, useValue: jsf }
]
...
declare let jsf: any;
This is what I tried to Inject json schema faker as a dependency in my angular app.. I am getting .. Uncaught ReferenceError: jsf is not defined
That is not how you use npm packages in an angular application.
First off, navigate to the directory of your package.json in your application and install the package:
npm install json-schema-faker
Then, inside your components or your services, use it as such:
// at the top of your file, next to other imports
import jsf from 'json-schema-faker';
// json-schema-faker is now available in the rest of your file as jsf
// you can, for example, have a service method that returns that:
jsf.generate({type: 'string'})
I had to change my providers and the declaration to
providers: [
{ provide: JSF_Token, useValue: JSONSchemaFaker }
]
declare let JSONSchemaFaker: any;
Reason: the global name for JSON Schema Faker mentioned in that library is "JSONSchemaFaker". It was a mistake on my part to declare it as jsf.
I'm working on internationalisation of an Angular app where I need to dynamically detect the locale that the user has actively selected for the app (the user must actively switch locale for the site to be displayed in their preferred language).
In my root module I specify the following provider for TRANSLATIONS, where the required xlf file (the translations file) is determined dynamically using a factory that depends on the value of my LOCALE_ID:
app.module.ts
... imports go here
export function selectedLocaleFactory(): string {
return LocaleService.getCurrentLanguage();
}
providers:
[
LocaleService,
{
provide: TRANSLATIONS,
useFactory: (locale) => {
locale = locale || 'en'; // default to english if no locale provided
return require(`raw-loader!../locale/messages.${locale}.xlf`);
},
deps: [LOCALE_ID]
},
{
provide: LOCALE_ID,
useFactory: selectedLocaleFactory
}
]
As you can see, in the TRANSLATIONS provider, I'm determining the translation file to use based on the value of LOCALE_ID.
LOCALE_ID is determined by using another factory (selectedLocaleFactory), which simply tries to return a LOCALE_ID value using a method getCurrentLanguage from the LocalService. I'm not sure this is the correct/best way to get a value from my service - I mean selectedLocaleFactory isn't really acting as a true 'factory', and services are designed to be injected.
In any case, I get this compile error in app.module.ts:
Property 'getCurrentLanguage' does not exist on type 'typeof LocaleService'.
Here's an extract from my LocalService:
#Injectable()
export class LocaleService {
private currLanguage: string;
constructor( private router: Router ) {
// this.currLanguage is set here - based on the URL being accessed by the user
}
getCurrentLanguage() {
return this.currLanguage;
}
If I make getCurrentLanguage a static method inside the LocaleService it's still inaccessible:
static getCurrentLanguage() {
return this.currLanguage;
}
UPDATE:
I realised that I need to instantiate the service to access the appropriate method:
export function selectedLocaleFactory(): string {
var localeService = new LocaleService(null);
return localeService.getCurrentLanguage();
}
My LocaleService has a Router dependency, so I just set this to null. It still feels though like I'm trying to do this the wrong way. My LocaleService should really be a singleton and I'm concerned I may end up with issues using my current approach (for example issues with shadowing). Is there a more 'correct' I can access a property from my service inside my root module?
The answer by #Thierry Templier here - What is the best way to declare a global variable in Angular 2 / Typescript suggests bootstrapping the service to get access to one of its variables, however this approach gives me an error saying I can't use the service as an entry point.
Currently I'm working on a project which is being hosted on a clients server. For new 'modules' there is no intention to recompile the entire application. That said, the client wants to update the router/lazy loaded modules in runtime. I've tried several things out but I can't get it to work. I was wondering if any of you knows what I could still try or what I missed.
One thing I noticed, most of the resources I tried, using angular cli, are being bundled into seperate chunks by webpack by default when building the application. Which seems logical as it makes use of the webpack code splitting. but what if the module is not known yet at compile time (but a compiled module is stored somewhere on a server)? The bundling does not work because it can't find the module to import. And Using SystemJS will load up UMD modules whenever found on the system, but are also bundled in a seperate chunk by webpack.
Some resources I already tried;
dynamic-remote-component-loader
module-loading
Loading modules from different server at runtime
How to load dynamic external components into Angular application
Implementing a plugin architecture / plugin system / pluggable framework in Angular 2, 4, 5, 6
Angular 5 - load modules (that are not known at compile time) dynamically at run-time
https://medium.com/#nikolasleblanc/building-an-angular-4-component-library-with-the-angular-cli-and-ng-packagr-53b2ade0701e
Some several other relating this topic.
Some code I already tried and implement, but not working at this time;
Extending router with normal module.ts file
this.router.config.push({
path: "external",
loadChildren: () =>
System.import("./module/external.module").then(
module => module["ExternalModule"],
() => {
throw { loadChunkError: true };
}
)
});
Normal SystemJS Import of UMD bundle
System.import("./external/bundles/external.umd.js").then(modules => {
console.log(modules);
this.compiler.compileModuleAndAllComponentsAsync(modules['External'])
.then(compiled => {
const m = compiled.ngModuleFactory.create(this.injector);
const factory = compiled.componentFactories[0];
const cmp = factory.create(this.injector, [], null, m);
});
});
Import external module, not working with webpack (afaik)
const url = 'https://gist.githubusercontent.com/dianadujing/a7bbbf191349182e1d459286dba0282f/raw/c23281f8c5fabb10ab9d144489316919e4233d11/app.module.ts';
const importer = (url:any) => Observable.fromPromise(System.import(url));
console.log('importer:', importer);
importer(url)
.subscribe((modules) => {
console.log('modules:', modules, modules['AppModule']);
this.cfr = this.compiler
.compileModuleAndAllComponentsSync(modules['AppModule']);
console.log(this.cfr,',', this.cfr.componentFactories[0]);
this.external.createComponent(this.cfr.componentFactories[0], 0);
});
Use SystemJsNgModuleLoader
this.loader.load('app/lazy/lazy.module#LazyModule')
.then((moduleFactory: NgModuleFactory<any>) => {
console.log(moduleFactory);
const entryComponent = (<any>moduleFactory.moduleType).entry;
const moduleRef = moduleFactory.create(this.injector);
const compFactory = moduleRef.componentFactoryResolver
.resolveComponentFactory(entryComponent);
});
Tried loading a module made with rollup
this.http.get(`./myplugin/${metadataFileName}`)
.map(res => res.json())
.map((metadata: PluginMetadata) => {
// create the element to load in the module and factories
const script = document.createElement('script');
script.src = `./myplugin/${factoryFileName}`;
script.onload = () => {
//rollup builds the bundle so it's attached to the window
//object when loaded in
const moduleFactory: NgModuleFactory<any> =
window[metadata.name][metadata.moduleName + factorySuffix];
const moduleRef = moduleFactory.create(this.injector);
//use the entry point token to grab the component type that
//we should be rendering
const compType = moduleRef.injector.get(pluginEntryPointToken);
const compFactory = moduleRef.componentFactoryResolver
.resolveComponentFactory(compType);
// Works perfectly in debug, but when building for production it
// returns an error 'cannot find name Component of undefined'
// Not getting it to work with the router module.
}
document.head.appendChild(script);
}).subscribe();
Example with SystemJsNgModuleLoader only works when the Module is already provided as 'lazy' route in the RouterModule of the app (which turns it into a chunk when built with webpack)
I found a lot of discussion about this topic on StackOverflow here and there and provided solutions seem really good of loading modules/components dynamically if known up front. but none is fitting for our use case of the project. Please let me know what I can still try or dive into.
Thanks!
EDIT: I've found; https://github.com/kirjs/angular-dynamic-module-loading and will give this a try.
UPDATE: I've created a repository with an example of loading modules dynamically using SystemJS (and using Angular 6); https://github.com/lmeijdam/angular-umd-dynamic-example
I was facing the same problem. As far as I understand it until now:
Webpack puts all resources in a bundle and replaces all System.import with __webpack_require__. Therefore, if you want to load a module dynamically at runtime by using SystemJsNgModuleLoader, the loader will search for the module in the bundle. If the module does not exist in the bundle, you will get an error. Webpack is not going to ask the server for that module. This is a problem for us, since we want to load a module that we do not know at build/compile time.
What we need is loader that will load a module for us at runtime (lazy and dynamic). In my example, I am using SystemJS and Angular 6 / CLI.
Install SystemJS: npm install systemjs –save
Add it to angular.json: "scripts": [ "node_modules/systemjs/dist/system.src.js"]
app.component.ts
import { Compiler, Component, Injector, ViewChild, ViewContainerRef } from '#angular/core';
import * as AngularCommon from '#angular/common';
import * as AngularCore from '#angular/core';
declare var SystemJS;
#Component({
selector: 'app-root',
template: '<button (click)="load()">Load</button><ng-container #vc></ng-container>'
})
export class AppComponent {
#ViewChild('vc', {read: ViewContainerRef}) vc;
constructor(private compiler: Compiler,
private injector: Injector) {
}
load() {
// register the modules that we already loaded so that no HTTP request is made
// in my case, the modules are already available in my bundle (bundled by webpack)
SystemJS.set('#angular/core', SystemJS.newModule(AngularCore));
SystemJS.set('#angular/common', SystemJS.newModule(AngularCommon));
// now, import the new module
SystemJS.import('my-dynamic.component.js').then((module) => {
this.compiler.compileModuleAndAllComponentsAsync(module.default)
.then((compiled) => {
let moduleRef = compiled.ngModuleFactory.create(this.injector);
let factory = compiled.componentFactories[0];
if (factory) {
let component = this.vc.createComponent(factory);
let instance = component.instance;
}
});
});
}
}
my-dynamic.component.ts
import { NgModule, Component } from '#angular/core';
import { CommonModule } from '#angular/common';
import { Other } from './other';
#Component({
selector: 'my-dynamic-component',
template: '<h1>Dynamic component</h1><button (click)="LoadMore()">LoadMore</button>'
})
export class MyDynamicComponent {
LoadMore() {
let other = new Other();
other.hello();
}
}
#NgModule({
declarations: [MyDynamicComponent],
imports: [CommonModule],
})
export default class MyDynamicModule {}
other.component.ts
export class Other {
hello() {
console.log("hello");
}
}
As you can see, we can tell SystemJS what modules already exist in our bundle. So we do not need to load them again (SystemJS.set). All other modules that we import in our my-dynamic-component (in this example other) will be requested from the server at runtime.
I've used the https://github.com/kirjs/angular-dynamic-module-loading solution with Angular 6's library support to create an application I shared on Github. Due to company policy it needed to be taken offline. As soon as discussions are over regarding the example project source I will share it on Github!
UPDATE: repo can be found ; https://github.com/lmeijdam/angular-umd-dynamic-example
I have tested in Angular 6, below solution works for dynamically loading a module from an external package or an internal module.
1. If you want to dynamically load a module from a library project or a package:
I have a library project "admin" (or you can use a package) and an application project "app".
In my "admin" library project, I have AdminModule and AdminRoutingModule.
In my "app" project:
a. Make change in tsconfig.app.json:
"compilerOptions": {
"module": "esNext",
},
b. In app-routing.module.ts:
const routes: Routes = [
{
path: 'admin',
loadChildren: async () => {
const a = await import('admin')
return a['AdminModule'];
}
},
{
path: '',
redirectTo: '',
pathMatch: 'full'
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
2. if you want to load a module from the same project.
There are 4 different options:
a. In app-routing.module.ts:
const routes: Routes = [
{
path: 'example',
/* Options 1: Use component */
// component: ExampleComponent, // Load router from component
/* Options 2: Use Angular default lazy load syntax */
loadChildren: './example/example.module#ExampleModule', // lazy load router from module
/* Options 3: Use Module */
// loadChildren: () => ExampleModule, // load router from module
/* Options 4: Use esNext, you need to change tsconfig.app.json */
/*
loadChildren: async () => {
const a = await import('./example/example.module')
return a['ExampleModule'];
}
*/
},
{
path: '',
redirectTo: '',
pathMatch: 'full'
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
``
Do it with angular 6 library and rollup do the trick. I've just experiment with it and i can share standalone angular AOT module with the main app without rebuild last.
In angular library set angularCompilerOptions.skipTemplateCodegen to false and after build library you will get module factory.
After that build an umd module with rollup like this:
rollup dist/plugin/esm2015/lib/plugin.module.ngfactory.js --file src/assets/plugin.module.umd.js --format umd --name plugin
Load text source umd bundle in main app and eval it with module context
Now you can access to ModuleFactory from exports object
Here https://github.com/iwnow/angular-plugin-example you can find how develop plugin with standalone building and AOT
I believe this is possible using SystemJS to load a UMD bundle if you build and run your main application using webpack. I used a solution that uses ng-packagr to build a UMD bundle of the dynamic plugin/addon module. This github demonstrates the procedure described:
https://github.com/nmarra/dynamic-module-loading
Yes, you can lazy load modules using by referring them as modules in the router. Here is an example https://github.com/start-angular/SB-Admin-BS4-Angular-6
First couple all the components that you are using into a single module
Now refer that module in the router and angular will lazy load your module into the view.