Simple require validation inside Custom Component input - javascript

I have multiple components and each having some input, select and other form elements. This all elements developed using template driven form.
But previously it was developed not in component wise, so we were able to check form status. Now as we have divided everything by component but validation we are not able to get it.
I have two questions.
For getting child component form elements to parent component, I am using ControlValueAccessor, is this correct way to do it ? In my child component i have multiple form controls like input, select and other.
Simple validations like required on input element, its not working inside child component. What should i do ?
This is Plunkr which mostly demonstrates what i wanted to achieve.
#Component({
selector: 'my-child',
template: `
<h1>Child</h1>
<input [ngModel]="firstName" (ngModelChange)="firstSet($event)" required>
<input [ngModel]="lastName" (ngModelChange)="lastSet($event)" required> <!-- required validation is not working -->
`,
providers: [
{provide: NG_VALUE_ACCESSOR, useExisting: Child, multi: true}
]
})
export class Child implements ControlValueAccessor, OnChanges {
private firstName: string,
private lastName: string;
fn: (value: any) => void;
constructor(){
}
writeValue(value: any) {
console.log("write value");
console.log(value);
if(value){
this.firstName = value.firstName;
this.lastName = value.lastName;
}
}
registerOnChange(fn: (value: any) => void) {
this.fn = fn;
}
registerOnTouched() {}
firstSet(v: string){
this.firstName = v;
this.fn({
firstName: this.firstName,
lastName: this.lastName
});
}
lastSet(v: string){
this.lastName = v;
this.fn({
firstName: this.firstName,
lastName: this.lastName
});
}
}
If you find wrong way please mention which is correct way to do it ?
Note: Please suggest solution which works on Template Driven Form. and i forgot to mention i am using Angular 2.4.8 version.

Not sure why you wouldn't use Reactive Forms - they are pretty much designed for validation.
You can also mix and match , you aren't locked into using Template Driven Forms just because you are using ngModel.
You can utilise Reactive Forms just for validation.
component.ts
form: FormGroup;
constructor(fb : FormBuilder) {
this.form = fb.group({
'firstName' : ['', Validators.required],
'lastName' : ['', Validators.required]
})
}
hasError(n: string){
return this.form.get(n).hasError('required') && this.form.get(n).touched;
}
template.html
<h1>Child</h1>
<input [ngModel]="firstName" name="firstName" [formControl]="form.get('firstName')" required>
<div *ngIf="hasError('firstName')">This field is required</div>
<input [ngModel]="lastName" name="lastName" [formControl]="form.get('lastName')" required> <!-- required validation is not working -->
<div *ngIf="hasError('lastName')">This field is required</div>
Plunkr found here

Related

Send validation from a componentised ReactiveFrom input to another ReactiveForm

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

Angular - components inside form - formControlName must be used with a parent

