【问题标题】:Reactive form validation using asynchronous validation and Ngrx store使用异步验证和 Ngrx 存储的反应式表单验证
【发布时间】:2019-08-31 02:00:35
【问题描述】:

我正在开发一个 Angular 8 应用程序。我想使用 NgRx 存储和使用自定义异步验证器的反应式表单来显示表单错误。

login.component.ts

@Component({
  selector: 'auth-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss'],
})
export class LoginComponent implements OnInit {
  public transitionController: TransitionController = new TransitionController(
    true,
  );
  public form: FormGroup;
  public pageState$: Observable<ILoginPageState>;
  public formSubmitted: boolean = false;

  constructor(
    private _formBuilder: FormBuilder,
    private _store: Store<IAppState>,
  ) {
    this.pageState$ = this._store.select(selectLoginPageState) as Observable<
      ILoginPageState
    >;
    this._buildForm();
  }

  ngOnInit() {
    this._animatePage();
  }

  public onFormSubmit() {
    this.formSubmitted = true;

    if (this.form.invalid) {
      return;
    }

    this._store.dispatch(Login(this.form.value));
  }

  private _buildForm() {
    this.form = this._formBuilder.group({
      email: this._formBuilder.control(
        null,
        [Validators.required, Validators.email],
        [this.test.bind(this)],
      ),
      password: this._formBuilder.control(null, Validators.required),
    });
  }

  private test(control: AbstractControl) {
    return this._store.select(selectLoginErrorMessage).pipe(
      tap(() => console.log('executing')),
      map(value => ({
          foo: true
      })),
    );
  }

  private _animatePage() {
    this.transitionController.animate(
      new Transition(EAnimationType.FadeUp, 500, TransitionDirection.In),
    );
  }
}

login-page.effects.ts

@Injectable()
export class LoginEffects {
  constructor(
    private _actions$: Actions,
    private _authService: AuthenticationSevice,
    private _router: Router,
    private _modalService: SuiModalService,
    private _store: Store<IAppState>,
  ) {}

  Login$ = createEffect(() => {
    return this._actions$.pipe(
      ofType(AuthActions.Login),
      tap(() => this._store.dispatch(ShowPageLoader())),
      switchMap((credentials: ILoginCredentials) =>
        this._authService.login(credentials).pipe(
          map((response: ILoginResponse) => AuthActions.LoginSuccess(response)),
          catchError((response: HttpErrorResponse) => {
            let validationErrors: ValidationErrors;

            switch (response.status) {
              case HttpStatusCode.BAD_REQUEST:
                validationErrors = {
                  error: {
                    validationErrors: response.error,
                    generalError:
                      'Oops! We found some errors with your provided details.',
                  },
                };
                break;
              case HttpStatusCode.NOT_FOUND:
                validationErrors = {
                  error: {generalError: 'Email or password is incorrect.'},
                };
                break;
            }

            return of(AuthActions.LoginFailure(validationErrors));
          }),
          finalize(() => this._store.dispatch(HidePageLoader())),
        ),
      ),
    );
  });

  LoginSuccess$ = createEffect(
    () => {
      return this._actions$.pipe(
        ofType(AuthActions.LoginSuccess),
        tap(() => {
          this._modalService.open(
            new ModalComponent<IModalContext>(undefined, {
              title: 'Login successful',
              imageSrc: 'assets/images/modal/login-successful.png',
            }),
          );

          this._router.navigateByUrl('/home');
        }),
      );
    },
    {dispatch: false},
  );
}

这里的主要问题在于我的test 方法。我希望在错误字段上设置{ foo : true},但它永远不会发生。我在谷歌上搜索了很多,我发现一个解决方案是在 pipe() 中添加 first() 方法,以便我的 observable 完成。它起作用了,但只是第一次。另外,提交表单时,异步验证器从未调用过。

我在互联网上找到的所有示例都是使用 Http 调用。我知道它在请求完成时完成了 observable 但在我的情况下,该 Http 调用正在我的 login-page.effects.ts

内部处理

有没有更好的方法呢?还是我需要一些我不熟悉的 RxJs 运算符?

【问题讨论】:

    标签: angular rxjs observable ngrx angular2-form-validation


    【解决方案1】:

    map rxjs 运算符在您的 test() 函数中没有返回您认为的内容,因为 foo 被解释为标签,而不是您想要的对象文字中的键。更多解释请参见docs

    有两种方法可以获得正确的语法。

    1.

    map(value => {
      return {foo: true};
    }),
    

    2.

    map(value => ({foo: true}),
    

    【讨论】:

    • 抱歉,我可能在这里的代码中遗漏了它,但我已经尝试过了。它只是不起作用。
    • 好的,那么请修改您发布的代码以反映这一点。
    【解决方案2】:

    不要返回this._store.select(),而是尝试连接到actions 流,监听多个动作完成事件AuthActions.LoginFailureAuthActions.LoginSuccess

    this.actions$.pipe(
            ofType(AuthActions.LoginFailure, AuthActions.LoginSuccess),
            switchMap(x => {
                if (x.type == AuthActions.LoginFailure)
                    return this._store.pipe(
                        select(selectLoginErrorMessage),
                        map(msg => ({ foo: true })),
                        take(1)
                    );
                return true;
            })
        )
    

    store.select 立即触发,不会等待效果结束。

    现在是另一个问题 -

    提交表单时从未调用过异步验证器。

    验证是在控制级别 (email) 配置的,因此不会被隐式触发。理想情况下,如果任何控件验证出现错误,则不应允许提交您的表单。

    【讨论】:

      【解决方案3】:

      我会说这是一个非常根本的错误。你首先要做的是了解 pipe() 和 subscribe() 的区别。

      这是一个有用的链接:Difference between the methods .pipe() and .subscribe() on a RXJS observable

      基本上,您的代码的问题在于您将操作传递到 observable 上,这意味着您的操作只会执行一次,并且不会监视更改。

      您需要使用下面显示的代码实际订阅选择操作:

      this.error$ = this.store.select(selectLoadingErrors);
      this.error$.subscribe(data=>{
      //do your code here
      
      });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-07-08
        • 2020-01-17
        • 1970-01-01
        • 2020-04-06
        • 1970-01-01
        • 2017-12-30
        • 2019-05-04
        相关资源
        最近更新 更多