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()">
Related
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
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
I am using Angular version 5.0.1 and I'm trying to fire an AsyncValidator on a FormGroup on a blur event on one of the FormControls in the FormGroup.
I can get onBlur to work on a form control using the following:
name: ['', {updateOn: 'blur'}]
However, when I try to apply it to a FormGroup it doesn't work.
Is it possible to only fire an AsyncValidator onBlur on a FormGroup, and if not, what is the best way to execute the validator only when the user has finished entering data?
My only other option at the moment is to us some sort of debounce wrapper for my validator.
Just reading through here and it appears I should be able to use updateOn: 'blur' for a form group.
After
new FormGroup(value, {updateOn: 'blur'}));
new FormControl(value, {updateOn: 'blur', asyncValidators: [myValidator]})
In Angular5 Specifying the update mode is possible for both types of forms: template-driven forms and reactive forms.
First one is working for you. Here's the working example for reactive forms
Component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
#Component({
selector: 'form01',
templateUrl: './student.component.html',
styleUrls: ['./student.component.scss']
})
export class StudentComponent implements OnInit {
formTitle:string="Student registration ";
nameForm: FormGroup;
constructor() {
}
ngOnInit() {
this.nameForm = new FormGroup ({
firstname: new FormControl('', {
validators: Validators.required,
asyncValidators: [yourAsyncValidatorFunction],
updateOn: 'blur'
}),
lastname: new FormControl('', {
validators: Validators.required,
asyncValidators: [yourAsyncValidatorFunction],
updateOn: 'blur'
})
});
}
}
HTML
<form [formGroup]="nameForm" novalidate>
<div class="form-group">
<label>What's your first name? </label>
<input class="form-control" formControlName="firstname">
</div>
<div class="form-group">
<label>What's your last name?</label>
<input class="form-control" formControlName="lastname">
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
<h3>Hello {{nameForm.value.firstname}} {{nameForm.value.lastname}}</h3>
It works on my side. Be aware that the FormBuilder may not support this yet.
this.formGroup = new FormGroup({
name: new FormControl('', {validators: []})
}, {
updateOn: 'blur'
});
I use this code snippet combining AbstractControlOptions with FormBuilder:
constructor(private formBuilder: FormBuilder) { }
this.signUpForm = new FormGroup(
this.formBuilder.group({
'email': ['', ValidationUtils.emailValidators()],
'fullname': ['', ValidationUtils.fullnameValidators()],
'idCard': ['', ValidationUtils.idCardValidators()],
'username': {value: '', disabled: true}
}).controls, {
updateOn: 'blur'
}
);
you can add this directive
import { Directive,Output, HostListener, EventEmitter } from '#angular/core';
#Directive({
selector: '[onBlur]'
})
export class OnBlurDirective {
// #Input('onBlur') onBlurFunction: Function;
#Output('onBlur') onBlurEvent: EventEmitter<any> = new EventEmitter();
constructor() { }
#HostListener('focusout', ['$event.target'])
onFocusout(target) {
console.log("Focus out called");
this.onBlurEvent.emit()
}
}
and use it in the HTML like this
<input type="text" (onBlur)="myFunction()"/>
and in the component you can define the function myFunction as you want
I am learning promises and I am trying to get a simple example to work but I get the error. The promises role is just to check whether a certain name has been entered and produce a boolean error on call back
Cannot read property 'shouldBeUnique' of null
Here is my component
import {Component, Inject} from '#angular/core';
import {FormGroup, Validators, FormBuilder} from '#angular/forms';
import {UsernameValidators} from './usernameValidators'
#Component({
selector: 'signup-form',
templateUrl: 'app/signup-form.component.html'
})
export class SignUpFormComponent {
form: FormGroup;
constructor(#Inject(FormBuilder) fb: FormBuilder) {
this.form = fb.group({
username: ['', Validators.compose([
Validators.required,
UsernameValidators.cannotContainSpace
]), UsernameValidators.shouldBeUnique],
password: ['', Validators.required]
})
}
get username(): any {return this.form.get('username');}
get password(): any {return this.form.get('password');}
}
Here is my component.html
<form [formGroup]="form" (ngSubmit)="signup()">
<div class="form-group">
<label for="username">Username</label>
<input
id="username"
type="text"
class="form-control"
formControlName="username" placeholder="Username"
>
<div *ngIf="username.touched && username.errors">
<div *ngIf="username.errors.shouldBeUnique" class="alert alert-danger">This username is already taken</div>
</div>
</form>
Here is my validator class where the promise is being made
import {FormControl} from '#angular/forms';
export class UsernameValidators {
static shouldBeUnique(control: FormControl) {
return new Promise((resolve, reject) => {
setTimeout(function(){
if (control.value == "mosh")
resolve({ shouldBeUnique: true});
else
resolve(null);
}, 1000)
});
}
}
Thanks
Try using the safe navigation operator (?.) to guard against null and undefined values in property paths.
<div *ngIf="username.touched && username.errors">
<div *ngIf="username.errors?.shouldBeUnique" class="alert alert-danger">This username is already taken</div>
</div>
This should resolve the error you are currently running into. Read more in the Angular 2 docs here:
https://angular.io/guide/template-syntax#safe-navigation-operator
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