I wanted to reuse a single component to dynamically generate form input fields and came up with this code
form-field.component.html
<label *ngIf="formLabel">{{formLabel}}</label>
<input
type="text"
class="form-control"
[formControlName]="formFieldName"
[ngClass]="{
'is-invalid': formField.dirty && formField.errors
}"
[placeholder]="placeholder"
/>
<div
*ngIf="formField.dirty && formField.errors"
class="invalid-feedback"
>
<span *ngIf="formField.errors.required">
{{requiredMessage}}
</span>
<div *ngIf="formField.errors.pattern">
<span *ngFor="let message of patternErrorMessages">
{{message}}
</span>
</div>
</div>
form-field.component.ts
#Input() formField: AbstractControl;
#Input() patternErrorMessages: string[];
#Input() formLabel: string;
#Input() formFieldName: string;
#Input() placeholder: string;
#Input() form: FormGroup;
requiredMessage = FORM_REQUIRED;
constructor() { }
ngOnInit(): void {
this.patternErrorMessages = this.patternErrorMessages.length === 0 ? ['Pattern Mismatch'] : this.patternErrorMessages;
}
parent-component.html
<form (ngSubmit)="validSubmit(myFormGroup)" [formGroup]="myFormGroup">
<div class="form-group">
<app-form-field
[formField]="myFormGroup.controls.name"
[patternErrorMessages]="['Only Alphabets and Numbers are allowed.']"
[formLabel]="'Name'"
[placeholder]="'Enter Name'"
[form]="myFormGroup"
>
</app-form-field>
</div>
</form>
And I'm getting this error while calling it.
ERROR Error: formControlName must be used with a parent formGroup directive. You'll want to add a formGroup
directive and pass it an existing FormGroup instance (you can create one in your class).
If you want to have custom form controls, make them implement ControlValueAccessor from #angular/forms.
export interface ControlValueAccessor {
writeValue(obj: any) : void;
registerOnChange(fn: any) : void;
registerOnTouched(fn: any) : void;
}
Then register the components to the NG_VALUE_ACCESSOR token:
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyAwesomeComponent(),
multi: true
}
]
Rant:
Take a step back and look at what you are doing. You are trying to build a unifying layer above form controls.
Do you want to repeat this for textareas, number inputs, email inputs...? How many validation error messages do you plan to add? What about their i18n? And the list goes on...
Your code will be no less complex than what you are trying to replace.
Do yourself a favor, abandon that ship.
I think you forgot to add formGroup in form-field.component.html.
Wrap your form-field in:
<div [formGroup]="form">
...
</div>
Use a formControl="YourFormControlName" instead of formControlName for single control. For grouped controls, you always need a formGroup parent. And, when using formControl, you have to add a name and id attribute.

validating and display error messages for specific form controller in a form array angular

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

Angular 6 Required only one field from many fields Reactive Form

I am new in angular. I have one scenario where I need only one field required from 5 fields in the form, means if the user fills at least one field then form makes valid.
Thanks in advance.
Since you need to check for the validity of whole form only if one of the fields is non empty , You can manually set the validity like below :
if(!this.valid){
this.form.setErrors({ 'invalid': true});
}else{
this.form.setErrors(null);
}
Where this.valid is your condition based on which you can set the validity
You can check the example : https://angular-exmphk.stackblitz.io
You can also check the answer : FormGroup validation in "exclusive or" which does form validation based on some condition
Hope this helps
See Custom Validators and Cross-field validation in https://angular.io/guide/form-validation
Exact example from our app, where at least one phone number field must be entered. This is a Validator Function, i.e. implements https://angular.io/api/forms/ValidatorFn
import { AbstractControl } from "#angular/forms";
import { Member } from "../../members/member";
export function phone(control: AbstractControl): {[key: string]: any} {
if (!control.parent) return;
const form = control.parent;
const member: Member = form.value;
if (member.contactphone || member.mobile || member.contactphonesecond) {
[
form.controls['contactphone'],
form.controls['mobile'],
form.controls['contactphonesecond']
].forEach(control => {
control.setErrors(null);
});
} else return {'noPhone': 'None of contact phones is specified'};
}
Member is our class that defines all the form fields, your code will be different but the example of the custom validator should help.
Check this example of phone number validator
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { NumberValidator } from '../validators/form-validators';
constructor(
private fb: FormBuilder){}
FormName: FormGroup = this.fb.group({
phoneNumber: ['', NumberValidator]
});
in form-validator file
import { AbstractControl, ValidatorFn } from '#angular/forms';
export const NumberValidator = (): ValidatorFn => {
return (control: AbstractControl): { [key: string]: any } | null => {
const mobileRegex = /^[()-\d\s]*$/g;
const result = mobileRegex.test(control.value);
return result ? null : { mobileInvalid: { value: control.value } };
};
};
let me know if you have any doubts.
<form [formGroup]="formdata">
<div class="form-group">
<label for="fieldlabel1">fieldLabel1</label>
<input type="text" id="fieldlabel1" formControlName="fieldName1" class="form-control"><br>
<label for="fieldlabel2">fieldLabel2</label>
<input type="text" id="fieldlabel2" formControlName="fieldName2" class="form-control">
</div>
</form>
<div class="row">
<div class="col-sm-12">
<button type="submit" value="submit" (click)="somefunctioncall()" [disabled]="formdata.invalid">
Submit
</button>
</div>
</div>
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { OnInit } from '#angular/core';
export class test {
formdata: FormGroup;
ngOnInit() {
this.formdata = new FormGroup({
fieldName1: new FormControl("", Validators.compose([
Validators.required
])),
fieldName2: new FormControl("", Validators.compose([
// remove required validation for one you dont need.
]))
})
}
}

