Angular / w ngrx - 连续的API调用

时间:2017-05-30 13:45:55

标签: angular redux ngrx

我正在Angular 4 app中实现ngrx。 redux相关部分的代码结构基于ngrx repo(https://github.com/ngrx/example-app)的示例应用程序。现在我想知道如何实现这样的东西:

  1. 我有某种实体的形式。
  2. 在提交时,我发送POST请求到API,只有该实体的名称。
  3. 作为回应,我得到了新创建的实体的身份。
  4. 之后我立即发送第二个请求,其余的表单值和我刚刚获得的id。
  5. 我应该在何处以及如何提出第二个请求?

1 个答案:

答案 0 :(得分:7)

如何实现连续的API调用取决于调用的内聚程度。
我的意思是你是否将这两个调用视为单个'事务',其中两个请求都必须成功,才能成功更改状态。

显然,如果第一个请求失败,则无法启动第二个请求,因为它依赖于第一个请求中的数据。 但......

当第一个请求成功并且第二个请求失败时会发生什么?

您的应用可以继续使用第一个请求中的id而没有第二个请求,还是会以不一致的状态结束?

我将介绍两种情况:

  1. 场景1:当其中一个请求失败时,您将其视为整个“事务”失败,因此无需关心哪个请求失败。
  2. 场景2:当请求1失败时,将不会执行请求2。当请求2失败时,请求1仍将被视为成功。
  3. 场景1

    由于两个请求都必须成功,因此您可以查看这两个请求,就好像它们只是一个请求一样。 在这种情况下,我建议隐藏服务中的连续调用(这种方法不是特定于ngrx / redux,它只是简单的RxJs):

    @Injectable()
    export class PostService {
        private API_URL1 = 'http://your.api.com/resource1';
        private API_URL2 = 'http://your.api.com/resource2';
    
        constructor(private http: Http) { }
    
        postCombined(formValues: { name: string, age: number }): Observable<any> {      
            return this.http.post(this.API_URL1, { name: formValues.name })
                .map(res => res.json())
                .switchMap(post1result =>
                    this.http.post(this.API_URL2, {
                     /* access to post1result and formValues */
                      id: post1result.id,
                      age: formValues.age,
                      timestamp: new Date()
                    })
                    .map(res => res.json())
                    .mergeMap(post2result => Observable.of({
                      /* access to post1result and post2result */
                      id: post1result.id,
                      name: post1result.name,
                      age: post2result.age,
                      timestamp: post2result.timestamp
                   })
                );
        }
    }
    

    现在,您可以像使用ngrx-example-app中展示的任何其他服务方法一样使用postCombined - 方法。

    • 如果任一请求失败,该服务将抛出一个错误,您可以捕获并处理您的效果。
    • 如果两个请求都成功,您将获得mergeMap内定义的数据。如您所见,可以从两个请求响应中返回合并数据。

    场景2

    使用这种方法,您可以区分两个请求的结果,如果其中一个请求失败,则会有不同的反应。 我建议将两个调用分解为独立的操作,这样你就可以独立地减少每个案例。

    首先,该服务现在有两个独立的方法(这里没什么特别的):

    <强> post.service.ts

    @Injectable()
    export class PostService {
        private API_URL1 = 'http://your.api.com/resource1';
        private API_URL2 = 'http://your.api.com/resource2';
    
        constructor(private http: Http) { }
    
        post1(formValues: { name: string }): Observable<{ id: number }> {
            return this.http.post(this.API_URL1, formValues).map(res => res.json());
        }
    
        post2(receivedId: number, formValues: { age: number }): Observable<any> {
            return this.http.post(this.API_URL2, {
              id: receivedId,
              age: formValues.age,
              timestamp: new Date()
            })
            .map(res => res.json());
      }
    }
    

    接下来为两个请求定义请求,成功和失败操作:

    <强> post.actions.ts

    import { Action } from '@ngrx/store';
    
    export const POST1_REQUEST = 'POST1_REQUEST';
    export const POST1_SUCCESS = 'POST1_SUCCESS';
    export const POST1_FAILURE = 'POST1_FAILURE';
    export const POST2_REQUEST = 'POST2_REQUEST';
    export const POST2_SUCCESS = 'POST2_SUCCESS';
    export const POST2_FAILURE = 'POST2_FAILURE';
    
    export class Post1RequestAction implements Action {
        readonly type = POST1_REQUEST;
        constructor(public payload: { name: string, age: number }) { }
    }
    
    export class Post1SuccessAction implements Action {
        readonly type = POST1_SUCCESS;
        constructor(public payload: { id: number }) { }
    }
    
    export class Post1FailureAction implements Action {
        readonly type = POST1_FAILURE;
        constructor(public error: any) { }
    }
    
    export class Post2RequestAction implements Action {
        readonly type = POST2_REQUEST;
        constructor(public payload: { id: number, name: string, age: number}) { }
    }
    
    export class Post2SuccessAction implements Action {
        readonly type = POST2_SUCCESS;
        constructor(public payload: any) { }
    }
    
    export class Post2FailureAction implements Action {
        readonly type = POST2_FAILURE;
        constructor(public error: any) { }
    }
    
    export type Actions
        = Post1RequestAction
        | Post1SuccessAction
        | Post1FailureAction
        | Post2RequestAction
        | Post2SuccessAction
        | Post2FailureAction
    

    现在我们可以定义在调度请求操作时将运行的两个效果,然后根据服务调用的结果调度成功或失败操作:

    <强> post.effects.ts

    import { PostService } from '../services/post.service';
    import * as post from '../actions/post';
    
    @Injectable()
    export class PostEffects {
        @Effect()
        post1$: Observable<Action> = this.actions$
            .ofType(post.POST1_REQUEST)
            .map(toPayload)
            .switchMap(formValues => this.postService.post1(formValues)
                .mergeMap(post1Result =>
                    Observable.from([
                        /*
                         * dispatch an action that signals that
                         * the first request was successful
                         */
                        new post.Post1SuccessAction(post1Result),
    
                        /*
                         * dispatch an action that triggers the second effect
                         * as payload we deliver the id we received from the first call
                         * and any other values the second request needs
                         */
                        new post.Post2RequestAction({
                            id: post1Result.id,
                            name: formValues.name,
                            age: formValues.age
                        })
                    ])
                )
                .catch(err => Observable.of(new post.Post1FailureAction(err)))
            );
    
        @Effect()
        post2$: Observable<Action> = this.actions$
            /*
             * this effect will only run if the first was successful
             * since it depends on the id being returned from the first request
             */
            .ofType(post.POST2_REQUEST)
            .map(toPayload)
            .switchMap(formValuesAndId =>
                this.postService.post2(
                    /* we have access to the id of the first request */
                    formValuesAndId.id,
                    /* the rest of the form values we need for the second request */
                    { age: formValuesAndId.age }
                )
                .map(post2Result => new post.Post2SuccessAction(post2Result))
                .catch(err => Observable.of(new post.Post2FailureAction(err)))
            );
    
        constructor(private actions$: Actions, private postService: PostService) { }
    }
    

    请注意第一个效果中mergeMapObservable.from([..])的组合。它允许您调度可以减少的Post1SuccessAction(通过减速器)以及将触发第二个效果运行的Post2RequestAction。如果第一个请求失败,第二个请求将不会运行,因为未调度Post2RequestAction

    如您所见,通过这种方式设置操作和效果,您可以独立于其他请求对失败的请求做出反应。

    要开始第一个请求,您只需在提交表单时发送Post1RequestAction即可。例如this.store.dispatch(new post.Post1RequestAction({ name: 'Bob', age: 45 }))