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;
}
Related
I am struggling with p-inputSwitch in angular.
I have an edit button in another component. Once I click it, a p-dialog pops up and it has the form below.
When the dialog is displayed, I want to make the preselected status on p-inputSwitch stay. It is inside the form control. Here is the code I made:
<home.component.html>
<form [formGroup]="myForm" (submit)="onSubmitForm()">
<div>
<p-inputSwitch
formControlName="ShowMe"
(onChange) = "test($event)"
></p-inputSwitch>
</div>
</form>
<home.component.ts>
export class HomeComponent implements OnInit {
checked: boolean;
myForm = this.fb.group({
ShowMe: [null, Validators.required],
});
constructor(
private fb: UntypedFormBuilder
) {}
ngOnInit() {
this.test();
}
test(e: any) {
this.checked = e.checked;
console.log(this.checked);
}
}
On the server side, I can see the ShowMe value is stored as either 0 or 1.
If it is 0, the checked should be false and the p-inputSwitch
should be off.
If it is 1, the checked should be true and the
p-inputSwitch should be on.
How can I see the preselected status on the input switch? Please help me out with any example code.
I fixed your code, there was a few bugs. Following this code, I did not have p-inputSwitch for my local, but I made a checkbox example over there which is the same logic:
TS ->
import { Component, VERSION } from '#angular/core';
import { UntypedFormBuilder, Validators } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
constructor(private fb: UntypedFormBuilder) {}
checked: boolean = true;
myForm = this.fb.group({
ShowMe: [false, Validators.required],
});
ngOnInit() {
this.myForm.patchValue({ ShowMe: false });
}
test(e?: any) {
this.checked = e.checked;
console.log(this.checked);
}
onSubmitForm() {
}
}
HTML ->
<form [formGroup]="myForm" (submit)="onSubmitForm()">
<div>
<input
#checkbox
type="checkbox"
formControlName="ShowMe"
(change)="test(checkbox)"
/>
</div>
</form>
https://stackblitz.com/edit/angular-ivy-61najh?file=src/app/app.component.html
Good luck.
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.
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 have a quiz app made with Ionic and Angular 4. User have to submit answer, I check if it's the same as the good answer or not.
I would like to check string correspondence, and handle event according to the correspondence between good answer and user answer.
In Exemple :
If the answer is 'azerty', and he wrote 'mzerty', I would like to allow him to continue.
If user wrote 'qwerty', or something too different, he failes.
A simple demo with Levenstein distance would be like that:
Typescript
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup } from '#angular/forms';
import { Observable } from 'rxjs/Observable';
import { map } from 'rxjs/operators';
import levenshtein from 'fast-levenshtein';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
form: FormGroup;
score$: Observable<number>;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.initForm();
this.initScore();
}
private initForm() {
this.form = this.fb.group({
str1: '',
str2: '',
});
}
private initScore() {
this.score$ = this.form
.valueChanges
.pipe(
map(({str1, str2}) => levenshtein.get(str1, str2))
);
}
}
HTML
<form [formGroup]="form">
<input type="text" formControlName="str1">
<br>
<br>
<input type="text" formControlName="str2">
</form>
<br>
<div>
Levenshtein score: {{ score$ | async }}
</div>
Stackblitz live demo: https://stackblitz.com/edit/angular-usydyu
You can simply create a method which will return you how many characters are matched. so on basis of matched characters and the length of string you can decide weather its a good answer or not.
function checkEq(str1, str2){
var arr1 = str1.split('');
var arr2 = str2.split('');
var counter = 0;
for(var i=0;i<arr1.length;i++){
if(arr1[i]==arr2[i]){
counter++;
}
}
return counter;
}
I have two page components that use the same methods with the exception of using two different type classes. The two components are called Services and Users. Both components use templates that are very similar with the exception of the class property info it displays. It seems to be inefficient to repeat methods on both controllers, is there a way to combine/share controllers.
Services.ts
import { Component } from '#angular/core';
import { CORE_DIRECTIVES } from '#angular/common';
const template = require('./service.component.html');
const style = require('./service.component.css');
interface Service {
id: number;
name: string;
summary: string;
path: string;
};
#Component({
selector: 'admin-services',
directives: [ CORE_DIRECTIVES],
template: template,
styles: [ style ]
})
export class ServiceComponent {
services = Services;
selectedService:Service ;
constructor() {
}
onselect(service:Service){
this.selectedService = service ;
}
onEdit(service:Service){
console.log("Edit: "+service);
}
onDelete(service:Service){
console.log("Delete: "+service);
}
onView(service:Service){
console.log("View: "+service);
}
onAdd(){
this.selectedService = <Service>{};
}
}
User.ts
import { Component } from '#angular/core';
import { CORE_DIRECTIVES } from '#angular/common';
const template = require('./users.component.html');
const style = require('./users.component.css');
interface User {
id: number;
image: string;
name: string;
email: string;
role: string;
};
#Component({
selector: 'admin-users',
directives: [ CORE_DIRECTIVES],
template: template,
styles: [ style ]
})
export class UsersComponent {
users = Users;
selectedUser:User ;
constructor() {
}
onselect(user:User){
this.selectedUser = user ;
}
onEdit(user:User){
console.log("Edit: "+user);
}
onDelete(user:User){
console.log("Delete: "+user);
}
onView(user:User){
console.log("View: "+user);
}
onAdd(){
this.selectedUser = <User>{};
}
}
Yep, this is where Angular's component-driven design and Typescripts's class-driven design really shine:
Having defined a ServicesComponent as you have above, you can simply extend that class and attach different component metadata to it:
#Component({
selector: 'admin-users',
directives: [ CORE_DIRECTIVES],
template: template,
styles: [ style ]
})
export class UsersComponent extends ServicesComponent {
constructor(){
super();
}
//override whatever methods/fields in the parent class you need to (and only those)
}
I believe you can create a service with a single set of methods and pass in an object. Then cast the object to the desired class and use it in the method.