通过 NGRX 显示成功、错误消息的正确方法

Posted

技术标签:

【中文标题】通过 NGRX 显示成功、错误消息的正确方法【英文标题】:Right approach to display success, error messages via NGRX 【发布时间】:2018-07-29 00:35:56 【问题描述】:

我知道这个问题的两种解决方案,第一个是让消息保持在你看起来不太好的状态,第二个是订阅我目前用来显示消息的ActionSubject

还有其他解决方案吗?还有如何在模板中设置 CSS 类,而不是在组件中?

这是我的例子:

 this.actionSubject.subscribe(action => 
      if (action.type === fromActions.LOGIN_SUCCESS) 
        this.message$ = action.payload.message;
        this.messageClass = 'alert alert-success';
      
      if (action.type === fromActions.LOGIN_FAILURE) 
        this.message$ = action.payload.error.message;
        this.messageClass = 'alert alert-danger';
        this.LoginForm.reset();
      
    )

似乎太长了,不是 DRY,我应该在我希望有消息的每个组件中都这样做。

【问题讨论】:

【参考方案1】:

来自原始码头https://github.com/ngrx/effects/blob/master/docs/intro.md的示例

创建描述登录操作来源的 AuthEffects 服务:

import  Injectable  from '@angular/core';
import  Http  from '@angular/http';
import  Actions, Effect  from '@ngrx/effects';
import  Observable  from 'rxjs/Observable';

@Injectable()
export class AuthEffects 
  constructor(
    private http: Http,
    private actions$: Actions
  )  

  @Effect() login$ = this.actions$
      // Listen for the 'LOGIN' action
      .ofType('LOGIN')
      // Map the payload into JSON to use as the request body
      .map(action => JSON.stringify(action.payload))
      .switchMap(payload => this.http.post('/auth', payload)
        // If successful, dispatch success action with result
        .map(res => ( type: 'LOGIN_SUCCESS', payload: res.json() ))
        // If request fails, dispatch failed action
        .catch(() => Observable.of( type: 'LOGIN_FAILED' ))
      );

通过 EffectsModule.run 提供您的服务以自动启动您的效果:

import  EffectsModule  from '@ngrx/effects';
import  AuthEffects  from './effects/auth';

@NgModule(
  imports: [
    EffectsModule.run(AuthEffects)
  ]
)
export class AppModule  

注意:对于依赖于要引导的应用程序的效果(即依赖于路由器的效果),请使用 EffectsModule.runAfterBootstrap。请注意,runAfterBootstrap 仅适用于根模块。

否则,您可以在此处查看如何使用防护:https://toddmotto.com/preloading-ngrx-store-route-guards

【讨论】:

【参考方案2】:

您可以采用多种方法将逻辑移至模板。

这是一种方法:

// component
public isSuccess = merge(
    this.actions.pipe(filter(x => x.type === 'SUCCESS'),mapTo(true)),
    this.actions.pipe(filter(x => x.type === 'FAILURE'),mapTo(false))
);
public message = merge(
    this.actions.pipe(filter(x => x.type === 'SUCCESS'),map(x => x.payload.message)),
    this.actions.pipe(filter(x => x.type === 'FAILURE'),map(x => x.payload.error.message))
);

// template
<div class="alert"
    [class.alert-success]="isSuccess | async"
    [class.alert-danger]="!(isSuccess | async)">
 message | async
</div>

这是另一个:

<div class="alert alert-success"
    *ngIf="(action | async).type === 'SUCCESS'">
     (action | async).payload.message 
</div>
<div class="alert alert-danger"
    *ngIf="(action | async).type === 'FAILURE'">
     (action | async).payload.error.message 
</div>

就表单重置而言,我想您仍然需要订阅。如果您正在使用效果,那么您可以使用 actions.ofType(...) 而不是过滤器运算符。我不知道你使用的是什么版本的 rxjs,所以我使用的是 pipeable 语法。

如果你要在多个地方做同样的事情,那么我建议设置一个封装这个逻辑的组件。

【讨论】:

【参考方案3】:

我将合并 @Kliment Ru 和 @bygrace 的答案,并举例说明我构建的东西,用于将带有快餐栏(材料)的全局消息传递逻辑封装为可调度操作。

message.action.ts

import  Action  from '@ngrx/store';

export const MESSAGE = '[Messages] Show Message';

export class Message implements Action 
    readonly type = MESSAGE;
    constructor(
        public payload: 
            message: string;
            action?: string;
            duration?: number;
            callback?: Function;
        
    )  

非常简单,将snackbar 属性封装到一个ngrx 动作中。

message.effect.ts

import  Injectable  from '@angular/core';

import  Effect, Actions  from '@ngrx/effects';
import * as MessageActions from '../actions/message.action';

import  tap, map  from 'rxjs/operators';
import  MatSnackBar  from '@angular/material';
import  first  from 'rxjs/operators/first';

@Injectable()
export class MessageEffects 

    constructor(
        private actions$: Actions,
        private snackBar: MatSnackBar
    )  

    @Effect( dispatch: false )
    navigate$ = this.actions$
        .ofType(MessageActions.MESSAGE)
        .pipe(
            map((action: MessageActions.Message) => action.payload),
            tap(( message, action, duration, callback ) => 
                duration = duration ? duration : 3000;

                // incase of an action assigned, subscribe to the snackbar, else just show the message
                if (callback) 
                    this.snackBar.open(message, action,  duration: duration )
                        .onAction()
                        .pipe(
                            first()
                        )
                        .subscribe(() => 
                            callback();
                        );
                 else 
                    this.snackBar.open(message, action,  duration: duration );
                

            ));


监听动作并显示snackbar的效果。

那么当你想使用它时,只需执行以下操作,

this.store.dispatch(new fromRoot.Message( message: 'Something went wrong, please try again later' ));

一个简单的单线,封装了应用程序中消息的整个逻辑和 UI,它背后的好处是我可以使用任何库将我的小吃栏更改为我想要的任何内容,并且我只需要更改代码一处。

【讨论】:

使用效果而不是直接调用服务有什么好处? 只是遵循消息模式是用户采取的行动的副作用,例如提交无效表单、更新某些内容、创建某些内容。 @DanielNetzer 如果用户不启动操作,onAction 订阅会发生什么情况。 Snackbar 模块是否会在持续时间过去后完成 observable? @IngóVals,好问题。从代码的外观来看,它不是,但您可以在小吃店持续时间周围使用 takeUntil + timer 运算符轻松终止他

以上是关于通过 NGRX 显示成功、错误消息的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

错误:商店没有提供者!在@ngrx 4.x

将变量从商店绑定到角度ngrx中的组件

Laravel 5.4 以不同方式显示 flash 错误和成功消息

如何使用 PHP 为 JQuery .ajax() 返回正确的成功/错误消息?

角度ngrx存储错误无法读取未定义的属性“计划”

在 jquery validate 插件上的回调函数上显示错误消息