Angular: Submit method executed by clicking another button - html

I'm using Angular with Angular Material components.
So, I added a login component (form) where the user can type in his email and password. The input field for the password has an eye on the end to enable the user to display the password in plain text if wanted.
Unfortunately, by clicking twice the eye button, the submit method executes which is binded to the submit button.
The simple question is: why?
Template
<form [formGroup]='loginForm' (ngSubmit)="onSubmit()">
<div>
<mat-form-field>
<mat-label for="email">E-Mail</mat-label>
<input matInput type="text" formControlField="email" />
<mat-error>
Test
</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field>
<mat-label for="password">Password</mat-label>
<input matInput [type]="hide ? 'password' : 'text'" formControlField="password" />
<button mat-icon-button matSuffix class="mat-icon-button mat-button-base" (click)="hide = !hide" [attr.aria-label]="'Hide password'" [attr.aria-pressed]="hide">
<mat-icon>{{ hide ? 'visibility_off' : 'visibility' }}</mat-icon>
</button>
</mat-form-field>
</div>
<button mat-raised-button color="primary" type="submit">Login</button>
Component
export class LoginComponent implements OnInit {
loginForm: FormGroup;
hide = true;
constructor(
private formBuilder: FormBuilder
) {
this.loginForm = this.formBuilder.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required]]
})
}
onSubmit() {
window.alert("Login button clicked");
}

Try adding type="button" to your "eye" button.
This will tell the browser not to treat it as a submit button when inside a form.
The reason is that missing type argument from button is treated as default state and by default button element has submit.
The missing value default and invalid value default are the Submit Button state.
Source: https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type

Related

Angular Material Hide Password toggles on enter

I'm new to angular and i have a project that already has a login with a username and password input. The password input has a button to show the password. The problem is, that when im in the password input field and i press enter, it toggles the button to show the password instead of submitting the form. How can i change that?
component.html
<div style="text-align: center" class="centered">
<form [formGroup]="form">
<br>
<mat-form-field appearance="fill">
<mat-label>Username</mat-label>
<input name="username" formControlName="username" matInput>
</mat-form-field>
<br>
<mat-form-field appearance="fill">
<mat-label>Password</mat-label>
<input name="password" formControlName="password" matInput [type]="passwordVisible ? 'text' : 'password'">
<button mat-icon-button matSuffix (click)="passwordVisible = !passwordVisible" [attr.aria-label]="'Hide password'"
[attr.aria-pressed]="!passwordVisible">
<mat-icon>{{passwordVisible ? 'visibility' : 'visibility_off'}}</mat-icon>
</button>
</mat-form-field>
<br>
<button (click)="login()" mat-raised-button>Login</button>
<button (click)="navigateToRegister()" mat-raised-button color="primary" style="margin-left: 10px">Register</button>
</form>
</div>
Default type for the button is submit that might be causing this issue try changing your show/hide button type to button something like
<button type="button" mat-icon-button matSuffix (click)="passwordVisible = !passwordVisible" [attr.aria-label]="'Hide password'"
[attr.aria-pressed]="!passwordVisible">
<mat-icon>{{passwordVisible ? 'visibility' : 'visibility_off'}}</mat-icon>
</button>
I also stumbled upon this issue recently and was able to resolve it using the approach below.
Method onClickRevealPassword will be called if you press enter on the password input field. Such action results in an event called pointerType which you can detect with this method and keep the password field not to reveal the plain password value. Have a look and experiment it with your component.
<mat-form-field appearance="fill">
<mat-label>Password</mat-label>
<input name="password" formControlName="password" matInput [type]="passwordVisible ? 'text' : 'password'">
<button mat-icon-button matSuffix (click)="passwordVisible = !passwordVisible; onClickRevealPassword($event)" [attr.aria-label]="'Hide password'"
[attr.aria-pressed]="!passwordVisible">
<mat-icon>{{passwordVisible ? 'visibility' : 'visibility_off'}}</mat-icon>
</button>
</mat-form-field>
onClickRevealPassword(event) {
event.preventDefault();
// Prevent revealing the password when enter button is pressed.
if (event?.pointerType) {
this.passwordVisible = !this.passwordVisible;
}
}

Set initial value to a Toggle of a form in Angular Material within HTML

