I'm developing an Angular 8 application. I want to display form errors using NgRx store and reactive forms using a custom asynchronous validator.
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},
);
}
The main problem here is inside my test
method. I want the { foo : true}
to be set on the error field but it never happens. I searched a ton on google and 1 solution I found was to add first()
method inside the pipe()
so that my observable gets completed. It worked, but only for the very first time. Plus, the async validator never called when the form was submitted.
All of the examples that I found on the internet were using Http call. I understand that it completes the observable when the request is complete but in my case, that Http call is being handled inside my login-page.effects.ts
Is there any better way to do it? or do I need some RxJs operator which I'm not familiar of?