Angular + NgRx;多次 API 调用的问题
Angular + NgRx; Issues with Multiple API calls
我正在开发一个 Angular 应用程序,该应用程序通过 Nx 生成并使用 NgRx 进行状态管理。
随着应用程序的规模,我面临着多次 API 调用的问题,我不知道为什么。
这是我的结构:
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 {}
我把所有API调用都放在了Repository模块中,如下:
repository.module.ts
@NgModule({
imports:[
],
providers:[
GirlHelper,
GirlRepository,
ProfileHelper,
ProfileRepository,
PhotoRepository,
ImageRepository,
ChatRepository,
UserRepository,
SettingsRepository,
NewsRepository,
EventRepository,
ServiceRepository
],
exports: [ ]
})
export class RepositoryModule {}
每个存储库都按提供的方式导出,因此我可以在任何地方使用它们。每个 service/repository 的格式如下(就像一个简单的数据服务):
@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))
);
}
}
我为我的应用程序的不同功能创建了功能模块,并将它们放在各自的文件夹中,就像普通功能模块一样。
例如client/client.module.ts
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(routes),
SsCommonModule,
ClientStoreModule
],
declarations: [
ClientComponent
],
})
export class ClientModule {}
我也在应用程序路由中延迟加载这些功能模块。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 {}
另一方面,我的 NgRx 存储在这些功能模块中,在 +state 文件夹中并按它们各自的文件划分(例如动作、模型等,通常的 ngrx 东西)
我还有一个单独的模块用于每个功能状态:
客户端/+state/client-store.module.ts
@NgModule({
imports: [
CommonModule,
StoreModule.forFeature(fromClient.clientFeatureKey, reducer),
EffectsModule.forFeature([ClientEffects])
],
providers: []
})
export class ClientStoreModule {}
然后将这些商店模块导入到它们各自的功能模块中,如下所示:
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(routes),
SsCommonModule,
ClientStoreModule
],
declarations: [
ClientComponent
],
})
export class ClientModule {}
相应的存储库被注入到功能模块的效果上以触发 API 调用:
@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)))
));
}
所以主要问题是我有多个 API 调用,我不知道为什么,一个例子只是在单击按钮时调用 API 调用(sendMessage 函数是在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!
}}));
}
}
现在,即使我只是直接调用 http 客户端调用,也会有多个 API 调用:
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();
}
我开发angular应用这么久了,还没遇到过这个问题。这是第一次尝试NgRx,因为他们说它对大型应用程序有益,现在我的应用程序足够大但恰恰相反。
有人遇到过这个问题吗?这应该是一个简单的 API 调用,现在我不确定为什么它会调用多个 API 调用。
是 NgRx 吗?是关于我如何构建商店模块吗?我应该删除 ngrx 吗?
非常感谢任何帮助,我现在被困住了,快要放弃 Ngrx 了。请救救我。
谢谢。
一些事情...
确保你的行为types/descriptions是独一无二的。已经说不出多少次了。
您将 catchError 放置在 Effect 的错误位置,并且正在调用不必要的 mergeMap
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)))
));
试试这个:
sendMessage$ = createEffect( () => this.actions$.pipe(
ofType( ChatActions.sendMessage ),
mergeMap( ( { payload } ) => this.profileRepo.sendChatGroupMessage(
payload?.uid,
payload?.chatGroupId!,
payload?.content,
payload?.contentType
).pipe(
**map**( response => of( ChatActions.sendMessageSuccess( { payload: new ProfileChatMessage( action.payload ) } ) ) ),
catchError( error => of( ChatActions.sendMessageFailure( error ) ) )
) )
) );
并更改此:
> 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))) ));
到
loadChatGroups$ = createEffect(() => this.actions$.pipe(
ofType(ChatActions.loadChatGroups),
mergeMap((action) => this.profileRepo.getChatGroups(action?.payload?.uid, action?.payload?.filters).pipe(
**map**(chatGroups => of(ChatActions.loadChatGroupsSuccess({ payload: chatGroups }))),
catchError(error => of(ChatActions.loadChatGroupsFailure(error)))
))
));
我发现导致多个 http requests.And 的问题不是 ng-rx,也不是我的应用程序发送的任何事件(感谢上帝,它不是 ng-rx,因为我喜欢用它),最后,它是 firebase。
"@angular/fire": "^7.0.4",
所以,简而言之,我有 firebase Auth 来保留我当前登录的用户,我有一个 HTTP 拦截器来获取 Bearer 令牌以传递我的 http 调用,我的拦截器如下所示:
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);
})
))
);
}
}
问题出在这一行:
authState(this.auth).pipe(...)
这导致我的http调用被多次触发,我不知道为什么(如果有人知道请回复)。
所以我所做的就是获取当前用户并调用 getIdToken()
并将其转换为 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);
})
);
现在我的 HTTP 调用按预期运行。万岁!
我正在开发一个 Angular 应用程序,该应用程序通过 Nx 生成并使用 NgRx 进行状态管理。 随着应用程序的规模,我面临着多次 API 调用的问题,我不知道为什么。
这是我的结构:
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 {}
我把所有API调用都放在了Repository模块中,如下:
repository.module.ts
@NgModule({
imports:[
],
providers:[
GirlHelper,
GirlRepository,
ProfileHelper,
ProfileRepository,
PhotoRepository,
ImageRepository,
ChatRepository,
UserRepository,
SettingsRepository,
NewsRepository,
EventRepository,
ServiceRepository
],
exports: [ ]
})
export class RepositoryModule {}
每个存储库都按提供的方式导出,因此我可以在任何地方使用它们。每个 service/repository 的格式如下(就像一个简单的数据服务):
@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))
);
}
}
我为我的应用程序的不同功能创建了功能模块,并将它们放在各自的文件夹中,就像普通功能模块一样。
例如client/client.module.ts
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(routes),
SsCommonModule,
ClientStoreModule
],
declarations: [
ClientComponent
],
})
export class ClientModule {}
我也在应用程序路由中延迟加载这些功能模块。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 {}
另一方面,我的 NgRx 存储在这些功能模块中,在 +state 文件夹中并按它们各自的文件划分(例如动作、模型等,通常的 ngrx 东西) 我还有一个单独的模块用于每个功能状态:
客户端/+state/client-store.module.ts
@NgModule({
imports: [
CommonModule,
StoreModule.forFeature(fromClient.clientFeatureKey, reducer),
EffectsModule.forFeature([ClientEffects])
],
providers: []
})
export class ClientStoreModule {}
然后将这些商店模块导入到它们各自的功能模块中,如下所示:
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(routes),
SsCommonModule,
ClientStoreModule
],
declarations: [
ClientComponent
],
})
export class ClientModule {}
相应的存储库被注入到功能模块的效果上以触发 API 调用:
@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)))
));
}
所以主要问题是我有多个 API 调用,我不知道为什么,一个例子只是在单击按钮时调用 API 调用(sendMessage 函数是在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!
}}));
}
}
现在,即使我只是直接调用 http 客户端调用,也会有多个 API 调用:
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();
}
我开发angular应用这么久了,还没遇到过这个问题。这是第一次尝试NgRx,因为他们说它对大型应用程序有益,现在我的应用程序足够大但恰恰相反。
有人遇到过这个问题吗?这应该是一个简单的 API 调用,现在我不确定为什么它会调用多个 API 调用。 是 NgRx 吗?是关于我如何构建商店模块吗?我应该删除 ngrx 吗?
非常感谢任何帮助,我现在被困住了,快要放弃 Ngrx 了。请救救我。
谢谢。
一些事情...
确保你的行为types/descriptions是独一无二的。已经说不出多少次了。
您将 catchError 放置在 Effect 的错误位置,并且正在调用不必要的 mergeMap
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))) ));
试试这个:
sendMessage$ = createEffect( () => this.actions$.pipe(
ofType( ChatActions.sendMessage ),
mergeMap( ( { payload } ) => this.profileRepo.sendChatGroupMessage(
payload?.uid,
payload?.chatGroupId!,
payload?.content,
payload?.contentType
).pipe(
**map**( response => of( ChatActions.sendMessageSuccess( { payload: new ProfileChatMessage( action.payload ) } ) ) ),
catchError( error => of( ChatActions.sendMessageFailure( error ) ) )
) )
) );
并更改此:
> 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))) ));
到
loadChatGroups$ = createEffect(() => this.actions$.pipe(
ofType(ChatActions.loadChatGroups),
mergeMap((action) => this.profileRepo.getChatGroups(action?.payload?.uid, action?.payload?.filters).pipe(
**map**(chatGroups => of(ChatActions.loadChatGroupsSuccess({ payload: chatGroups }))),
catchError(error => of(ChatActions.loadChatGroupsFailure(error)))
))
));
我发现导致多个 http requests.And 的问题不是 ng-rx,也不是我的应用程序发送的任何事件(感谢上帝,它不是 ng-rx,因为我喜欢用它),最后,它是 firebase。
"@angular/fire": "^7.0.4",
所以,简而言之,我有 firebase Auth 来保留我当前登录的用户,我有一个 HTTP 拦截器来获取 Bearer 令牌以传递我的 http 调用,我的拦截器如下所示:
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);
})
))
);
}
}
问题出在这一行:
authState(this.auth).pipe(...)
这导致我的http调用被多次触发,我不知道为什么(如果有人知道请回复)。
所以我所做的就是获取当前用户并调用 getIdToken()
并将其转换为 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);
})
);
现在我的 HTTP 调用按预期运行。万岁!