I have Root FormGroup that contains different different FormGroup .I want to apply validator on control between two or more FormGroup .
For Example:
I have to two FormGroup mobile and workPhone and each FormGroup has different controls such as number etc.
Now i have to write validator on both mobile and workPhone to become one optional on enter value in number control.
for more refrence please see the example on stackblitz.
how to achieve this Initial both control will be required. But when I enter the work phone number, the mobile number should become optional
And Vice Versa.
appcomponent.ts
import { Component, OnInit } from '#angular/core';
import {
AbstractControl,
FormBuilder,
FormControl,
FormGroup,
ValidationErrors,
ValidatorFn,
Validators,
} from '#angular/forms';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
rootForm!: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.rootForm = this.fb.group(
{
mobile: new FormGroup({
countryCode: new FormControl('+1'),
number: new FormControl('', [Validators.required]),
}),
workPhone: new FormGroup({
countryCode: new FormControl('+1'),
number: new FormControl('', [Validators.required]),
}),
},
{ Validators: EitherMobileOrWorkPhoneRequired }
);
}
public onSubmit(value) {
console.log('Form Invalid:' + this.rootForm.invalid);
}
}
export const EitherMobileOrWorkPhoneRequired: ValidatorFn = (
control: AbstractControl
): ValidationErrors | null => {
console.log(control);
return null;
};
If any other details required. Please let me know.
Thanks.
Related
I am creating an Edit Employee form and there is a button Add new skill which need to create new text box to add the skill.
But when I click on Add new skill, it is not adding any new text boxes and displaying these details inside the console.
My typescript code :
import { Component, OnInit } from '#angular/core';
import { FormArray, FormBuilder, NgForm, Validators } from '#angular/forms';
import { EmployeeDepartment } from '../employee-department-class';
#Component({
selector: 'app-edit-emp-form-builder',
templateUrl: './edit-emp-form-builder.component.html',
styleUrls: ['./edit-emp-form-builder.component.css']
})
export class EditEmpFormBuilderComponent implements OnInit {
/**
* Using some default values and may be different from other component
*/
department = [
new EmployeeDepartment(1, 'Payroll'),
new EmployeeDepartment(2, 'Internal'),
new EmployeeDepartment(3, 'HR')
]
skills: any = []
employeeDetails = this.fb.group({
employeeName: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(20)]],
department: [null, [Validators.required]],
skills: this.fb.array([
this.fb.control('')
])
});
constructor(private fb: FormBuilder) {
}
get skill() {
return this.employeeDetails.get('skills') as FormArray;
}
addSkill() {
this.skills.push(this.fb.control(''));
}
onSubmit(f: NgForm) {
console.log(f);
}
ngOnInit(): void {
}
}
My HTML code for the skill
<div formArrayName="skills">
<h3>Skill</h3>
<button (click)="addSkill()">Add new skill</button>
<div *ngFor="let s of skill.controls; let i = index">
<label>Skill:
<input type="text" [formControlName]="i">
</label>
</div>
</div>
Remove the variable skills: any = [] from your code.
Rename get skill() to get skills()
I am going to write some unit tests for my validator directive, but don't know how, even after googling and reading the Angular website. I will explain the codes (just the parts that are needed) here.
Here is my component.ts code:
import { Component, OnInit } from '#angular/core';
import { FormControl, FormGroup } from '#angular/forms';
import { isValidValidator } from '../../directives/is-valid.directive';
#Component({
selector: 'app-microplate',
templateUrl: './microplate.component.html',
styleUrls: ['./microplate.component.css']
})
export class MicroplateComponent implements OnInit {
form: FormGroup;
ngOnInit(): void {
this.form = new FormGroup({
columns: new FormControl('', [
isValidValidator()
])
});
}
}
Here is my directive.ts code:
import { Directive, Input } from '#angular/core';
import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn } from '#angular/forms';
const { isNumbersFieldValid } = require('../utils/utils.js');
export function isValidValidator(): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} | null => {
return isNumbersFieldValid(control.value) ? {isValid: {value: control.value}} : null;
};
}
#Directive({
selector: '[appIsValid]',
providers: [{provide: NG_VALIDATORS, useExisting: IsValidDirective, multi: true}]
})
export class IsValidDirective implements Validator {
#Input('appIsValid') isValid: string;
validate(control: AbstractControl): {[key: string]: any} | null {
return this.isValid ? isValidValidator()(control) : null;
}
}
Here is my directive.spec.ts code:
import { IsValidDirective } from './is-valid.directive';
describe('IsValidDirective', () => {
it('should create an instance', () => {
const directive = new IsValidDirective();
expect(directive).toBeTruthy();
});
// How can I write some unit tests here to check if my directive works well for different input strings?
});
here you can find how to test attribute directives
https://angular.io/guide/testing-attribute-directives
basically you need to create a testComponent in your spec files and apply your directive in that testComponent, your test expectation can be done on the testComponent directly
I have a reactive form that is basically this.
ngOnInit() {
this.myForm = this.fb.group({
sections: this.fb.array([])
});
}
addSection(){
let section = <FormArray>this.myForm.controls.sections;
section.push(this.fb.group({
name: '',
items: this.fb.array([]),
percentage: '',
}));
}
addSection() is a function that adds an element to my sections FormArray when i click something that's on my template
I sum up all percentages from every section inside the sections formArray and validate that it isn't bigger than 1 (I assume user is typing floating points in those specific inputs). Finally i want to disable the submit button at the end of the form if the sum if bigger than 1.
I tried the answer from this question but didn't work cause https://stackoverflow.com/a/48706808/8579973 cause it was meant for a group of object thats all the same. I need it to validate just the "percentage" element from every group that is made.
I also tried to store the sum in local storage, but I don't want any extra button that triggers that procedure.
Thanks for your answers,
Regards
Like this? Stackblitz: https://stackblitz.com/edit/angular-vdgdv2
import { Component} from '#angular/core';
import {FormBuilder, FormControl, FormArray, FormGroup, FormGroupDirective, NgForm, ValidatorFn, Validators} from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent{
myForm: FormGroup;
constructor(private fb: FormBuilder){
this.myForm = this.fb.group({
sections: this.fb.array([], CustomValidator.checkPercentageSum)
});
this.addSection();
}
addSection(){
let section = this.myForm.get('sections') as FormArray;
section.push(this.fb.group({
percentage: 0.2,
}));
section.push(this.fb.group({
percentage: 0.3,
}));
section.push(this.fb.group({
percentage: 1,
}));
console.log(this.myForm.valid , this.myForm.get('sections').errors);
// Output: false {Invalid: true}
}
}
//Custom Validator
export class CustomValidator {
static checkPercentageSum(sections: FormArray): ValidationResult {
if (sections) {
let sumOfPercentages: number = 0;
sections['controls'].forEach((sectionItem: FormGroup) => {
sumOfPercentages = sectionItem['controls'].percentage.value + sumOfPercentages;
});
if (sumOfPercentages > 1) {
return {"Invalid": true};
}
}
return null;
}
}
export interface ValidationResult {
[key: string]: boolean;
}
I have created a simple custom component in Angular 2 by implementing the CustomValueAccessor interface and it works fine. This component has just 1 input field in it. e.g. Postcode component
<postcode label="Post Code" cssClass="form-control" formControlName="postcode"> </postcode>
Now, I want to expand on this example and create an address component that has multiple input fields, line1, line 2, line3, postcode and country.
I have expanded the postcode example to include multiple fields and I can see the input component coming up on screen. However, the address component values are not being reflected in the host form.
Appreciate any pointer in this direction.
Example :
import { Component, OnInit, Input } from '#angular/core';
import { FormControl, FormGroup, ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR, Validators } from '#angular/forms';
import { Subscription } from 'rxjs/Subscription';
#Component({
selector: 'address',
templateUrl: './address.component.html',
styleUrls: ['./address.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: CommonAddressComponent,
multi: true
}
]
})
export class CommonAddressComponent implements OnInit , ControlValueAccessor {
addressForm : FormGroup
ngOnInit() {
this.addressForm = this.formBuilder.group({
line_1: '',
line_2: '',
});
}
/*private addressForm = new FormControl()*/
private subscription: Subscription;
public disabled: any = false;
constructor(private formBuilder: FormBuilder) { }
//model to view
writeValue(value: any) {
console.log("value = " + value)
this.addressForm.setValue(value);
}
registerOnChange(fn: (value: any) => void) {
console.log("registerOnChange = " + fn)
this.addressForm.valueChanges.subscribe(fn);
}
registerOnTouched() {}
}
Template file :
<div class="form-group" [formGroup]="addressForm">
<input class="form-control" type="text" formControlName="line_1" />
<input class="form-control" type="text" formControlName="line_2" />
</div>
Host Form component file:
import { Component, OnInit } from '#angular/core';
import { FormControl, FormGroup, Validators, FormBuilder} from '#angular/forms';
#Component({
selector: 'app-contacts',
templateUrl: './contacts.component.html',
styleUrls: ['./contacts.component.scss']
})
export class ContactsComponent implements OnInit {
contactsForm: FormGroup;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.contactsForm = this.fb.group({
name: 'test', // <--- the FormControl called "name"
postcode: 'tester111', // <--- the FormControl called "name"
line_3: '111', // <--- the FormControl called "name"*/
addressForm: new FormGroup({
line_1: new FormControl('I am line 1', Validators.minLength(2)),
line_2: new FormControl('I am line 2')
}),
});
}
ngOnInit() {
}
}
Host Form Component Template file:
<form [formGroup]="contactsForm">
<p>Form value: {{ contactsForm.value | json }}</p>
<p>Form status: {{ contactsForm.status | json }}</p>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div>
<postcode label="Post Code" cssClass="form-control" formControlName="postcode"> </postcode>
</div>
<div class="form-group">
<address-line cssClass="form-control" name="line3" label="line 3" elementName="line3"
elementID="line3" formControlName="line_3"> </address-line>
</div>
<!--<div [formGroup]="contactsForm.addressForm"> -->
<div >
<address formGroupName="addressForm"> </address>
</div>
</form>
After multiple attempts, I was able to get a custom control with multiple input fields in Angular working. Code for the same goes as follows:
Custom component with multiple input fields
import { Component, OnInit, Input, ViewChild } from '#angular/core';
import { FormControl,NgForm, FormGroup, ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR, Validators, NgModel } from '#angular/forms';
import { Subscription } from 'rxjs/Subscription';
#Component({
selector: 'address',
templateUrl: './address.component.html',
styleUrls: ['./address.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: AddressComponent,
multi: true
}
]
})
export class AddressComponent implements OnInit , ControlValueAccessor {
addressForm : FormGroup
#Input() label: string;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.addressForm = this.formBuilder.group({
line1: '',
line2: '',
line3: '',
line4: '',
});
}
writeValue(value: any) {
if (value) {
this.addressForm.setValue(value);
}
}
registerOnChange(fn: (value: any) => void) {
console.log("registerOnChange = " + fn)
this.addressForm.valueChanges.subscribe(fn);
}
registerOnTouched() {}
}
Template of Custom Component
Template Code
<div class="form-group" [formGroup]="addressForm">
<input type="text" name="line1" class="form-control" type="text" formControlName="line1" />
<input type="text" name="line2" class="form-control" type="text" formControlName="line2" />
<input type="text" name="line3" class="form-control" type="text" formControlName="line3" />
<input type="text" name="line4" class="form-control" type="text" formControlName="line4" />
</div>
Host or Parent Component class
import { Component } from '#angular/core';
import { NgForm, Validators, FormControl, FormGroup } from '#angular/forms';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
pageForm: FormGroup = new FormGroup({
address: new FormGroup({
line1: new FormControl('',Validators.required),
line2: new FormControl('',Validators.required),
line3: new FormControl('',Validators.required),
line4: new FormControl('')
}),
})
}
4.
<div class="container">
<form [formGroup]="pageForm">
<p>Form value: {{ pageForm.value | json }}</p>
<p>Form status: {{ pageForm.status | json }}</p>
<address label="Line 1" formControlName="address" > </address>
</form>
</div>
Note that Host or Parent Component class must declare the "address" field as a FormControl, not a FormGroup:
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
pageForm: FormGroup = new FormGroup({
address: new FormControl({
line1: new FormControl('',Validators.required),
line2: new FormControl('',Validators.required),
line3: new FormControl('',Validators.required),
line4: new FormControl('')
}),
})
}
Getting error in Chrome Console: EXCEPTION: Error: Uncaught (in promise): TypeError: Cannot read property 'applicationName' of null.
Model:
export class BasicInfoModel {
applicationName: string;
localDirectoryPath: string;
}
Controller emitting data to parent component, parent component there it is saved to services.
Controller:
import { Component, Output, OnInit, EventEmitter} from '#angular/core';
import { FormGroup, FormControl, REACTIVE_FORM_DIRECTIVES, Validators,
FormBuilder, FormArray}from "#angular/forms";
import { Observable } from "rxjs/Rx";
import { BasicInfoModel } from '../basicinfomodel';
import { BasicInfoService} from '../app.dev.basicinfo.service';
#Component({
selector: 'basic-info',
templateUrl: './basicInfo.html',
styleUrls: ['../../ComponentStyles.css'],
directives: [REACTIVE_FORM_DIRECTIVES]
})
export class BASIC_INFOComponent implements OnInit {
observableBasic: BasicInfoModel;
basicInfoForm: FormGroup;
#Output() basicInfoUpdate = new EventEmitter<JSON>();
#Output() basicInfoFormValid = new EventEmitter<Boolean>();
constructor(private formBuilder: FormBuilder, private BasicInfoService:
BasicInfoService) { }
onSubmit() {
debugger;
this.observableBasic;
this.basicInfoUpdate.emit(this.basicInfoForm.value);
}
ngOnInit() {
this.basicInfoForm = new FormGroup({
'applicationName': new FormControl('', Validators.required),
'localDirectoryPath': new FormControl('', Validators.required)
});
this.basicInfoForm.valueChanges.subscribe(data => console.log('form
changes', data));
this.BasicInfoService.currentBasicInfo
.subscribe(
(basic: BasicInfoModel) => {
this.observableBasic = basic;
});
(<FormGroup>this.basicInfoForm).setValue(this.observableBasic, { onlySelf: true });
}
}
What i want to achieve:
When i build my code, i want my formGroup should be populated with null values.
when i filled the data and saved it to behaviourSubject in my services, latter when i revisit the page my formGroup should be in sync with data services.
Modified the Controler by adding : (this.observableBasic != undefined)
ngOnInit() {
this.basicInfoForm = new FormGroup({
'applicationName': new FormControl('', Validators.required),
'localDirectoryPath': new FormControl('', Validators.required)
});
this.BasicInfoService.currentBasicInfo
.subscribe((basic: BasicInfoModel) => { this.observableBasic = basic; });
if (this.observableBasic != undefined) {
(<FormGroup>this.basicInfoForm).setValue(this.observableBasic, { onlySelf: true });
}
this.basicInfoForm.valueChanges.subscribe(data => console.log('form changes', data));
}