Проверка реактивной формы с использованием асинхронной проверки и хранилища Ngrx

Я разрабатываю приложение Angular 8. Я хочу отображать ошибки формы, используя хранилище NgRx и реактивные формы, используя собственный асинхронный валидатор.

логин.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),
    );
  }
}

страница входа.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} было установлено в поле ошибки, но этого никогда не происходит. Я искал тонну в Google, и одно решение, которое я нашел, заключалось в том, чтобы добавить метод first() внутри pipe(), чтобы мой наблюдаемый объект был завершен. Это сработало, но только в самый первый раз. Кроме того, асинхронный валидатор никогда не вызывался при отправке формы.

Все примеры, которые я нашел в Интернете, использовали вызов Http. Я понимаю, что он завершает наблюдаемое, когда запрос завершен, но в моем случае этот вызов Http обрабатывается внутри моего login-page.effects.ts

Есть ли лучший способ сделать это? или мне нужен какой-то оператор RxJs, с которым я не знаком?


person Zahid Saeed    schedule 31.08.2019    source источник


Ответы (3)


Оператор map rxjs в вашей функции test() не возвращает то, что вы думаете, потому что foo интерпретируется как метка, а не как ключ в литерале объекта, как вы предполагали. Дополнительную информацию см. в документах. .

Есть два способа получить правильный синтаксис.

1.

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

2.

map(value => ({foo: true}),
person Collierre    schedule 31.08.2019
comment
Извините, я мог пропустить это в коде здесь, но я уже пробовал это. Это просто не работает. - person Zahid Saeed; 01.09.2019
comment
Хорошо, тогда, пожалуйста, измените код, который вы разместили, чтобы отразить это. - person Collierre; 01.09.2019

Вместо того, чтобы возвращать this._store.select(), попробуйте подключиться к потоку actions, прослушивающему несколько событий завершения действия AuthActions.LoginFailure и AuthActions.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), поэтому она не будет запускаться неявно. В идеале ваша форма не должна быть допущена к отправке, если есть ошибки в какой-либо контрольной проверке.

person Pankaj    schedule 01.09.2019

Я бы сказал, что это очень фундаментальная ошибка. Прежде всего вам нужно понять разницу между pipe() и subscribe().

Вот ссылка, которая будет полезна: Разница между методами .pipe() и .subscribe() наблюдаемого объекта RXJS

По сути, проблема с вашим кодом заключается в том, что вы передаете операцию наблюдаемому, что означает, что ваши операции будут выполняться только один раз и не будут отслеживать изменения.

Вам нужно фактически подписаться на действие выбора, используя код, показанный ниже:

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

});
person The Cloud Guy    schedule 02.09.2019