I tried to implement dynamic configuration as can be seen in this post.
Everything works in JiT compiler, but I get
ERROR in Error during template compile of 'environment'
Function calls are not supported in decorators but 'Environment' was called.
when trying to build with the AoT compiler.
This is my environment.ts (note class Environment is exported):
export class Environment extends DynamicEnvironment {
public production: boolean;
constructor() {
super();
this.production = false;
}
}
export const environment = new Environment();
I would still like to use the environment in the standard way some.component.ts:
import { environment } from '../environments/environment';
console.log(environment.config.property);
Don't. Seriously, stay away from those two files (environment.ts and environment.prod.ts). Those are NOT about the DevOps meaning of the word "environment", they are about debug constants.
If you need to know if you're running a debug build, import isDevMode:
import { isDevMode } from '#angular/core';
If you need dynamic configuration, just read a Json from somewhere or have the server side inject it as a script tag, then read it directly or via Dependency Injection (it's not that hard to do).
But don't mess with those files. Trust me, you'll thank me later ;)
Solved this by creating config.module.ts and config.service.ts. Config module declares providers:
#NgModule({
providers: [
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: (appConfigService: ConfigService) => () => appConfigService.loadAppConfig(),
deps: [ConfigService],
multi: true,
},
],
})
export class ConfigModule {}
Usage of config service in some.component.ts:
#Component(...)
export class SomeComponent {
constructor(private configService: ConfigService) { }
private myMethod() {
console.log(this.configService.get.property);
}
}
For tests, json testing config file is imported:
import { default as appTestConfig } from '../../../../assets/app-config.test.json';
and set directly on config service:
TestBed.configureTestingModule({
...,
imports: [
ConfigModule,
...
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: (appConfigService: ConfigService) => () => appConfigService.setConfig(appTestConfig),
deps: [ConfigService],
multi: true,
},
]
}).compileComponents();
Related
I want to ensure a singleton service is created on application boot. I could add it as injection parameter to my AppComponent and not use it at all, but that looks a bit dirty. Right now I'm going with this solution:
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '#angular/core';
import { NavigationService } from './navigation.service';
#NgModule()
export class NavigationServiceModule {
public static forRoot(): ModuleWithProviders<NavigationServiceModule> {
return {
ngModule: NavigationServiceModule,
providers: [
{
provide: APP_INITIALIZER,
deps: [NavigationService],
multi: true,
useFactory: () => () => { }
}
]
}
}
}
But don't really love it, too. Any ideas how this could be achieved best?
Just use the default service setup and don't add it to the providers array - only one instance will be created (unless you explicitly provide it outside of constructors)
#Injectable({
providedIn: 'root'
})
export class NavigationService
After first problem with JWT_MODULE_OPTION, back to old problem who I thought I was fixed. It turned out that when I "fix" old problem create the new with JWT.
So again can't compile:
Nest can't resolve dependencies of the AuthService (?, RoleRepository, JwtService). Please make sure that the argument at index [0] is available in the AppModule context. +25ms
It's really strange, because this way work on another my project and can't understand where I'm wrong. Here is the auth.service.ts:
#Injectable()
export class AuthService {
constructor(
#InjectRepository(User) private readonly userRepo: Repository<User>,
#InjectRepository(Role) private readonly rolesRepo: Repository<Role>,
private readonly jwtService: JwtService,
) { }
It get role and jwtService but the problem is with User, the path is correct. Here is app.module.ts:
#Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule, AuthModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
type: configService.dbType as any,
host: configService.dbHost,
port: configService.dbPort,
username: configService.dbUsername,
password: configService.dbPassword,
database: configService.dbName,
entities: ['./src/data/entities/*.ts'],
}),
}),
],
controllers: [AppController, AuthController],
providers: [AuthService],
})
export class AppModule { }
Have the same compile error for controllers & providers & can't understand what is wrong...
You might be missing the TypeOrmModule.forFeature([User]) import. Typically, all entities are imported in dedicated feature modules. If you only have one module (i.e. AppModule) you need to put the forFeature import there in addition to the forRoot import.
#Module({
imports: [
TypeOrmModule.forRootAsync({...}),
TypeOrmModule.forFeature([User, Role]),
],
The global problem was that I try to add AuthService & AuthController twice. So I remove them from app.module.ts and just export AuthService from auth.module.ts:
Problem :
I have to render a dynamic html which generates at run-time as a string, so for that purpose I've used dynamic components approach as innerHTML just not cater angular attributues like *ngIf, formGroup, router-link etc, but getting following error after deploying on local server.
Error :
Error: Unexpected value 'function(){}' imported by the module 'function(){}'. Please add a #NgModule annotation.
I tried creating the dynamic module but was getting error when making aot build because it was inside a non lazy-loaded module and according to a solution I found that aot will not work for dynamic components generating in a lazy-loaded module.
Now when I have moved it in a non lazy-loaded module I am not getting any error locally when generating aot build but getting this error after deployment however deployment is successful and nothing is breaking there.
DYNAMIC MODULE
export function createCompiler(compilerFactory: CompilerFactory) {
return compilerFactory.createCompiler();
}
#NgModule({
declarations: [StepComponent],
imports: [
CommonModule
],
exports: [StepComponent],
entryComponents: [StepComponent],
providers: [
{ provide: COMPILER_OPTIONS, useValue: {}, multi: true },
{ provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
{ provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] }
]
})
export class DynamicStepModule { }
STEP COMPONENT
export class StepComponent implements OnInit, OnChanges {
#Input() step: Steps;
#ViewChild('dynamicComponent', { read: ViewContainerRef }) container: ViewContainerRef;
constructor(private compiler: Compiler) { }
ngOnInit() { }
ngOnChanges(change: SimpleChanges) {
if (this.step && change.step.currentValue !== change.step.previousValue) {
this.addComponent(this.step);
}
}
addComponent(step: Steps) {
const cmp = Component({ template: step.formattedText })(class DynamicComponent {
stepFormGroup: FormGroup;
constructor(
) {
// Some Component related code
}
});
const meta = NgModule({
imports: [
CommonModule,
FormsModule,
MaterialModule,
ReactiveFormsModule,
LibSharedModule,
BrowserModule,
BrowserAnimationsModule
],
declarations: [cmp]
})(class DynamicModule {
});
this.compiler
.compileModuleAndAllComponentsAsync(meta)
.then(factories => {
const factory = factories.componentFactories.find(component => component.componentType === cmp);
this.container.remove();
this.container.createComponent(factory);
});
}
}
Locally when generating aot build not getting error but after deploying on a local server getting this issue:
ERROR Error: Unexpected value 'function(){}' imported by the module 'function(){}'. Please add a #NgModule annotation.
I have a value that is from of a config file from static AppConfigService.
Described below:
reference code/article: https://blogs.msdn.microsoft.com/premier_developer/2018/03/01/angular-how-to-editable-config-files/
import { Injectable } from '#angular/core';
import { AppConfig } from './app-config';
import { HttpClient } from '#angular/common/http';
import { environment } from 'src/environments/environment';
#Injectable()
export class AppConfigService {
static settings: AppConfig;
constructor(private http: HttpClient) { }
load() {
console.log('is this getting fired before routing module check?');
const jsonFile = `assets/config/config.${environment.name}.json`;
return new Promise<void>((resolve, reject) => {
this.http.get(jsonFile)
.toPromise()
.then((response: AppConfig) => {
AppConfigService.settings = <AppConfig>response;
console.log(AppConfigService.settings);
resolve();
})
.catch((response: any) => {
reject(`Could not load file '${jsonFile}':
${JSON.stringify(response)}`);
});
});
}
}
This config gets loaded in my APP_INITIALIZER in the app.module.ts
providers: [
AppConfigService,
{
provide: APP_INITIALIZER,
useFactory: (appConfigService: AppConfigService) => () => {appConfigService.load() },
deps: [AppConfigService], multi: true
}
],
but my routing module, named AppRoutingModule is reading something out of my AppConfigService.settings variable which is crazy enough, UNDEFINED. My application crashes. I expect the APP_INITIALIZER to fire BEFORE AppRoutingModule but this is not the case:
Uncaught TypeError: Cannot read property 'oldUrl' of undefined
oldUrl is a property of AppConfigService.settings. I checked if AppConfigService.settings is set, it IS, properly AFTER routing module is fired but this is not what I want.
I checked some other sources for help. I used the following already as maybe a fix: https://github.com/angular/angular/issues/14615 and https://github.com/angular/angular/issues/14588
#component({})
class App {
constructor(router: Router, loginService: LoginService) {
loginService.initialize();
router.initialNavigation();
}
}
#NgModule({
imports: [
BrowserModule,
RouterModule.forRoot(routes, {initialNavigation: false})
],
declarations: [ App ],
bootstrap: [ App ],
providers: [ Guard, LoginService ]
})
export class AppModule {
}
Unfortunately, the above solution is not fixing my problem. I also tried to put in AppModule but alas, that didn't help either.
Any help is very welcome.
I've solved my App Initialization and Routing with NgRx listening the central state to know when the system is Loaded and activating the route Guards after that.
But for a direct solution, you need to add a Route Guard checking when your service is loaded. So, add a loaded: boolean flag in your Service, and check it from a Guard like this:
https://github.com/angular/angular/issues/14615#issuecomment-352993695
This is better handled with Observables tho, and I'm wiring all with NgRx in my Apps using Facades to facilitate everything:
https://gist.github.com/ThomasBurleson/38d067abad03b56f1c9caf28ff0f4ebd
Best regards.
I have an angular 4 app that is broken down into different feature. So I have a shopping feature with all its components and billing feature with all its component.
However, I have an issue with configuration. I would like to share configuration between multiple feature of the app. My configuration is different for each feature so I created a base config class and inherit from it.
However in my app.module is did a registration such as below.
{ provide: BillingModuleConfig, useClass: ConfigService },
{ provide: ShoppingConfig, useClass: ConfigService }
{
provide: APP_INITIALIZER,
useFactory: configServiceFactory,
deps: [ShoppingConfig],
multi: true
}
However, this does not work for the BillingModuleConfig. Which implies when I inject BIllingConfig in the section I need it from I cannot get the config setting. Is there a reason why this would not work? My ConfigServiceFactory is the an external function that load the config file as below.
export function configServiceFactory(config: ConfigService) {
return () => config.load();
}
The config.load method loads all the configuration into a class that inherits from both ShoppingConfig and BillingConfig.
The config.load implementation is
import { Injectable, OnInit } from '#angular/core';
import { Http } from '#angular/http';
import { BillingModuleConfig} from '#razor/Billing';
import { ShoppingConfig } from '#razor/shopping';
import { environment } from './../../environments/environment';
import { HttpClient } from '#angular/common/http';
abstract class Settings implements BillingModuleConfig, ShoppingConfig {
public ShoppingAPIUrl: string;
public BillingApiUrl: string;
}
#Injectable()
export class ConfigService implements Settings {
constructor(private http: HttpClient) {}
public load() {
return new Promise((resolve, reject) => {
this.http
.get<Settings>(environment.configFileName)
.subscribe((config: AppSettings) => {
this.ShoppingAPIUrl= config.ShoppingAPIUrl;
this.BillingApiUrl= config.BillingApiUrl;
resolve(true);
});
});
}
}
Considering that config.load assigns fetched configuration to ConfigService class instance, it will work only for provider token that was specified as a dependency for APP_INITIALIZER, which is ShoppingConfig.
In order for the configuration to be fetched in BillingModuleConfig, it should be specified, too:
{
provide: APP_INITIALIZER,
useFactory: configServiceFactory,
deps: [ShoppingConfig],
multi: true
},
{
provide: APP_INITIALIZER,
useFactory: configServiceFactory,
deps: [BillingModuleConfig],
multi: true
}
However, this will result in two requests. To avoid this, the most simple way is to use ConfigService everywhere. If BillingModuleConfig and ShoppingConfig were introduced because the configuration is currently combined but supposed to be divided later, they can reuse a common instance:
ConfigService,
{ provide: BillingModuleConfig, useExisting: ConfigService },
{ provide: ShoppingConfig, useExisting: ConfigService },
{
provide: APP_INITIALIZER,
useFactory: configServiceFactory,
deps: [ConfigService],
multi: true
}