I have a reactive form with two ion-input fields and two buttons (that are placed outside the form block). The first button performs an action on the form’s data, but the 2nd button takes the user to another view in the same page, regardless the form’s state.
When the user fills the form and press the 2nd button, it activates the 2nd view normally, but if the current ion-input fails one of its validators then the click event on any of the buttons is not being fired, despite the fact that I can see the ripple effect on the button… I need to click the button a 2nd time to get the click event fired.
a) This the markup for the Login view. This page consists of several “views”, each one handled by hidden divs. There is a property called “view” that control which view is active at any time. app-rest-button is a component that consists of a ion-button that get disabled once clicked and shows a spinner on the screen. I use that component for actions that requires a call to the REST api.
<!--
LOGIN VIEW
-->
<div class="content login" [hidden]="view != 1">
<h3>INGRESA A TU CUENTA</h3>
<h5>Por favor, ingresa tus credenciales para acceder a tu cuenta</h5>
<form [formGroup]="loginForm" novalidate>
<ion-list>
<!-- EMAIL OR USER NAME-->
<ion-item>
<ion-input
#loginNameInput
formControlName="uid"
label="Email o nombre de usuario"
labelPlacement="stacked"
autocomplete="new-password"
></ion-input>
</ion-item>
<div *ngIf="this.loginForm.controls['uid'].touched && this.loginForm.controls['uid'].invalid">
<div class="ion-padding luna-error-hint" *ngIf="this.loginForm.controls['uid'].errors?.required">
<ion-text color="danger">Debes indicar tu email o nombre de usuario</ion-text>
</div>
<div class="ion-padding luna-error-hint" color="danger" *ngIf="this.loginForm.controls['uid'].errors?.minlength">
<ion-text color="danger">Este valor debe tener al menos 10 caracteres</ion-text>
</div>
</div>
<!-- CONTRASEÑA -->
<ion-item>
<ion-input
formControlName="pwd"
label="Tu contraseña"
labelPlacement="stacked"
placeholder=""
type="{{pwdType}}"
autocomplete="new-password"
></ion-input>
<ion-button slot="end" fill="clear" (click)="togglePassword()">
<ion-icon *ngIf="showPwd==false" name="eye-outline"></ion-icon>
<ion-icon *ngIf="showPwd==true" name="eye-off-outline"></ion-icon>
</ion-button>
</ion-item>
<div *ngIf="this.loginForm.controls['pwd'].touched && this.loginForm.controls['pwd'].invalid">
<div class="ion-padding luna-error-hint" *ngIf="this.loginForm.controls['pwd'].errors?.required">
<ion-text color="danger">Debes indicar tu contraseña</ion-text>
</div>
<div class="ion-padding luna-error-hint" color="danger" *ngIf="this.loginForm.controls['pwd'].errors?.minlength">
<ion-text color="danger">Tu contraseña debe tener al menos 6 caracteres</ion-text>
</div>
</div>
</ion-list>
</form>
<section class="buttons">
<app-rest-button
expand="full"
color="primary"
(start)="login()"
[running]="signingUp">
Ingresar
</app-rest-button>
<!-- THESE TWO BUTTONS CAUSES THE PROBLEM -->
<ion-button expand="full" color="secondary" size="large" (click)="setView(5)">Olvide mi contraseña</ion-button>
<br/>
<ion-button expand="full" color="secondary" size="large" (click)="setView(2)">Quiero registrarme</ion-button>
<br/>
<br/>
<br/>
</section>
</div> <!-- LOGIN -->
c) This is the relevant parts of the setView method called by the second button on the login view:
/*
void setView(number)
CHANGE CURRENT VIEW
*/
setView(view: number) {
this.view = view;
let targetInput = null;
let targetForm = null;
switch (view) {
case 0: // START
break;
case 1: // LOGIN
targetInput = this.loginNameInput;
targetForm = this.loginForm;
break;
...
case 5: // PWD RECOVERY (1)
targetInput = this.resetPwdInput1;
targetForm = this.resetPwdForm1;
break;
...
}
// If the new view has a form, reset it.
if (targetForm) {
targetForm.setErrors(null);
targetForm.updateValueAndValidity();
}
// If the new view has a form, focus the first input box. We apply
// a delay to allow UI to fully display before attempting the focus.
if (targetInput)
setTimeout(() => targetInput.setFocus(), 500);
} // setView
Once the login view is activated, if I click on the 2nd button (a normal ion-button that calls setView() method), I can see the ripple effect on the button, I see the form’s validation getting in action, but setView() method is not being called… I need to click a 2nd time on the button to actually fire the setView() method…is like the form’s validation is blocking the button’s click event the first time.
It’s possibly because your condition for the ‘view’ to be hidden:
This will make the login view visible whenever view is different thant 1. When you click on the button Olvide mi contraseña the value of view is set to 5, so the login div will not be hidden. The weird thing is that it is hiding it after the second click (probably some other part of your code is changing the value of the view variable), because, according to the code you shared, it shouldn’t.
Try using *ngIf instead of [hidden], attributing a unique ‘view code’ to each of the views(so that view will be displayed if, and only if, the variable view has a specific value) and moving the section buttons to outside of the ‘views box’.
Something like this:
Code
<div id="main">
<section id="views">
<div class="content login" *ngIf="view === 1"></div>
<!-- other 'views' -->
<div class="content recover" *ngIf="view === 5"></div>
</section>
<section id="buttons">
<app-rest-button> Ingresar </app-rest-button>
<!-- only visibled if in login view -->
<ion-button *ngIf="view === 1" (click)="setView(5)">Olvide mi contraseña</ion-button>
<!-- visible if in any view other than the register one -->
<ion-button *ngIf="view !== 2" (click)="setView(2)">Quiero registrarme</ion-button>
<!-- visible if not in the login view -->
<ion-button *ngIf="view !== 1" (click)="setView(1)">Inicio de Sesión</ion-button>
<!-- any other buttons you may have -->
</section>
</div>
I highly recommend creating separate components for register and recover and navigate to them using the Router. This will make your code more readable and clean while also making maintenance far more easier down the road.
Thanks for your suggestion. I tried the *ngIf approach but not only the problem persists, but it also breaks the ability to focus ion-input programmatically (that’s why I had to move from *ngIf to [hidden] in the first place). I also checked and re-checked the code, and even tough I call setView() methods in several places, none of them are being called at that time. The button just call setView() and that methods does just 3 things: set the new value of view property, reset the appropriate form and focus the form’s first ion-input.
Actually, I just ran some additional tests and discovered two more things:
a) Problem occurs only when the focus is placed on an ion-input; if I remove the call to focus() on setView() method, the button works as expected.
b) Problem is actually being caused by the validators; if I remove all validators from the form, then the button works as expected even if an ion-input is focused when I click on the button.
Regarding this:
I highly recommend creating separate components for register and recover and navigate to them using the Router. This will make your code more readable and clean while also making maintenance far more easier down the road.
I completely agree… is just that in this particularly case I preferred to keep all the code related to login-register-pwdreset actions in a single page.