I have this button that will be disabled everytime that the form is invalid
<button [disabled]="createForm.invalid" type="submit" class="btn btn-primary pull-right button-spinner" tabindex="0" ><span>NEXT</span></button>
but it seems like the validation only effect everytime I blur on a form control instead on change.
I also added this on ngOnit and changed the [disabled]="isNextDisabled" but it seems that the state will change on change too
this.createForm.valueChanges
.subscribe(formValue => {
this.isNextDisabled = !this.createForm.valid;
}
);
The initialization of the form:
this.createForm = this.formBuilder.group({
formText: ['', Validators.required],
list: ['', Validators.required]
)};
<!-- disable the button -->
<button>
Disable Button
</button>
<!-- enable the button -->
<button>
Enable Button
</button>
<!-- the target button -->
<button disabled="disabled">
I've been clicked {{count}} times.
</button>
follow the link below for getting the detail:
https://blogs.msdn.microsoft.com/laurieatkinson/2016/09/12/using-data-binding-with-angular-2-to-disable-a-button/
I will try to resolve your issue with one example as I did. Follow this you can do.
test.html
<form [formGroup]="changePasswordForm" (ngSubmit)="changePassword()">
<ion-item color="border-text">
<ion-input type="password" placeholder="Existing Password"
formControlName="existing_pass"></ion-input>
</ion-item>
<ion-item no-lines *ngIf="existingPass.invalid && existingPass.touched">
<ion-label style="color:#F53D3D" no-margin stacked>Existing Password Required</ion-label>
</ion-item>
<div class="buttonClass">
<button [disabled]="!isReadyToSave" ion-button round color="primary" block
style="margin-top: 20px !important;">Save
</button>
</div>
</form>
test.ts
import {FormBuilder, FormGroup, Validators} from '#angular/forms';
changePasswordForm: FormGroup;
isReadyToSave: boolean = false;
constructor(formBuilder: FormBuilder,) {
this.changePasswordForm = formBuilder.group({
existing_pass: ['', Validators.required]
});
this.changePasswordForm.valueChanges.subscribe((v) => {
this.isReadyToSave = this.chanegPasswordForm.valid;
});
}
You can use two operations
<button [disabled]='createForm.invalid || isNextDisabled'>Submit</button>
And use what you have tried to disable when valueChanges
this.createForm.valueChanges.subscribe((v) => {
this.isNextDisabled = !this.createForm.valid;
});
Working Stackblitz
Actually you may not need to separately look for valueChanges. Here is a code
HTML
<form [formGroup]="createForm">
<input formControlName='formText'>
<input formControlName='list'>
<button [disabled]="createForm.invalid"
type="submit"
class="btn btn-primary pull-right button-spinner"
tabindex="0" >
<span>NEXT</span>
</button>
</form>
component.ts
import { Component, OnInit } from "#angular/core";
import {
FormsModule,
ReactiveFormsModule,
FormGroup, FormBuilder, Validators
} from "#angular/forms";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
name = "Angular";
createForm: FormGroup;
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.createForm = this.formBuilder.group({
formText: ["", Validators.required],
list: ["", Validators.required]
});
}
}
Stackblitz DEMO
You can achieve this by checking your formGroup is valid or not with the attribute [disabled], check below for more clarification
TS:
// method which will create the FormGroup
private createMyForm() {
this.myForm = new FormGroup({
FormFiled1 : new FormControl('', [Validators.required, Validators.maxLength(200)]),
FormFiled2 : new FormControl('', []),
FormFiled3 : new FormControl('', [Validators.required])
});
}
[Validators.required] this will make the formfield required, as soon as you dont enter anything into the field the form will invalid
hence you have to check form is valid or not by using below code
HTML:
// set [disabled] value as true or false to disable or enable the button
<button type="submit" [disabled]="myForm.invalid" (click)="MyFunction()">Submit</button>
Demo
Yes, using onchange disables or enables when the focus is out from the input field.
For this you need to use ngModelChange, then it will enable/disable the button when you're in the input field.
This method is generally used for only a single input field like confirmation pop with input field for deleting, suspended etc
<mat-form-field appearance="outline">
<mat-label>Please Confirm</mat-label>
<input matInput name="value" [(ngModel)]='value' placeholder="Enter {{confirmValue}}" (ngModelChange)="onChange($event)"
autocomplete="off">
</mat-form-field>
<button mat-raised-button class="mat-accent mr-16" (click)="onSubmit()" [disabled]="confirmDisable">
<mat-icon>done</mat-icon>Confirm
</button>
TS
confirmDisable: boolean = false;
value!: string;
onChange($event: any) {
this.confirmDisable= this.value === this.confirmValue ? false : true;
}
onSubmit() {
//now confirm, we can proceed further
}
Related
I am using angular stepper to display all my data and take input for some of the text boxes, but I want to add my custom validations when user clicks on Next button.
stackblitz - material-stepper-custom-validation
html:
<mat-horizontal-stepper #stepper>
<mat-step>
<div class="m-10">
<input type="text" id="fname" placeholder="First Name" >
</div>
<div class="m-10">
<input type="text" id="lname" placeholder="Last Name" >
</div>
<div>
<button mat-button (click)="checkData()" matStepperNext>Next</button>
</div>
</mat-step>
<mat-step [stepControl]="secondFormGroup" [optional]="isOptional">
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</mat-step>
<mat-step>
<ng-template matStepLabel>Done</ng-template>
You are now done.
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button (click)="stepper.reset()">Reset</button>
</div>
</mat-step>
</mat-horizontal-stepper>
Here, first name and last name should be validated before go to next stepper, but not using formGroup.
ts:
import {Component, OnInit} from '#angular/core';
import {FormBuilder, FormGroup, Validators} from '#angular/forms';
#Component({
selector: 'stepper-optional-example',
templateUrl: 'stepper-optional-example.html',
styleUrls: ['stepper-optional-example.css']
})
export class StepperOptionalExample implements OnInit {
constructor() {}
ngOnInit() { }
checkData() {
let lname = (<HTMLInputElement>document.getElementById("fname")).value;
if(lname == '') {
alert('error');
}
return false;
}
}
How? - If first name is empty then don't allow them to go Next stepper.
Because you use the template driven approach, you will need to map all input fields into an ngModel somehow. Here is an example for it:
HTML:
<input type="text" required [(ngModel)]="model.name" name="name">
TS:
#Component({
selector: 'stepper-optional-example',
templateUrl: 'stepper-optional-example.html',
styleUrls: ['stepper-optional-example.css']
})
export class StepperOptionalExample implements OnInit {
#ViewChild('stepper') stepper;
model = {
name: 'Initial Value'
}
constructor() {}
ngOnInit() { }
}
Using that you can then, check the attribute onClick. You need to remove the matStepperNext and add the (click) event listener instead like so:
HTML:
<button mat-button (click)="onNext()">Next</button>
TS:
onNext() {
// Validate your value in the function
if (this.model.name !== 'Henry') {
this.stepper.next();
}
}
Other than that I also recommend to take a look on the official guide showing how to implement the template driven approach: https://angular.io/guide/forms
I am creating a basic login page. All the user has to do if enter a password and then tick the box if they want to add another user. They will only be able to add another user if they have right permissions (this bit I will be able to figure out myself).
I have managed to get the password input working, where the user enters a password and I can log this to the console (please remember that this is just a test). I cannot seem to read the value of the checkbox, it always returns null.
HTML
<div class="login">
<h2 class="login-header">Log in</h2>
<form [formGroup]="loginForm" class="login-container" (ngSubmit)="login()">
<p [ngClass]="{ 'has-error': isSubmitted && formControls.password.errors }">
<input type="password" placeholder="Password" formControlName="password" autofocus autocomplete="off">
</p>
<p>
<input id="addUser" type="checkbox" name="addUser" value="addUser" formcontrolName="checkbox">Add User?
</p>
<div id="invalid">
<p>Invalid Login... Try Again</p>
</div>
<p>
<input type="submit" value="Log in">
</p>
</form>
</div>
.ts
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators, FormControl } from '#angular/forms';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
constructor(private authService: AuthService, private router: Router, private FormBuilder: FormBuilder, private httpService: HttpClient) { }
loginForm: FormGroup;
isSubmitted = false;
loginDetails: any = []
loginLength: any = [];
ngOnInit() {
this.loginForm = this.FormBuilder.group({
password: ['', Validators.required],
checkbox: new FormControl
})
}
get formControls() { return this.loginForm.controls}
login() {
var checkboxresult = this.loginForm.get('checkbox').value;
console.log(checkboxresult)
console.log(this.loginForm)
}
}
When the checkbox is checked I want it to return true and when it is not I want it to return false. Currently it only returns null.
I have tried following this guide but this returns null. How do I get true or false from the form when submit is pressed?
Placing a child component with inputs in a parent form and submitting with invalidation will not show the errors in the child component only in the parent. The mat-error in the child will only show when clicked on the inputs.
To replicate the error with this parent Form
#Component({
selector: 'lyx-parent-form',
template: `
<form novalidate autocomplete="off" role="form" [formGroup]="form" (ngSubmit)="onSubmit()">
<mat-form-field>
<input type="password" formControlName="currentPassword" matInput placeholder="current password">
<mat-error *ngIf="form.get('currentPassword').hasError('required')">current required</mat-error>
</mat-form-field>
<lyx-child [form]="form"></lyx-child>
<button type="submit">submit</button>
</form>`
})
export class ParentFormComponent {
form: FormGroup;
constructor(fb: FormBuilder) {
this.form = fb.group({'currentPassword': ['', [Validators.required]]});
}
onSubmit() {}
}
The child component
#Component({
selector: 'lyx-child',
template: `
<div [formGroup]="form">
<mat-form-field>
<input type="password" formControlName="newPassword" matInput placeholder="password">
<mat-error *ngIf="form.get('newPassword').hasError('required')">Password Required</mat-error>
</mat-form-field>
</div> `
})
export class ChildComponent implements OnInit {
#Input() form: FormGroup;
ngOnInit(): void {
const newPassword = new FormControl('', Validators.compose([Validators.required]));
this.form.addControl('newPassword', newPassword);
}
}
Here is a workaround before I'm able to better understand the way the parent/child form interact.
When submitting the parent form manually set the child "newPassword" control as "touched"
onSubmit(): void {
this.form.controls.newPassowrd.markAsTouched({onlySelf: true});
}
I came up with the following solution that works for any number of nested forms. To use this directive, simply add formSubmittedSync to any child form group, e.g., <div [formGroup]="childForm" formSubmittedSync>
import { Directive, SkipSelf } from "#angular/core";
import { FormGroupDirective, ControlContainer } from "#angular/forms";
#Directive({
selector: '[formSubmittedSync]'
})
export class FormSubmittedSyncDirective {
constructor(
#SkipSelf() private parentFormGroupDirective: FormGroupDirective,
private formGroupDirective: FormGroupDirective) {
this.parentFormGroupDirective.ngSubmit.asObservable().subscribe(() => {
(this.formGroupDirective as any).submitted = true;
this.formGroupDirective.ngSubmit.emit();
});
}
}
I have a form with one field that acts as autocomplete. If the user enters a word and presses enter, the content of the field should be added to a list below the field.
The problem: When the user hits enter, naturally the whole form is being submitted.
I already return false on the function that handles the keypress. But the form seems to be submitted even before this function is called.
How do I prevent this from happening?
The basic form:
<div id="profileForm">
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()" method="post" *ngIf="!showSuccessMessage">
<div class="row">
<div class="form-group col-xs-12 col-sm-6">
<label for="first_name">My Skills</label>
<div class="autocomplete">
<input formControlName="skill_string" [(ngModel)]="skillString" name="skill_string"
type="text" class="form-control" id="skill_string" placeholder="Comma separated" (keyup.enter)="skillsHandleEnter(skillString)">
<ul class="autocomplete-list" *ngIf="skillHints.length > 0">
<li class="list-item" *ngFor="let skill of skillHints" (click)="addSkillFromAutocomplete(skill)">{{skill}}</li>
</ul>
</div>
<div id="skill-cloud" class="tag-cloud">
<span class="skill-tag tag label label-success" *ngFor="let skill of selectedSkills" (click)="removeSkill(skill)">{{skill}} x</span>
</div>
</div>
</div>
<div class="row">
<hr>
<div class="form-group submit-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-primary pull-right" [disabled]="!profileForm.valid">Save</button>
</div>
</div>
</div>
</form>
</div>
The basic component (I stripped a lot of the logic for posting it here):
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder } from '#angular/forms';
import { ActivatedRoute, Router } from '#angular/router';
import { Subscription } from 'rxjs/Rx';
import 'rxjs/add/operator/debounceTime';
import * as _ from 'lodash';
import { MemberService } from '../shared/index';
#Component({
moduleId: module.id,
selector: 'signup',
templateUrl: 'signup.component.html',
styleUrls: ['signup.component.css']
})
export class SignupComponent implements OnInit {
private profileForm:FormGroup;
private validation_errors:Array<any>;
private selectedSkills:Array<string>;
private skillHints:Array<string>;
private skillString:string;
constructor(private route: ActivatedRoute,
private formBuilder: FormBuilder,
private memberService: MemberService,
private router: Router ) {
this.selectedSkills = [];
this.skillHints = [];
this.skillString = '';
// Set up form
this.profileForm = this.formBuilder.group({
skill_string: ['']
});
}
ngOnInit(): any {
// Do something
}
updateSelectedSkills(skillString:string):void {
if(skillString) ) {
let cleanString = skillString.trim().replace(/[ ]{2,}/g, ' ');
this.selectedSkills = _.compact(this.selectedSkills.concat(cleanString.split(',')));
this.skillString = '';
this.skillHints = [];
}
}
skillsHandleEnter(skillString:string):void {
console.log("ENTER");
this.updateSelectedSkills(skillString);
return false;
}
autocompleteSkills(term:string):void {
this.memberService.autocompleteSkills(term).subscribe(
res => {
this.skillHints = [];
for(let i = 0; i < res.data.length; i++) {
this.skillHints.push(res.data[i].name);
}
}
);
}
addSkillFromAutocomplete(skillString:string):void {
this.selectedSkills.push(skillString);
this.memberProfile.skill_string = '';
this.skillHints = [];
this.skillString = '';
}
onSubmit():void {
this.memberService.saveProfile(this.memberProfile, this.selectedSkills).subscribe(
res => {
console.log(res);
}
);
}
}
Try
<form (keydown.enter)="$event.target.tagName == 'TEXTAREA'" [formGroup]="profileForm" (ngSubmit)="onSubmit($event)">
It will also allow enter in Textareas.
So the answer was actually quite simple... It wasn't Event.preventDefault() since I was listening for Enter on the input field and not the button. Removing type="submit" from the button wasn't enough since all buttons are type submit by default. The only change necessary was on the button element, adding type="button" explicitly and adding a (click) listener:
<button type="button" (click)="onSubmit()" class="btn btn-primary pull-right" [disabled]="!profileForm.valid">Save</button>
The only kind-of problem: Now submitting the form with enter never works. Would be a tiny bit more elegant to only prevent enter from submitting the form when the focus is in the autocomplete input field.
Edit:
To only prevent enter from submitting the form when the cursor is in the autocomplete field can be achieved by using Ankit Singh's solution and modifying it a bit:
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()" method="post" (keydown.enter)="$event.target.id != 'skill_string'" *ngIf="!showSuccessMessage">
(Note: The condition has to return false to prevent the default action to be triggered)
Of course we then need our regular submit button again (without the click event attached, or the form will submit twice):
<button type="submit" class="btn btn-primary pull-right" [disabled]="!profileForm.valid">Save</button>
You could also check the event.target.classList if you want to use a .autocomplete class. Or move the checking logic to a function into which you pass the $event.
Events in Angular 2 behave like normal DOM events. To capture the event object, pass $event as a parameter in the event callback from the template:
Html:
<button (keyup.enter)="skillsHandleEnter($event, skillString)"></button>
JavaScript using Event.preventDefault():
#Component(...)
class MyComponent {
skillsHandleEnter(event, skillString) {
event.preventDefault();
// ... your logic
}
}
Prevent a form from submittion on enter or by clicking a button
<textarea (keydown.enter)="provoked($event)"></textarea>
<button (keydown.enter)="provoked($event)" (click)="provoked($event)"></button>
provoked($event) {
$event.preventDefault()
}
in case if you need to pass data to the method, then try
<textarea (keydown.enter)="provoked($event, data)"></textarea>
<button (keydown.enter)="provoked($event, data)" (click)="provoked($event, data)"></button>
provoked($event, data) {
$event.preventDefault()
// process the data here
}
This is to some extent a dupe of this question but hopefully less convoluted.
Is it possible to to add a completely new FormGroup via a form input where the new FormGroupName can be defined via input? If so which directives or functions would I use to achieve this?
User Case:
The user can define a new FormGroupName via input field and click to extend the form with the new FormGroup, then fill out values for that FormGroup’s FormControls.
1 User fills out an existing 'name' field as normal.
2 Types ‘foo’ into an input field
3 Clicks ‘add data group’ to create the new FormGroup 'foo'
4 The form reveals input fields for 'height' and 'weight' for the new FormGroup foo.
5 User fills out 'height' and 'weight' values for 'foo'
6 Repeats step 2-5 for 'bar'
7 Submits:
{
"name":"Data Form",
"foo":{
"height":"6.00",
"weight":"300"
},
"bar":{
"height":"5.11",
"weight":"260"
}
}
The FormControls in the added FormGroups will always be consistent.
So as in the above example always ‘weight’ and ‘height’.
The intention is to use the form as a basic UI to help generate some JSON data.
Thanks.
The solution I came up with was to use a dynamic [formGroupName] which references an array which is updated each time a new group is added
[formGroupName]="myGroupName[i]"
app.component.ts :
import { Component, OnInit } from '#angular/core';
import { Customer } from './customer.interface';
import { FormControl, FormBuilder, FormGroup, FormArray, Validators } from '#angular/forms';
#Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app.component.html',
})
export class AppComponent implements OnInit {
public myForm: FormGroup;
myGroupName = ['test'];
constructor(private _fb: FormBuilder) {
}
ngOnInit() {
this.myForm = this._fb.group({
myArray: this._fb.array([
this._fb.group({
test: this._fb.group({
name: ['test'],
title: [''],
link: [''],
cta: [''],
})
}),
])
});
}
initArray(nameObj:any) {
return this._fb.group({
[nameObj]: this._fb.group({
name: [nameObj],
title: [''],
link: [''],
cta: [''],
})
})
}
addArray(newName:string) {
const control = <FormArray>this.myForm.controls['myArray'];
this.myGroupName.push(newName);
control.push(this.initArray(newName));
document.getElementById('newName').value='';
}
removeDataKey(i: number) {
const control = <FormArray>this.myForm.controls['myArray'];
control.removeAt(i);
this.myGroupName.splice(i,1);
}
}
app.component.html:
<form [formGroup]="myForm" novalidate (ngSubmit)="save(myForm)">
<div formArrayName="myArray">
<div *ngFor="let myGroup of myForm.controls.myArray.controls; let i=index" class="panel panel-default">
<div [formGroupName]="i" class="panel-body">
<span *ngIf="myForm.controls.myArray.controls.length > 1"
(click)="removeDataKey(i)" class="glyphicon glyphicon-remove pull-right">
</span>
<h5 >Group {{i + 1 }}</h5>
<h3> {{myGroupName[i] }}</h3>
<!--[formGroupName]="myGroupName[i]"-->
<div [formGroupName]="myGroupName[i]" class="panel-body">
<div class="form-group">
<label>Title</label>
<input type="text" class="form-control" formControlName="title" >
</div>
<div class="form-group">
<label>Link</label>
<input type="text" class="form-control" formControlName="link" >
</div>
<div class="form-group">
<label>Cta</label>
<input type="text" class="form-control" formControlName="cta" >
</div>
</div>
<!--[formGroupName]="myGroupName[i]"-->
</div>
<!--[formGroupName]="i" -->
</div>
</div>
<!-- myArray array-->
<div class="margin-20">
<input #newName
(keyup.enter)="addName(newName.value)"
type="text" style="width:30%" id="newName">
<a (click)="addArray(newName.value)" style="cursor: default" class="btn">Add Group +</a>
</div>
</form>
The dupe of this question is also answered here with related plunks