I am working with a simple form:
HTML
<mat-card>
<form (submit)="onAddBet(betForm)" #betForm="ngForm">
<mat-form-field>
<textarea #description="ngModel" matInput rows="6" placeholder="Description" name="description" ngModel
required></textarea>
<mat-error *ngIf="description.invalid">Please enter a Bet Description</mat-error>
</mat-form-field>
<mat-slide-toggle #privacy="ngModel" name="privacy" ngModel>Private</mat-slide-toggle>
<mat-slide-toggle #comments="ngModel" name="comments" ngModel>Allow comments</mat-slide-toggle>
<button mat-raised-button color="primary" type="submit">
CREATE
</button>
</form>
</mat-card>
If I press submit without touching any field of the form I get all the fields empty as it should be expected but I would like to get instead of the status of the form, meaning all the fields as "" but in the fields "privacy" and "comments" as false (boolean) (the default appearance of the toggles is as not marked).
I know that this could be easily done by Typescript from the component following the method:
Typescript
onAddBet(form: NgForm) {
if (!form.value.privacy) {
form.value.privacy = false;
console.log(form.value);
} else console.log(form.value);
if (form.invalid) return;
form.resetForm();
}
But I was wondering if there is any HTML attribute to run the same code avoiding to use Typescript
How could I do it?
You can initialize your ngModel in your HTML by using ngModel with a one-way binding
<mat-slide-toggle #privacy="ngModel" name="privacy" [ngModel]="false">Private</mat-slide-toggle>
<mat-slide-toggle #comments="ngModel" name="comments" [ngModel]="false">Allow comments</mat-slide-toggle>
Then in your component
onAddBet(form: NgForm) {
console.log(form.value);
if (form.invalid) return;
form.resetForm();
}
As per the documentation of slide-toggle you can use [checked]="checked" in HTML.

Creating multiple forms using ngFor