Angular 4 - Reactive Forms enable/disable not working

I am having trouble enabling and disabling a form controls in my application. It is probably because the form is being enabled/disabled in an asynchronous context.
Here is how the code is.
user.component.html
<form [formGroup]="form">
<input type="text" id="name" formControlName="name" />
<input type="text" id="age" formControlName="age" />
</form>
user.component.ts
ngOnInit() {
this._route.queryParams.subscribe(params => {
getUser(params.userId).subscribe(user => {
this._buildForm(user);
})
});
}
private _buildForm(user){
this.form = _fb.group({
name: [{value: user.name, disabled: user.someCondition}, Validators.required],
age: [{value: user.age, disabled: user.anotherCondition}, Validators.required]
})
}
When the first time the user is loaded upon param change, the controls do get enabled/disabled on the basis of their relevant conditions. When the params change subsequently, the state of the controls remain the same although the values are set appropriately.
I have tried different ways to fix this but no help. For example, I have tried the following at the end of the _buildForm method.
this.form.disable() //Doesn't do anything
this.form.controls.name.disable(); //Doesn't help
One thing that does work as expected is the following (but this isn't what is required).
<button (click)="form.disable()" value="Disable Form" />
<button (click)="form.enable()" value="Enable Form" />
What I feel is that the problem is due the fact the _buildForm() is being called from the asynchronous context (subscribe method).
How can I fix this problem?
UPDATE
Please note that the Observable subscription is triggered on the basis of the navigation as below.
this._router.navigate([], {
relativeTo: this._route,
queryParams: {userId: 10}
})
UPDATE 2
https://angular-cvf1fp.stackblitz.io
This is an easier way to understand my problem
You just need enable/disable methods of form control.
Here is stackblitz example. It works perfectly.
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
group;
isDisabled = true;
constructor(fb: FormBuilder) {
this.group = fb.group({
stuff: fb.control({value: 'stuff', disabled: this.isDisabled})
});
}
toggle() {
this.isDisabled = !this.isDisabled;
this.group.controls.stuff[this.isDisabled ? 'enable' : 'disable']();
}
}
You're right. The subscription is only fired once, so after submitting the form the user entity does not change anymore. To do it, add for instance an event handler to your form submit.
user.component.html
<form [formGroup]="form" (submit)="_updateForm()">
<input type="text" id="name" fromControlName="name" />
<input type="text" id="age" fromControlName="age" />
</form>
user.component.ts
private userId: any;
ngOnInit() {
this._route.queryParams.subscribe(params => {
this.userId = params.userId;
this._updateForm();
});
}
private _buildForm(user){
this.form = _fb.group({
name: [{value: user.name, disabled: user.someCondition}, Validators.required],
age: [{value: user.age, disabled: user.anotherCondition}, Validators.required]
});
}
// The update function has to be public to be called inside your template
public _updateForm(){
getUser(this.userId).subscribe(user => {
this._buildForm(user);
});
}
Create the formGroup directly in ngOnit w/controls disabled by default so you're sure it exists w/o worrying about async issues.
In this._route.queryParams.subscribe(...) you can use this.form.setValue({//set all control values}) or this.form.patchValue({//set individual controls}) to update the individual controls as needed/once available.
Use component instance variables for condition and someOtherCondition (or a user object as a whole if you want to separate your data model from the form's model - they don't have to be the same). Initialize these to "false", and refer to them instead for enabling/disabling the controls in the formGroup creation. You can also update those variables in the same .subscribe() block once the user data is in place, instead of waiting to access user data model directly.
This way your controls and formGroup exist, and are disabled, until the async data is available.
<form [formGroup]="form">
<input type="text" id="name" fromControlName="name" />
<input type="text" id="age" fromControlName="age" />
</form>
do you see a typo here?fromControlName should be formControlName
After that change toggling will work ;)
https://angular-rivxs5.stackblitz.io

Categories