Issue
I am developing an Angular application generated via Nx and using NgRx for state management. As the application scale, I am face with the issues of multiple API calls and I have no idea why.
Here is my structure:
app.module.ts
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
BrowserAnimationsModule,
HttpClientModule,
HammerModule,
// Firebase
AppFirebaseModule,
// Translation
AppTranslateModule,
// Main Root Store
StoreModule.forRoot({
[fromAuth.AUTH_FEATURE_KEY]: authReducers.reducer,
[fromProfile.profileFeatureKey]: profileReducers.reducer,
}),
EffectsModule.forRoot([AuthEffects, ProfileEffects]),
!environment.production ? StoreDevtoolsModule.instrument() : [],
StoreRouterConnectingModule.forRoot(),
// Service Worker
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production,
// Register the ServiceWorker as soon as the app is stable
// or after 30 seconds (whichever comes first).
registrationStrategy: 'registerWhenStable:30000',
}),
// App Routing Module
AppRoutingModule,
SsCommonModule,
NgxMaskModule.forRoot(),
RepositoryModule,
],
providers: [
EnvironmentService,
AppRoutesService,
CookieService,
AppRouteMaps,
{
provide: HTTP_INTERCEPTORS,
useClass: ApiInterceptor,
multi: true
}
],
bootstrap: [AppComponent],
})
export class AppModule {}
I put all API calls in the Repository module as follows:
repository.module.ts
@NgModule({
imports:[
],
providers:[
GirlHelper,
GirlRepository,
ProfileHelper,
ProfileRepository,
PhotoRepository,
ImageRepository,
ChatRepository,
UserRepository,
SettingsRepository,
NewsRepository,
EventRepository,
ServiceRepository
],
exports: [ ]
})
export class RepositoryModule {}
Each repository are exported as provided so I can use them anywhere. And each service/repository are formatted like these (just like a simple data service):
@Injectable()
export class ProfileRepository {
private api = this.env.get('apiUrl') + 'profile/';
constructor(
private env: EnvironmentService,
private http: HttpClient,
private helper: ProfileHelper) { }
getAll(filters: ProfileListFilters): Observable<Profile[]> {
return this.http.get<Profile[]>(this.api, {
params: { ...filters }
}).pipe(
map((data: any) => data?.result),
catchError((err) => this.onError(err))
);
}
}
I created feature modules for different features of my application and put them in their respective folders just like a normal feature module.
e.g. client/client.module.ts
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(routes),
SsCommonModule,
ClientStoreModule
],
declarations: [
ClientComponent
],
})
export class ClientModule {}
I also lazy load these feature modules, in the app-routing.module.ts:
const routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{
path: '',
canActivate: [CanActivateSync],
children: [
{
path: 'home',
loadChildren: () => import('@myapp/home').then((m) => m.HomeModule),
},
{
path: 'auth',
children: authRoutes,
},
{
path: 'client',
loadChildren: () =>
import('@myapp/client').then((module) => module.ClientModule),
},
{
path: 'admin',
canActivate: [CanActivateAdmin],
loadChildren: () =>
import('@myapp/admin').then((module) => module.AdminModule),
}
....
],
},
{
path: '**',
redirectTo: '/',
},
{
path: 'events',
loadChildren: () =>
import('@myapp/events').then((module) => module.EventsModule),
},
{
path: 'news',
loadChildren: () =>
import('@myapp/news').then((module) => module.NewsModule),
},
];
@NgModule({
imports: [
CommonModule,
RouterModule.forRoot(routes, { initialNavigation: 'enabled' }),
AuthModule,
],
exports: [RouterModule],
})
export class AppRoutingModule {}
On the other hand, my NgRx stores are inside these feature modules, inside +state folder and divided by their respective files (e.g. actions ,models, etc, usual ngrx stuffs) I also have a separate module for each feature state:
client/+state/client-store.module.ts
@NgModule({
imports: [
CommonModule,
StoreModule.forFeature(fromClient.clientFeatureKey, reducer),
EffectsModule.forFeature([ClientEffects])
],
providers: []
})
export class ClientStoreModule {}
These store modules are then imported in their respective feature module like the one below:
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(routes),
SsCommonModule,
ClientStoreModule
],
declarations: [
ClientComponent
],
})
export class ClientModule {}
The respective repository is injected on the effects of a feature module to trigger an API call:
@Injectable()
export class ClientEffects {
loadChatGroups$ = createEffect(() => this.actions$.pipe(
ofType(ChatActions.loadChatGroups),
mergeMap((action) => this.profileRepo.getChatGroups(action?.payload?.uid, action?.payload?.filters)),
mergeMap(chatGroups => of(ChatActions.loadChatGroupsSuccess({ payload: chatGroups }))),
catchError(error => of(ChatActions.loadChatGroupsFailure(error)))
));
loadChatGroupMessages$ = createEffect(() => this.actions$.pipe(
ofType(ChatActions.loadChatMessages),
mergeMap((action) => this.profileRepo.getChatGroupMessages(action?.payload?.uid, action?.payload?.chatGroupId, action?.payload?.filters)),
mergeMap(messages => of(ChatActions.loadChatMessagesSuccess({ payload: messages }))),
catchError(error => of(ChatActions.loadChatMessagesFailure(error)))
));
sendMessage$ = createEffect(() => this.actions$.pipe(
ofType(ChatActions.sendMessage),
mergeMap((action) => this.profileRepo.sendChatGroupMessage(
action?.payload?.uid,
action?.payload?.chatGroupId!,
action?.payload?.content,
action?.payload?.contentType
).pipe(
mergeMap(response => of(ChatActions.sendMessageSuccess({ payload: new ProfileChatMessage(action.payload) }))),
)),
catchError(error => of(ChatActions.sendMessageFailure(error)))
));
}
So the main problem is I am having multiple API calls and I don't know why, one example is just calling an API call when a button is click (the sendMessage function is binded in the (click) event in the HTML):
sendMessage(chatGroupId: number, ev: Event): void {
ev.preventDefault();
const message = this.messageFC.value;
if(message !== '') {
this.messageFC.setValue('');
this.store.dispatch(ChatActions.sendMessage({ payload: {
chatGroupId: chatGroupId,
content: message,
contentType: ProfileChatMessageType.Message,
uid: this.client?.id!
}}));
}
}
And now, even when I just call a direct http client call, there are multiple API calls:
sendMessage(chatGroupId: number, ev: Event): void {
ev.preventDefault();
const message = this.messageFC.value;
if(message !== '') {
this.messageFC.setValue('');
console.log('====MAIN START===');
this.http.post<boolean>(this.api + '/chats/send-message', {
chat_id: chatGroupId,
profile_id: this.client?.id,
content: message,
content_type: ProfileChatMessageType.Message,
}).subscribe();
}
I am developing angular application for so long now, and I haven't encountered this issue. And this was the first time to try NgRx because they said that it is beneficial for large application, and now my application is large enough but its the opposite.
Anyone encountered this issue? This should be a simple API call, and now I am not sure why its calling multiple API calls. Is it NgRx? Was it on how I structure the store modules? Should I just removed ngrx?
Any help is much appreciated, I am stucked right now and is near to ditch Ngrx. Please save me.
Thank you.
Solution
I've found the issue that is causing the multiple http requests.And Its not ng-rx, or any event that was dispatched by my app (thank god its not ng-rx, coz I love using it),in the end, it was the firebase.
"@angular/fire": "^7.0.4",
So, in short, I have firebase Auth to persists my currently logged in user, and I have an HTTP interceptor to get the Bearer token to pass on my http calls, my inteceptor looks like this:
export default class ApiInterceptor implements HttpInterceptor {
constructor(@Optional() private auth: Auth) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
if (!request.url.includes("api")) return next.handle(request);
return next.handle(request).pipe(
mergeMap(req => authState(this.auth).pipe(
shareReplay(),
mergeMap((user: any) => user ? from(user!.getIdToken()) : of(null)),
mergeMap(token => {
if(!token) return next.handle(request);
const headers = new HttpHeaders({
'Authorization': 'Bearer ' + token
});
const newRequest = request.clone({ headers });
return next.handle(newRequest);
})
))
);
}
}
The issue was this line:
authState(this.auth).pipe(...)
This causes my http calls to be triggered multiple times, and I don't know why (if anyone knows please comment back).
So what I did was to just get the current user and just call the getIdToken() and convert it as a Observable.
const user = this.auth.currentUser;
return from(user.getIdToken()).pipe(
mergeMap(token => {
if(!token) return next.handle(request);
const headers = new HttpHeaders({
'Authorization': 'Bearer ' + token
});
const newRequest = request.clone({ headers });
return next.handle(newRequest);
})
);
And now my http calls are now behaving as expected. Hurray!
Answered By - Sin Studio
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.