Angular/w ngrx - 连续 API 调用

标签 angular redux ngrx

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

  1. 我有某种实体的形式。
  2. 提交后,我会向 API 发送 POST 请求,其中仅包含该实体的名称。
  3. 作为响应,我获得了新创建实体的 ID。
  4. 紧接着,我想发送第二个请求,其中包含其余的表单值和我刚刚获得的 ID。

我应该在哪里以及如何提出第二个请求?

最佳答案

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

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

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

您的应用能否仅使用第一个请求中的 id 而无需第二个请求来继续工作,还是最终会处于不一致的状态?

<小时/>

我将介绍两种情况:

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

场景 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

通过这种方法,您可以区分两个请求的结果,并在其中一个请求失败时做出不同的 react 。 我建议将这两个调用分成独立的操作,以便您可以独立地减少每个案例。

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

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([..]) 的结合。它允许您调度一个可以缩减(通过reducer)的Post1SuccessAction以及一个将触发第二个效果运行的Post2RequestAction。如果第一个请求失败,第二个请求将不会运行,因为 Post2RequestAction 未调度。

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

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

关于Angular/w ngrx - 连续 API 调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44264063/

相关文章:

javascript - 从 redux-observable 史诗中访问状态

javascript - 将 Redux 与 React v0.12.2 结合使用

angular - 从组件类中的可观察值中获取唯一/最新值(商店选择器的结果)

javascript - 具有 Store 调度操作的 Angular 单元测试组件

angular - 如何在 Angular 2 中订阅 DOMContentLoaded 事件?

angular - ionic2 将 ngx-translate 应用于菜单项

javascript - 访问 Angular2 模板中的特定数组元素

react-native - 如何以及在何处使用 AsyncStorage 保存整个 redux 存储

angular - 如何规范化 ngrx/store 中的深层嵌套数据?

css - 如何在 Angular 中更改整个页面的背景颜色