I have a sample form in angular
app.html
<form [ngFormModel]="newListingForm" (ngSubmit)="submitListing(newListingForm.value)">
<input type="radio" #typeRadio="ngForm" [ngFormControl]="newListingForm.controls['typeRadio']" value="Event" id="type-event_radio" required>
<input type="radio" #typeRadio="ngForm" [ngFormControl]="newListingForm.controls['typeRadio']" value="Venue" id="type-venue_radio" required>
<input type="text" placeholder="New Title" #mainTitleInput="ngForm" [ngFormControl]="newListingForm.controls['mainTitleInput']" id="main-title_input" class="transparent">
<input type="email" #emailAddressInput="ngForm" [ngFormControl]="newListingForm.controls['emailAddressInput']" id="email-address_input" required>
<!-- etc -->
</form>
app.ts
import {Component} from 'angular2/core';
import {FORM_DIRECTIVES, FormBuilder, ControlGroup, Validators} from 'angular2/common';
#Component({
templateUrl : 'app/components/new-listing/app.html',
directives: [FORM_DIRECTIVES]
})
export class NewListingComponent {
//Helpers
newListingForm: ControlGroup;
//Costructor
constructor(
fb: FormBuilder
){
//New Listing Form
this.newListingForm = fb.group({
'typeRadio': ['', Validators.required],
'mainTitleInput': ['', Validators.required],
'emailAddressInput': ['', Validators.required],
});
}
//TODO Form submission
submitListing(value: string): void {
console.log("Form Submited");
}
}
At the moment only validation I see is default one provided by google on required fields. it all goes away. I've been looking into docs and min/max length seems to be easy to implement with my current setup, however there is another interesting asp NG_VALIDATORS Which I think (not sure) provides validation by looking at things like type="email", required etc.. inside a form. Essentially I want to be able to display messages like invalid email this field is required etc.. through angular2..
Simply use expression like this (sample with Bootstrap) to leverage attributes (valid or not, errors, ...) of the controls you associated with your inputs:
<form [ngFormModel]="newListingForm">
<!-- Field name -->
<div class="form-group form-group-sm"
[ngClass]="{'has-error':!newListingForm.controls.mainTitleInput.valid}">
<label for="for"
class="col-sm-3 control-label">Name:</label>
<div class="col-sm-8">
<input [ngFormControl]="newListingForm.controls.mainTitleInput"
[(ngModel)]="newListing.mainTitleInput"/>
<span *ngIf="!newListingForm.controls.mainTitleInput.valid"
class="help-block text-danger">
<span *ngIf="newListingForm.controls.errors?.required">
The field is required
</span>
</span>
</div>
</div>
</form>
With this sample, fields with errors will be displayed in red and error messages is dynamically displayed.
Moreover you add implement custom validators for your fields:
export class CityValidator {
static validate(control: Control) {
var validValues = [ 'mycity', ... ];
var cnt = validValues
.filter(item => item == control.value)
.length;
return (cnt == 0) ? { city: true } : null;
}
}
Simply add then into your validators for fields:
this.newListingForm = fb.group({
'myfield': ['', Validators.compose([
Validators.required, CityValidator.validate])]
(...)
});
Hope it helps you,
Thierry
#Ilja you are on good track, improve your code with this:
this.newListingForm = fb.group({
'typeRadio': ['', Validators.compose([Validators.required, Validators.minLength(2), Validators.maxLength(40)])],
'mainTitleInput': ['', Validators.compose([Validators.required, Validators.minLength(2), Validators.maxLength(40)])],
'emailAddressInput': ['', Validators.compose([Validators.required, Validators.minLength(2), Validators.maxLength(40)])],
});
Consider this example ( with custom email Validator )
https://plnkr.co/edit/5yO4HviXD7xIgMQQ8WKs?p=info
Related
i have a component.ts that looks like this :
import { Component, OnInit } from '#angular/core';
import {FormBuilder, FormGroup, Validators} from "#angular/forms";
import {Observable, Subscription} from "rxjs";
import {ArticleService} from "../article.service";
#Component({
selector: 'app-article-new',
templateUrl: './article-new.component.html',
styleUrls: ['./article-new.component.css']
})
export class ArticleNewComponent implements OnInit {
response$?: Subscription;
constructor(private formBuilder: FormBuilder, private articleService: ArticleService) { }
articleForm: FormGroup = this.formBuilder.group({
title: ['', Validators.required],
content: ['', [Validators.required, Validators.minLength(69)]]
})
ngOnInit(): void {
}
async submit(){
console.log('article / sub', this.articleForm.value);
this.response$ = await this.articleService.createArticle(this.articleForm.value).subscribe(
res => console.log(res)
);
}
get title() {
return this.articleForm.get('title');
}
get content() {
return this.articleForm.get('content');
}
}
And the html of the component :
<h3>Create New Article</h3>
<form [formGroup]="articleForm" (ngSubmit)="submit()">
<div class="form-group">
<label for="title">Title :</label>
<input type="text" formControlName="title" class="form-control" id="title" required>
<div *ngIf="title?.invalid" class="alert alert-danger">
Title is required
</div>
</div>
<div class="form-group">
<label for="content">Content :</label>
<textarea class="form-control" formControlName="content" id="content" rows="3" required></textarea>
<div *ngIf="content?.errors && (content?.errors['required'] !== null)" class="alert alert-danger">
Content is required
</div>
<div *ngIf="content?.errors && (content?.errors['minLength'] !== null)" class="alert alert-danger">
Minimum length
</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
As you can see, i have 3 divs with *ngIf. The first one with "Title" gave me a similar error that i solved by adding "?" after the variable name.
But with the content variable, adding the question mark didn't change anything. So i have that error for those 2 divs :
TS2533: Object is possibly 'null' or 'undefined'.
Yes, this is strict checking and having "long" paths like (content?.errors['required'] !== null will cause such things. It should clear up by using
content?.errors?.required
but I do prefer using hasError instead, so that is what I would suggest you to use, better readability in my opinion ;) So that would mean simply
content?.hasError('required')
and that can be applied to every formcontrol in your form, any validators. Please not that there is a gotcha with minLenght and maxLength. The L needs to be lowercase, so:
content?.hasError('minlength')
Try to use this :
articleForm= new FormGroup({
title: new FormControl('', Validators.required),
content: new FormControl('', [Validators.required, Validators.minLength(69)])
});
See this documentation :
https://www.concretepage.com/angular-2/angular-2-formgroup-example
Move the form creation logic inside ngOnInit hook
ngOnInit(): void {
this.buildForm();
}
buildForm():void{
this.articleForm = this.formBuilder.group({
title: ['', Validators.required],
content: ['', [Validators.required, Validators.minLength(69)]]
})
}
and add check in html part for articleForm
<form *ngIf="articleForm" [formGroup]="articleForm" (ngSubmit)="submit()">
A traditional ReactiveForm you specify all inputs and add formControls and validation to those inputs on the related component HTML file. I am moving some of these inputs into their own components so they become sharable and reusable.
In my example StackBlitz there is already logic to use the validation to disable/enable the search input based on the form validation. However, now that I have moved one of these inputs into its own component, that relationship of being in the same formBuilder form for validation purposes no longer applies.
component.ts
this.registerForm = this.formBuilder.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
// password: ['', [Validators.required, Validators.minLength(6)]]
});
I've commented out the password input as I am no longer building it in this form, however I still want to know its validation and apply that to this form so that search will only enable once all 3 inputs have been filled in and pass validation rules. Currently you only have to complete first and last name to enable the search input field.
Password now looks like this :
HTML
<password-input label="Password" [value]=""></password-input>
We can inject ControlContainer inside password-input component to get access to parentFormgroup. Then we can add password form control to existing formGroup dynamically.
component.ts
import { Component, OnInit, Input } from '#angular/core';
import { ControlContainer, FormControl, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'password-input',
templateUrl: './passwordinput.component.html'
})
export class PasswordInputComponent implements OnInit {
#Input('value') value = '';
#Input('label') label = 'test label';
control: FormControl;
formGroup:FormGroup;
constructor(private controlContainer:ControlContainer) {}
ngOnInit() {
const parentForm = (this.controlContainer['form'] as FormGroup);
parentForm.addControl('password',new FormControl(this.value,[Validators.required, Validators.minLength(6)]));
this.control = parentForm.get('password') as FormControl;
}
}
component.html
<div class="form-group">
<label>Password</label>
<input type="password" [formControl]="control" class="form-control" />
</div>
Working Example
on focus, when I click on one input field I want and if it is not valid to be validation disappears, but when we move to another field the same validation appears. It means only the focus of the current field to take off.
component.ts code
createForm() {
this.contactForm = this.fb.group({
firstName: [null, [Validators.required, Validators.minLength(0),
Validators.maxLength(150)]],
lastName: [null, [Validators.required, Validators.minLength(0),
Validators.maxLength(150)]]
});
}
component.html code
<div class="col-lg-2 col-md-2 col-sm-2 pl-1">
<label class="customer-title" for="account">
First Name<span class="text-danger">*</span>
</label>
<input
name="account"
class="form-control customer-input-field"
placeholder="First Name"
[ngClass]="{'is-invalid': f.firstName.touched && f.firstName.errors }"
formControlName="firstName"/>
<div class="text-danger error-text mt-1"
*ngIf="f.firstName.touched && f.firstName.errors?.required">
First Name is required
</div>
<div
class="text-danger error-text mt-1"
*ngIf="f.firstName.errors?.maxlength">
First name must be less than 150 characters long
</div>
</div>
I dont like the idea of removing validation on focus, because you will potentially allow at least one field to be invalid when a user submits his data.
It is possible with a directive:
import { Directive, ElementRef, HostListener } from "#angular/core";
import { NgControl, ValidatorFn } from "#angular/forms";
#Directive({
selector: "[no-val-on-focus]"
})
export class NoValidationOnFocusDirective {
constructor(
private el: ElementRef<HTMLInputElement>,
private control: NgControl
) {}
private validator: ValidatorFn | null = null;
#HostListener("focus", ["$event"])
public onFocus(): void {
this.validator = this.control.control.validator;
this.control.control.clearValidators();
this.control.control.updateValueAndValidity();
}
#HostListener("blur", ["$event"])
public onBlur(): void {
this.control.control.setValidators(this.validator);
this.control.control.updateValueAndValidity();
}
}
The proceudeure is simple:
On focus we store the validation function in a private member of the directive and clear all validators on the control.
On blur we set the validator of the control to the original function.
Stackblitz example
I'm testing a simple login functionality on my products-page component form using Angular 7 and I'm having this strange behaviour. I am trying to display the appropriate validation messages if an error exists, like if it is not a valid email, the " must be a valid email " message should display, and if the field is left blank, the " email is required ", error should display, and when you start typing, the required message disappears and only the valid email error message shows. This is my code.
Addproduct.component.html
So now I'm trying to render the span-messages if the error exists but it's not working.
<form [formGroup]="loginForm" class="login-container" (ngSubmit)="login()">
<p>
<input class="form-control" type="email" placeholder="Email here" formControlName="email">
<span *ngIf="f.email.errors.required">email is required</span>
<span *ngIf="f.email.errors.email">must be a valid email</span>
</p>
<p>
<input class="form-control" type="password" placeholder="Password here" formControlName="password">
<span *ngIf="f.password.errors.required">Password is required</span>
</p>
<p><button type="submit" class="btn btn-md btn-primary">Submit</button></p>
</form>
Addproduct.component.ts
and this is the controller, I tried using the short notation for the validation rules but yet to no avail.
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'app-addproduct',
templateUrl: './addproduct.component.html',
styleUrls: ['./addproduct.component.css']
})
export class AddproductComponent implements OnInit {
loginForm:FormGroup;
isSubmitted:boolean = false;
constructor(
private router:Router,
private fb:FormBuilder
){}
ngOnInit() {
this.loginForm = this.fb.group({
email : ['', [Validators.required,Validators.email]],
password : ['', [Validators.required,Validators.min(6)]]
});
}
get f(){
return this.loginForm.controls;
}
}
I also changed the validation scripts to this but still to no avail
ngOnInit() {
this.loginForm = this.fb.group({
email : new FormControl('', [Validators.required,Validators.email]),
password : new FormControl('', [Validators.required,Validators.min(6)])
});
}
and I am getting this error
You are checking for the presence of an error where no error might exist.
You want something like this:
f.email.errors?.required
Or even:
f.email?.errors?.required
Do the same for the password field and anywhere else where the property might not exist when it is first called.
You can also use
<span *ngIf="loginForm.hasError('required', ['password'])">Password is required</span>
In my opinion it is a lot more easier.
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, FormArray, Validators, ValidatorFn, AbstractControl } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
myForm: FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.buildForm();
}
get vehicleGroup(): FormArray {
return <FormArray>this.myForm.get('vehicleGroup');
}
buildForm(): void {
this.myForm = this.fb.group({
name: ['', Validators.required],
vehicleGroup: this.fb.array([
this.fb.group({
vehicle: ['', Validators.required]
})
], [Validators.required]),
});
}
addVehicles(): void{
const itemToAdd = this.fb.group({
vehicle: ['', Validators.required]
});
this.vehicleGroup.push(itemToAdd);
}
deleteVehicle(i: number){
this.vehicleGroup.removeAt(i);
}
save(): void{
console.log('save');
}
}
<form novalidate (ngSubmit)="save()" [formGroup]="myForm">
<div class="form-group">
<label for="name">Name</label>
<input id="name" type="text" formControlName="name">
</div>
<div class="form-group">
<label for="vehicle">Vehicle</label>
<div formArrayName="vehicleGroup" *ngFor="let vehicle of vehicleGroup.controls; let i=index">
<div class="form-group" [formGroupName]="i">
<div>
<input id="{{'vehicle'+i}}" type="text" formControlName="vehicle">
<button type="button" (click)="deleteVehicle(i)"
*ngIf="vehicleGroup.length >= 2">remove
</button>
</div>
</div>
</div>
<div class="form-group">
<button type="button" class="link-button" [disabled]="!vehicleGroup.valid" (click)="addVehicles()">
+ Add more vehicles
</button>
</div>
</div>
</form>
I have this (stackBlitz) simple form created with angular formBuilder
I simple need to how to validate each elements of the dynamic formArray and display a unique message for each of them if that particular field is not valid.
I tried several solutions and also tried custom validator function with return an ValidatorFn. with that I could simply validate the formArray, but It's not good enough for my scenario and still I can not display messages according to the validate functions behavior. How ever to narrow it down, I simply need to know if there is a better way to validate each dynamic elements of this formArray. these are the validate rules.
each filed value should be unique.
need to validate real time
after adding few elements, someone edit previously added field, it also should be validate in real time with every other field values(this is where I got struck, I could validate upwards from the editing field, but not the fields below the editing field is validated accordingly)
If some one can show me some way to achieve this in a right way, It would be really great, since I'm struck with this for almost 3 days now, and still can't get an better solution.
I have used unique validator of #rxweb/reactive-form-validators in my project. It can be used directly in the component without creating any custom validation function.
You can edit your addVehicles method as follows:
addVehicles(): void{
const itemToAdd = this.fb.group({
vehicle: ['', RxwebValidators.unique()]
});
this.vehicleGroup.push(itemToAdd);
}
and adding
ReactiveFormConfig.set({"validationMessage":{"unique":"Enter unique value in the input"}});
to ngOnInit.
Here is the forked stackblitz