I created Angular 5 project and create one factory which should provide metadata from service before application start.
The problem is that I get error:
RangeError: Maximum call stack size exceeded
at resolveNgModuleDep (core.js:10559)
at _callFactory (core.js:10649)
at _createProviderInstance$1 (core.js:10599)
at resolveNgModuleDep (core.js:10581)
at _createClass (core.js:10622)
at _createProviderInstance$1 (core.js:10596)
at resolveNgModuleDep (core.js:10581)
at NgModuleRef_.get (core.js:11806)
at new MetadataService (metadata.service.ts:23)
at _createClass (core.js:10622)
App module:
#NgModule({
declarations: [
AppComponent,
...
],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule,
FormsModule,
FlexLayoutModule,
I18NextModule.forRoot()
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
MetadataService,
{ provide: APP_INITIALIZER, useFactory: MetadataProviderFactory, deps: [MetadataService], multi: true },
{ provide: APP_INITIALIZER, useFactory: I18NextProviderFactory, deps: [I18NEXT_SERVICE], multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule { }
Metadata service:
import { Injectable, Injector } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class MetadataService {
private http: HttpClient;
constructor(private injector: Injector) {
this.http = injector.get(HttpClient); // this row makes a problem
}
This row makes a problem.
this.http = injector.get(HttpClient);
I put this line avoid circular reference and I get "Maximum call stack size exceeded".
Where I am wrong ?
You can temporarily get over this by delaying your http call that you make on service init, with a simple setTimeOut (don't need any seconds). I've gone through a few open Angular threads and the main issue has to do with HttpInterceptor and circular depedenecy with the HttpClient.
Related
I am developing an Angular library where there is an authentication module that provides an HttpInterceptor. The main idea is to have this interceptor working automatically in any app that imports this authentication module without having to do any extra setup at it.
What I have so far is the following:
AuthenticationModule
#NgModule({
imports: [ConcreteAuthModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: BearerInterceptor,
multi: true
}
]
})
export class AuthenticationModule {
static forRoot(config: AuthConfig): ModuleWithProviders {
return {
ngModule: AuthenticationModule,
providers: [
{
provide: AUTH_CONFIG,
useValue: config
}
]
};
}
}
ConcreteAuthModule
#NgModule({
imports: [ThirdPartyLibModule],
providers: [
{
provide: AuthenticationService,
useClass: ConcreteAuthService
}
]
})
export class ConcreteAuthModule { }
BearerInterceptor
#Injectable()
export class BearerInterceptor implements HttpInterceptor {
constructor(private authService: AuthenticationService) { }
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const headers: any = {};
if (this.authService.isUserAuthenticated()) {
headers.Authorization = `Bearer ${this.singleSignOnService.getUserToken()}`;
}
const authReq = req.clone({ setHeaders: headers });
return next.handle(authReq);
}
}
And from a test Angular app I am importing this module the following way at the AppModule:
#NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule,
AuthenticationModule.forRoot({ /* config stuff */ })
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
I checked how some third party libraries did this, also came across a couple of Stack Overflow questions that discussed about this and they all suggested having an Angular module created (I already have it: AuthenticationModule), then provide the http-interceptor on it (already have it too) and finally importing this module from an Angular app (also did this).
But still, none of the http requests in my app are being intercepted.
Tried importing the BearerInterceptor directly from my test app and providing it on the AppModule like this:
import { BearerInterceptor } from 'my-lib':
#NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule,
AuthenticationModule.forRoot({ /* config stuff */ })
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: BearerInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {}
And that works! But this workaround is not what I am looking for...
You're very close to a working solution.
The key is to look at how the module is being imported by the AppModule.
imports: [
BrowserModule,
HttpClientModule,
AuthenticationModule.forRoot({ /* config stuff */ })
],
That is how the AuthenticationModule is imported by the AppModule, but that NgModule does not provide the HTTP_INTERCEPTORS.
You've provided the token in the #NgModule() decorator, but that module is not being used by your application. It's the module defined by the forRoot() function.
Move the declaration of the HTTP_INTERCEPTORS to the forRoot() function.
Try this instead:
#NgModule({
imports: [ConcreteAuthModule]
})
export class AuthenticationModule {
static forRoot(config: AuthConfig): ModuleWithProviders {
return {
ngModule: AuthenticationModule,
providers: [
{
provide: AUTH_CONFIG,
useValue: config
}, {
provide: HTTP_INTERCEPTORS,
useClass: BearerInterceptor,
multi: true
}
]
};
}
}
The problem was because I was installing the library locally for testing purposes like the following:
$ npm i --save my-lib_path/dist/my-lib
After I published it and installed it from the npm registry it worked fine:
$ npm i --save my-lib
"Error: No provider for TranslateStore!" while UI Unit testing translation service in Angular 4.
After I run ng test command I get the test cases failed error along with the above error message.
Try this:
import {TranslateFakeLoader,TranslateLoader,TranslateModule,TranslateService } from '#ngx-translate/core';
TestBed.configureTestingModule({
imports: [
...
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateFakeLoader
}
})
],
...
providers: [
TranslateService
]
Importing & adding TranslateStore to the providers in my lazy loaded module resolved issue for me.
import { TranslateModule,TranslateService,TranslateStore } from '#ngx-translate/core';
export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
#NgModule({
declarations: [...],
imports: [
TranslateModule.forChild(
{
loader: {
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [HttpClient]
}
})],
providers:[TranslateStore ]
})
Check the issue link below for more information
https://github.com/ngx-translate/core/issues/883#issuecomment-502037966
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
}
I have the following two environments in my angular-cli (v1.5.1, angular v5) application:
dev
prod
Dev makes use of mock data, which I provide with an http-interceptor.
Pro makes use of a live rest api.
How do I provide the http-interceptor on dev, but not on pro?
I already tried the following, but it doesn't work:
{
provide: HTTP_INTERCEPTORS,
useFactory: () => {
if (environment.useMockBackend === true) {
return MockHttpInterceptor;
}
return false;
},
multi: true
}
In my Angular 5.2 project I used following approach.
app.module.ts
import { HttpClientModule, HTTP_INTERCEPTORS } from '#angular/common/http';
import { environment } from '../environments/environment';
import { MyInterceptor } from './my.interceptor';
const commonProviders = [/*...*/];
const nonProductionProviders = [{
provide: HTTP_INTERCEPTORS,
useClass: MyInterceptor,
multi: true
}];
#NgModule({
imports: [
HttpClientModule,
// ...
],
providers: [
...commonProviders,
...!environment.production ? nonProductionProviders : []
]
})
my.interceptor.ts
import { Injectable } from '#angular/core';
import { HttpEvent, HttpRequest, HttpInterceptor, HttpHandler } from '#angular/common/http';
import { Observable } from 'rxjs';
#Injectable()
export class MyInterceptor implements HttpInterceptor {
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
// ...
return next.handle(req);
}
}
I've come up with the following approach (this is in Angular 7), by drawing on the previous answers from #dhilt and #kemsky:
Your dev environment file
import { HTTP_INTERCEPTORS } from '#angular/common/http';
import { MyDevInterceptor} from './my-dev.interceptor';
export const ENVIRONMENT_SPECIFIC_PROVIDERS = [
{ provide: HTTP_INTERCEPTORS, useClass: MyDevInterceptor, multi: true }
];
environment.prod.ts
export const ENVIRONMENT_SPECIFIC_PROVIDERS = [];
app.module.ts
#NgModule({
declarations: [],
imports: [
HttpClientModule
],
providers: [
ENVIRONMENT_SPECIFIC_PROVIDERS
]
})
It's simple, it works a treat, and it means that your code base contains no references to anything that's not required by your environment.
The idea is to export interceptor providers from environment file, prod environment exports do-nothing interceptor or just any other dummy provider (lets name it DefaultHttpInterceptor) and dev exports MockHttpInterceptor.
dev environment: export const INTERCEPTORS = {provide: HTTP_INTERCEPTORS, ... MockHttpInterceptor}
prod environment: export const INTERCEPTORS = {provide: HTTP_INTERCEPTORS, ... DefaultHttpInterceptor}
Then you can use it like usual:
import { INTERCEPTORS } from './../environments/environment';
#NgModule({
providers : [
...
INTERCEPTORS
...
]
...
})
I currently have a module setup like below (exerpt);
AppModule
RoutingModule
AuthRouteGuard
AuthModule
LoginFormComponent
AuthService
I have defined my AuthService (responsible for handling user authentication and provides a method for determining whether the current user is authenticated) as a provider in my AuthModule;
// auth.module.ts - uses https://github.com/auth0/angular2-jwt
export function authHttpServiceFactory(http: Http, options: RequestOptions) {
return new AuthHttp(new AuthConfig({
tokenName: jwtLocalStorageKey
}), http, options);
}
export let authHttpServiceProvider = {
provide: AuthHttp,
useFactory: authHttpServiceFactory,
deps: [Http, RequestOptions]
};
#NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule
],
exports: [AuthComponent],
declarations: [AuthComponent, LoginComponent, RegisterComponent],
providers: [
AuthService,
authHttpServiceProvider
]
})
export class AuthModule { }
I can use this service with no problem within its sibling LoginFormComponent. When I attempt to use the AuthService within the AuthRouteGuard class in the RoutingModule however I get the following error;
Error: Invalid provider for the NgModule 'AuthModule' - only instances of Provider and Type are allowed, got: [?undefined?, ...]
I have the AuthModule imported within the RoutingModule. The error above occurs as soon as the AuthService is defined as a dependency for the AuthRouteGuard;
export class AuthRouteGuard implements CanActivate {
constructor(
private router: Router,
private authService: AuthService // Removing this injection removes the error
) {}
canActivate() {
// #todo: if not authenticated
this.router.navigate(['/login']);
return true;
}
}
What am I missing here, and why would injecting the service in the constructor cause an invalid provider error that does not occur when that injection is removed?
Edit - Same error occurs if the authHttpServiceProvider provider is removed altogether, so the AuthModule module looks like;
#NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule
],
exports: [AuthComponent],
declarations: [AuthComponent, LoginComponent, RegisterComponent],
providers: [
AuthService
]
})
export class AuthModule { }
Add authHttpServiceProvider to imports of the module. It's exported to global and not available to module. So you can't provide the service because you have unknown provider to the module.
#NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule
],
exports: [AuthComponent],
declarations: [AuthComponent, LoginComponent, RegisterComponent],
providers: [
AuthService
]
})
export class AuthModule {
The actual problem was within the AuthService itself.
AuthModule defined a constant;
export const jwtKey = 'jwt';
Which was being imported into the AuthService and used;
import { jwtKey } from '../auth.module';
For some reason if I remove this import everything works fine.