I have a component like this:
Form: FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit(): void {
this.Form = this.fb.group({
contratacion: [],
beneficios: this.fb.array([ this.nuevoBeneficio() ])
});
}
beneficiosBase() {
let _form = this.Form.get('beneficios') as FormArray
_form.push(this.nuevoBeneficio('Ejemplo: Prestaciones de ley'));
_form.push(this.nuevoBeneficio('Ejemplo: Bono por puntualidad'));
_form.push(this.nuevoBeneficio('Ejemplo: Prestaciones de ley'));
}
nuevoBeneficio(textoPlaceholder?:string): FormGroup {
return this.fb.group({
nombre: [],
descripcion: [textoPlaceholder || null],
});
}
And my html looks like this:
<ng-container *ngFor="let item of Form.get('beneficios').controls; let i = index;">
<input formControlName="nombre" type="text" class="form-control" id="" [placeholder]="descripcion">
</ng-container>
I realized this is not the way to set the placeholder value since it expects a formControlName 'descripcion', but I wonder if there's a way to do this.
You could create an auxiliary array like this listOfPlaceholder: string[]; then in your method beneficiosBase
beneficiosBase() {
_form.push(this.nuevoBeneficio();
this.listOfPlaceholders.push('Ejemplo: Prestaciones de ley');
}
Your HTML should looks like this
<ng-container *ngFor="let item of Form.get('beneficios').controls; let i = index;">
<input formControlName="nombre" type="text" class="form-control" id="" [placeholder]="listOfPlaceholders[i]">
</ng-container>
Related
I have angular form that are generated by NgFor on two different div. i want to make form fields unique.
both fields are not working individually
<div *ngFor="let configField of configFields; let i = index"><mat-form-field fxFlex fxFlex.lt-md="calc(50% - 16px)" >
<input matInput placeholder="Url" name="url_{{i}}" [(ngModel)]="headerFields.url" >
</mat-form-field>
<mat-form-field fxFlex fxFlex.lt-md="calc(50% - 16px)" >
<input matInput placeholder="Method" name="type_{{i}}" [(ngModel)]="headerFields.type">
</mat-form-field></div>
i want to add flag on field name for uniquely identified in (headerFields)Array.. like name="type_{{i}}" type_1 ,type_2
On initialization or assignment of configFields, you can initialize or assign to the headerFields an array of length equal to the one as configFields, with url and type set as empty. Something like the following:
const configFields = [1,2,3];
const headerFields = Array.from(configFields, (el) => ({ url: '', type: ''})) // Set to empty string or null as desired]
How about using form array instead?
file component.ts
export class AppComponent {
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = fb.group({
config: fb.array([])
});
}
addNewAddressGroup() {
const add = this.form.get('config') as FormArray;
add.push(
this.fb.group({
url: [],
type: []
})
);
}
deleteAddressGroup(index: number) {
const add = this.form.get('config') as FormArray;
add.removeAt(index);
}
}
file html.ts
<div [formGroup]="form">
<div>
<button (click)="addNewAddressGroup()">Add</button>
</div>
<div
*ngFor="let addressGroup of form.get('config').controls;let i = index"
[formGroup]="addressGroup">
<div>Url <input formControlName="url" /></div>
<div>Type <input formControlName="type" /></div>
<button (click)="deleteAddressGroup(i)">Delete</button>
</div>
</div>
result <br />
<pre>
{{form.value | json}}
</pre>
You can check out this Demo
I have components in order to add user.
This is my form component:
createForm(): void {
this.courseAddForm = this.formBuilder.group({
name: ['', [
Validators.required,
Validators.maxLength(this.val.maxLen.title)
]],
roleId: ['', Validators.compose([Validators.required])]
});
}
name : username ,
roleId : selected role from dropdown .
I create a acomponents for roleId . <kt-searchable-dropdown>
HTML :
<form id="courseAddForm" [formGroup]="courseAddForm" (ngSubmit)="onSubmit()" autocomplete="off">
<div class="form-group kt-form__group row">
<!-- title -->
<div class="col-lg-6 kt-margin-bottom-20-mobile">
<mat-form-field class="mat-form-field-fluid" appearance="outline">
<mat-label>{{'GENERAL.TITLE' | translate}} *</mat-label>
<input matInput formControlName="title" [placeholder]="'GENERAL.TITLE' | translate">
<!--requied error-->
<mat-error *ngIf="courseAddForm.get('title').errors?.required">
{{ 'VALIDATION.REQUIRED.TITLE' | translate }}</mat-error>
<!--length error-->
<mat-error *ngIf="courseAddForm.get('title').errors?.maxlength">
{{'VALIDATION.MAX_LENGTH' | translate}} {{val.maxLen.title}}
</mat-error>
</mat-form-field>
</div>
<div class="col-lg-6 kt-margin-bottom-20-mobile">
<kt-searchable-dropdown [formGroup]="courseAddForm" [formcontrolName]="'courseId'" (selectedId)="selectedCourse($event)"
[formTitle]="'COURSE.COURSE_GROUP'" [url]="url"></kt-searchable-dropdown>
</div>
</div>
</form>
This is my component for roleId dropdown :
TS :
export class SearchableDropdownComponent implements OnInit {
#Input() url: string;
#Input() formTitle: string;
#Input() ItemId: number;
#Input() formcontrolName: string;
#Input() formGroup: FormGroup;
#Output() selectedId = new EventEmitter<number>();
loading = false;
values: KeyValue[];
title: string;
fC: FormControl;
constructor(
private searchService: SearchableDropDownService,
private cdRef: ChangeDetectorRef) {
}
ngOnInit(): void {
this.getValues(null);
}
getValues(event): void {
this.cdRef.detectChanges();
this.loading = true;
let model = {} as SendDateModel;
model.page = 1;
model.pageSize = 60;
model.title = event;
this.searchService.getAll(this.url, model).subscribe(data => {
this.values = data['result']['records'];
this.cdRef.detectChanges();
this.loading = false;
});
}
}
HTML :
<form [formGroup]="formGroup">
<mat-form-field appearance="outline">
<mat-label>{{formTitle| translate}} *</mat-label>
<mat-select formControlName="courseId" >
<div class="col-lg-12 mt-4 kt-margin-bottom-20-mobile">
<mat-form-field class="mat-form-field-fluid" appearance="outline">
<mat-label>{{'GENERAL.TITLE' | translate}} *</mat-label>
<input (keyup)="getValues($event.target.value)" matInput
[placeholder]="'GENERAL.TITLE' | translate">
</mat-form-field>
</div>
<mat-progress-bar *ngIf="loading" class="mb-2" mode="indeterminate"></mat-progress-bar>
<mat-option (click)="emitdata(item.key)" *ngFor="let item of values"
[(ngModel)]="ItemId" [value]="item.key">
{{item.value}}
</mat-option>
<mat-error *ngIf="formGroup.get('courseId').errors?.required">
{{ 'COURSE.VALIDATIONS.REQUIRED.CLASS_LEVEL' | translate }}</mat-error>
</mat-select>
</mat-form-field>
I write this code but it is not work.
i need to bind validation in the form in this components . for example when roleId is required and user not select item ,show error that the roleId is reqierd , I need to show it in this components SearchableDropdownComponent . how can i do this ????
You can achieve this by using angular observables. Create a service to hold the error state in your first component.
ErrorService.ts
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs/BehaviorSubject';
#Injectable()
export class ErrorService {
private errorObj = new Subject<any>({});
data = this.errorObj.asObservable();
constructor() { }
updatedErrorObj(error){
this.errorObj.next(error);
}
getErrorObj(){
return this.errorObj.asObservable();
}
}
Inside your first component, you need to create an instance of this service and on error stage update the error object.
FirstComponent.ts
// Creating an instance of service in constructor
constructor(private errorService: ErrorService) { }
onSubmit() {
if(this.courseAddForm.invalid) {
// Updating error object
cont errorObject = { key: 'value' }; //Create your custom error object here
this.errorService.updatedErrorObj(errorObject);
}
}
Caputure this error in the SearchableDropdownComponent
// Creating an instance of service in constructor
constructor(private errorService: ErrorService) {
errorService.getErrorObj.subscribe(data => {
// do what ever needs doing when data changes
// You will recive the error object here.
})
}
Please refer this blog for details.
I need to add input fields to array of objects and a map which grows dynamically based on user's choice.
For e.g. InputForm has a list and a map which needs to be filled by user.
export class InputForm {
mapA: {};
listA: ListA[] = [];
}
export class ListA {
input1 : String
input2 : number
}
I need to show it on UI in such a way that input1, input2 and key, value for map of criteria is visible as the input field. The user fills all 4 input fields and clicks on the add button.
Then again same input fields should be editable for the user for the second input. This way he can build list and map and when he clicks on submit button array and map should have all the values filled before.
*ngFor doesn't work because the initial list is empty.
Assuming you are using Angular Reactive Form, you can use a combination of *ngFor and FormArray. You can start with an empty FormArray and add dynamically using the .push() method.
Here is a good and detailed example
// order-form.component.ts:
#Component({
selector: '...',
templateUrl: './order-form.component.html'
})
export class OrderFormComponent implements OnInit{
orderForm: FormGroup;
items: FormArray;
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.orderForm = this.formBuilder.group({
customerName: '',
email: '',
items: this.formBuilder.array([ this.createItem() ])
});
}
createItem(): FormGroup {
return this.formBuilder.group({
name: '',
description: '',
price: ''
});
}
addItem(): void {
this.items = this.orderForm.get('items') as FormArray;
this.items.push(this.createItem());
}
}
<!-- order-form.component.html -->
<div formArrayName="items"
*ngFor="let item of orderForm.get('items').controls; let i = index;">
<div [formGroupName]="i">
<input formControlName="name" placeholder="Item name">
<input formControlName="description" placeholder="Item description">
<input formControlName="price" placeholder="Item price">
</div>
Chosen name: {{ orderForm.controls.items.controls[i].controls.name.value }}
</div>
I'm trying to include a reset button on a Reactive form in Angular 5. For all form fields, the reset is working perfectly except for the multiple checkboxes, which are dynamically created.
Actually the reset apparently happens for the checkboxes as well, but the result is not reflected in the view.
service.component.html
<form [formGroup]="f" (ngSubmit)="submit()">
<input type="hidden" id="$key" formControlName="$key">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" formControlName="name">
</div>
<br/>
<p>Professionals</p>
<div formArrayName="prof">
<div *ngFor="let p of professionals | async; let i = index">
<label class="form-check-label">
<input class="form-check-input" type="checkbox (change)="onChange({name: p.name, id: p.$key}, $event.target.checked)" [checked]="f.controls.prof.value.indexOf(p.name) > -1"/>{{ p.name }}</label>
</div>
<pre>{{ f.value | json }}</pre>
</div>
<br/>
<button class="btn btn-success" type="submit" [disabled]="f?.invalid">Save</button>
<button class="btn btn-secondary" type="button" (click)="resetForm($event.target.checked)">Reset</button>
</form>
service.component.ts
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormControl, FormGroup, Validators, FormArray } from '#angular/forms'
import { AngularFireAuth } from 'angularfire2/auth';
import { Router, ActivatedRoute } from '#angular/router';
import { AngularFireDatabase, FirebaseListObservable, FirebaseObjectObservable} from 'angularfire2/database';
import { Service } from './service';
export class ServiceComponent implements OnInit {
f: FormGroup;
userId: string;
$key: string;
value: any;
services: FirebaseListObservable<Service[]>;
service: FirebaseObjectObservable<Service>;
professionals: FirebaseListObservable<Service[]>;
profs: FirebaseListObservable<Service[]>;
constructor(private db: AngularFireDatabase,
private afAuth: AngularFireAuth,
private route: ActivatedRoute,
private router: Router,
private fb: FormBuilder) {
this.afAuth.authState.subscribe(user => {
if(user) this.userId = user.uid
this.services = db.list(`services/${this.userId}`);
})
this.afAuth.authState.subscribe(user => {
if(user) this.userId = user.uid
this.professionals = this.db.list(`professionals/${this.userId}`);
})
}
ngOnInit() {
// build the form
this.f = this.fb.group({
$key: new FormControl(null),
name: this.fb.control('', Validators.required),
prof: this.fb.array([], Validators.required)
})
}
onChange(name:string, isChecked: boolean) {
const profArr = <FormArray>this.f.controls.prof;
if(isChecked) {
profArr.push(new FormControl(name));
console.log(profArr.value);
} else {
let index = profArr.controls.findIndex(x => x.value == name)
profArr.removeAt(index);
console.log(profArr.value);
}
}
resetForm(){
let profArr = <FormArray>this.f.controls.prof;
this.f.controls.name.setValue('');
profArr.controls.map(x => x.patchValue(false));
this.f.controls.$key.setValue(null);
}
}
service.ts
export class Service {
$key: string;
name: string;
professionals: string[];
}
The result of the code above, displayed by line <pre> {{f.value | json}} </ pre> is:
When I fill out the form:
{
"$key": null,
"name": "Test service",
"prof": [
{
"name": "Ana Marques",
"id": "-LEZwqy3cI3ZoYykonWX"
},
{
"name": "Pedro Campos",
"id": "-LEZz8ksgp_kItb1u7RE"
}
]
}
When I click on Reset button:
{
"$key": null,
"name": "",
"prof": [
false,
false
]
}
But checkboxes are still selected:
What is missing?
I would stop using FormControls to handle what is basically state.
You have some code which loads the professionals property of the component. Just add to that data a checked property and change the type of professionals to Service[]:
this.db.list(`professionals/${this.userId}`)
.subscribe(professionals => {
professionals.forEach(p => p.checked = false);
this.professionals = professional;
});
Btw, you don't have a checked property on your Service type, so either you extend it or transform professionals in something else (i.e. a CheckableService).
The template becomes:
<div *ngFor="let p of professionals; let i = index">
<label class="form-check-label">
<input class="form-check-input" type="checkbox (change)="onChange(p, $event.target.checked)" [checked]="p.checked"/>{{ p.name }}</label>
</div>
And the onChange method becomes:
onChange(professional: Service, isChecked: boolean) {
professional.checked = isChecked;
this.profArr = this.professionals.filter(p => p.checked);
}
It seems a lot cleaner to me (you will need to adjust for the checked parameter not being in the Service type, but the code is simply adaptable). No messing with controls, only data cleanly flowing through your component.
You are not referencing your checkboxes. Give them a name using the index.
<div formArrayName="prof">
<div *ngFor="let p of professionals | async; let i = index">
<label class="form-check-label">
<input [formControlName]="i" class="form-check-input" type="checkbox (change)="onChange({name: p.name, id: p.$key}, $event.target.checked)" [checked]="f.controls.prof.value.indexOf(p.name) > -1"/>{{ p.name }}</label>
</div>
<pre>{{ f.value | json }}</pre>
</div>
This is my checkall checkbox
<input type="checkbox" value="a" (click)="checkAll" [checked]="checkAll">
This i a regular checkbox, i used this whithin an ngfor
<input type="checkbox" value="a" (click)="check(object)" name="checkbox" [checked]="true">
And the checkall checkboxes function
let elements = document.getElementsByTagName('input');
if (this.checkAll) {
this.checkAll = false;
for (let i = 0; i < elements.length; i++) {
if (elements[i].type == 'checkbox') {
elements[i].checked = false;
}
}
}
else....the other way
I want to check whether the dropdown is empty.
Need to show the required message and
If not empty, enable the submit button.
If empty, disable the submit button. Below is my html
Below is my html
<form [formGroup]="myForm" (ngSubmit)="save()" >
<mat-form-field>
<mat-select formControlName="name" placeholder="Element List" (selectionChange)="elementSelectionChange($event)" required>
<mat-option *ngFor="let element of Elements" [value]="element.name">
{{ element.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="myForm.hasError('required', 'name')">Please choose an name</mat-error>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="symbol" placeholder="Symbol List" required>
<mat-option *ngFor="let element of selectedElementSymbol" [value]="element.symbol">
{{ element.symbol }}
</mat-option>
</mat-select>
<mat-error *ngIf="myForm.hasError('required', 'symbol')">Please choose an symbol</mat-error>
</mat-form-field>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">Cancel</button>
<button type="submit" mat-button cdkFocusInitial>Add</button>
</div>
</form>
below is my component
export class DialogOverviewExampleDialog {
myForm: FormGroup;
symbol = new FormControl('', Validators.required);
name = new FormControl('', Validators.required);
constructor(
public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
#Inject(MAT_DIALOG_DATA) public data: any,
private formBuilder: FormBuilder) {
this.myForm = this.formBuilder.group({
name: [this.name],
symbol: [this.symbol],
});
}
save() {
console.log(this.myForm.value);
}
}
updated demo here
You are currently assigning formcontrols to your formcontrol, whereas you want to assign value to your form controls. Below you are assigning form control name to formcontrol name:
WRONG:
name = new FormControl('', Validators.required);
this.myForm = this.formBuilder.group({
'name': [this.name, Validators.required],
// ...
});
so instead, just declare name, do what you want with the value, then assign that value to your form control...
CORRECT:
name: string;
this.myForm = this.formBuilder.group({
'name': [this.name, Validators.required],
// ...
});
Then just add [disabled]="!myForm.valid" on your submit button.
As for the other question, by default Angular material doesn't show error message unless the field has been touched, so you need to have a custom error state matcher that shows the error even when field is not touched (if that is what you want):
import {ErrorStateMatcher} from '#angular/material/core';
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
return !!(control.invalid);
}
}
and in your TS, declare a error state matcher:
matcher = new MyErrorStateMatcher();
and use in template:
<mat-select formControlName="name" ... [errorStateMatcher]="matcher">
Here's your
StackBlitz
To make the submit button disabled (link)
<button type="submit" [disabled]="!myForm.valid" mat-button cdkFocusInitial>Add</button>
In order to check whether the dropdown is empty or not, you need to make the form fields required like
this.myForm = this.formBuilder.group({
'name': [this.name, Validators.required],
'symbol': [this.symbol, [Validators.required]]
});
Inorder to show the error highlight you need to add an ngClass in the templete like.
[ngClass]="{'error': myForm.controls.name.valid == false}"
You have to insert the validator into the form builder object.
Have a quick look at:
https://angular.io/guide/reactive-forms#validatorsrequired
this.heroForm = this.fb.group({
name: ['', [Validators.required] ],
});
As for the button:
<button type="submit" [disabled]="!form.valid" mat-button cdkFocusInitial>Add</button>