Have to create a form inside each row-detail of a ngx-datatable.
<ngx-datatable-row-detail [rowHeight]="'100%'" #serviceDetailRow (toggle)="onDetailToggle($event)">
<ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template>
<mat-tab-group>
<mat-tab label="Components">
<mat-card-actions class="component-actions">
<form [formGroup]="addComponentForm" (ngSubmit)="addComponent(row.serviceId)">
<mat-form-field class="add-component">
<input matInput placeholder="Component Name" [formControl]="componentFormControl" [errorStateMatcher]="componenteMatcher">
<mat-error *ngIf="componentFormControl.hasError('pattern') && !componentFormControl.hasError('required')">
Please enter a valid component name
</mat-error>
<mat-error *ngIf="componentFormControl.hasError('minlength') && !componentFormControl.hasError('required')">
Please enter at least 3 characters
</mat-error>
<mat-error *ngIf="componentFormControl.hasError('required')">
Component Name is <strong>required</strong>
</mat-error>
</mat-form-field>
<button type="button" mat-raised-button color="primary">Add Components</button>
</form>
</mat-card-actions>
...
</ngx-datatable-row-detail>
On click of expanded row button, we trigger the expanded row function
toggleExpandRow(row) {
this.servicesTable.rowDetail.toggleExpandRow(row);
// Create component form
console.log(this.services);
this.addComponentForm[row.serviceId] = this.fb.group({
component: this.componentFormControl
});
** component.ts **
export class ServicesComponent implements OnInit {
#ViewChild('servicesTable') servicesTable: any;
addServiceForm: FormGroup;
addComponentForm: FormGroup[] = [];
On trying to attach a formGroup to the componentForm which is an array of FormGroup initialised as an empty array, i get the error
ERROR TypeError: this.form._updateTreeValidity is not a function

Angular reactive FormGroup is not reset in third party controls

I am using Angular6 and angular material stepper. There are three flows in which stepper control have one form and second stepper control have 2 input fields.
When i have entered the firstStepperCtrl and navigate to second stepper and click on prev and come to second stepper which shows the validation.
Without touching the form , the validation is showing , how to reset the stepper and validate the stepper if second form field has been touched.
Here is the mark up:
<md-horizontal-stepper [linear]="isLinear" #stepper>
<md-step [stepControl]="firstFormGroup">
<form [formGroup]="firstFormGroup">
<ng-template mdStepLabel>Fill out your name</ng-template>
<md-form-field>
<input mdInput placeholder="Last name, First name" formControlName="firstCtrl" required>
</md-form-field>
<div>
<button md-button mdStepperNext>Next</button>
</div>
</form>
</md-step>
<md-step [stepControl]="secondFormGroup">
<form [formGroup]="secondFormGroup" #formDirective="ngForm">
<ng-template mdStepLabel>Fill out your address</ng-template>
<md-form-field>
<input mdInput placeholder="Address" formControlName="secondCtrl" required>
</md-form-field>
<md-form-field>
<input mdInput placeholder="phoneNo" formControlName="secondCtrlPhoneNo" required>
</md-form-field>
<div>
<button md-button mdStepperPrevious>Back</button>
<button md-button mdStepperNext>Next</button>
<button md-button (click)="resetWholeStepper(stepper)">Reset</button>
<button md-button (click)="reset2Stepper()">Reset1</button>
</div>
</form>
</md-step>
<md-step>
<ng-template mdStepLabel>Done</ng-template>
You are now done.
<div>
<button md-button mdStepperPrevious>Back</button>
<button md-button (click)="resetStepper(stepper)">Reset</button>
</div>
</md-step>
</md-horizontal-stepper>
app.component.ts
export class AppComponent implements OnInit, AfterViewInit {
private ngVersion: string = VERSION.full;
isLinear = false;
firstFormGroup: FormGroup;
secondFormGroup: FormGroup;
constructor(private _formBuilder: FormBuilder,) { }
// Event fired when component initializes
ngOnInit() {
this.firstFormGroup = this._formBuilder.group({
firstCtrl: ['', Validators.required]
});
this.secondFormGroup = this._formBuilder.group({
secondCtrl: ['', Validators.required],
secondCtrlPhoneNo:['',Validators.required]
});
}
// Event fired after view is initialized
ngAfterViewInit() {
}
resetWholeStepper(stepper: MdStepper){
stepper.selectedIndex = 0;
}
reset2Stepper1(formDirective: FormGroupDirective){
alert(2);
formDirective.resetForm();
// this.secondFormGroup.clearValidators();
this.secondFormGroup.reset();
}
[screencast link of issue]]1
demo

html5 validation in Ionic

I am learning Ionic at the time. But it seems like HTML5 validation is not working in Ionic.
So, i have a login form like below.
<h3> Login </h3>
<form>
<input name="emailaddress" placeholder="Enter Email Address" class="email" [(ngModel)]="userData.l_email" type="email" required />
<input name="name" placeholder="Enter Password" class="name" type="password" [(ngModel)]="userData.l_password" required />
<button class="semi-transparent-button is-blue with-border" (click)="login()">
Login
</button>
</form>
When i click on login button it didn't perform validation. As i have put required in both field but it simply submit the form.
Also email validation is not working.
I have checked How can I make validation of email in Ionic using HTML5, JS or Angular work? but it is a work around. That i want to avoid.
HTML5 form validation does not work in Ionic. Instead, you can use Angular forms.
This is a great tutorial by Josh Morony on how to do it.
In your case, you can do something like this using FormBuilder and specifying Validators for each field (for a full list of available validators, take a look at the docs).
import { Component } from '#angular/core';
import { Validators, FormBuilder, FormGroup } from '#angular/forms';
#Component({
selector: 'page-login',
templateUrl: 'login.html'
})
export class Login {
login: FormGroup;
submitted: boolean = false;
constructor(public formBuilder: FormBuilder) {
this.login = this.formBuilder.group({
email: ['', Validators.compose([Validators.email, Validators.required])],
password: ['', Validators.required],
});
}
onLogin() {
this.submitted = true;
if (this.login.valid) {
console.log(this.login.value);
}
}
}
In your template, use FormGroup and show your error message when the field is invalid.
<form [formGroup]="login" (ngSubmit)="onLogin()">
<ion-item>
<ion-label>Email</ion-label>
<ion-input type="email" formControlName="email"></ion-input>
</ion-item>
<p ion-text [hidden]="login.controls.email.valid || submitted === false" color="danger" padding-left>
Please enter a valid email
</p>
<ion-item>
<ion-label>Password</ion-label>
<ion-input type="password" formControlName="password"></ion-input>
</ion-item>
<p ion-text [hidden]="login.controls.password.valid || submitted === false" color="danger" padding-left>
Password is required
</p>
<button ion-button type="submit">Login</button>
</form>