How to add input fields dynamically in angular 6 - javascript

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>

Related

Angular pass checkbox value to custom pipe

I'm looping through the values and displaying checkboxes which I'm showing, now in addition there is an search box filter on the top of list of checkboxes basically to filter out checkbox list.
HTML
<input [(ngModel)]="searchText" class="lmn-input lmn-select-input-inner mt-input" placeholder="Search by key word" id="mngd-seg"/>
<div formArrayName="checkBoxValueList"
*ngFor="let dataConceptList of scopeSetDetails.controls['checkBoxValueList'].controls | searchFilter : searchText; let i = index">
<label class="lmn-selection-control">
<span class="lmn-checkbox">
<input class="lmn-control-input" type="checkbox" [formControlName]="i">
<i class="lmn-control-icon lmnicon lmnicon-check"></i>
</span>
<span>Data Concept </span>
<span style="padding-left: 150px">{{ checkBoxValueList[i].scopeSets }}</span>
</label>
</div>
Therefore I've created an custom pipe 'searchFilter' and using this pipe above in *ngFor
PIPE
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'searchFilter',
})
export class FilterPipe implements PipeTransform {
transform(items: any, searchText: string): any {
if (!items) return [];
if (!searchText) return items;
searchText = searchText.toLowerCase();
return items.filter((it) => {
return it.name.toLowerCase().includes(searchText);
});
}
}
In TS I'm initializing the checkboxes to false (with checkboxes, the Angular form control value is bound to the "checked" state (not the "value" attribute), so it should be either true or false) since it needs to be unchecked initially.
TS
const formControls = this.checkBoxValueList.map(
(control) => new FormControl(false)
);
const selectAllControl = new FormControl(false);
this.scopeSetDetails = this.fb.group({
search_text: this.fb.control(''),
checkBoxValueList: new FormArray(formControls, this.minSelectedScopeSet(1)),
selectAll: selectAllControl,
});
Now issue is in filter pipe I'm getting items as an array of FormControl whose value property is 'false'. I need the checkbox text somehow and initially all checkboxes should be unchecked. how can I achieve this?

How to use [(ngModel)] in ngFor with dynamic Name (Key)

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

How to bind to an Angular form from users selected option

OK it's a bit more complicated than the headline..
This form I am working on is a form group. It has a few fields ( supplement name, description and tags) the supplement name one is what I need help with as I have not worked on a complicated form like this and want to get it right and not just offer a messy patch job.
Here is the expected logical order of what happens
user adds a new supplement by clicking on the field and begins typing "creatine" for example
there is a query sent out that fetches products based on the entry into the input and
returns a JSON that are offered as suggestions
user clicks the suggestion "creatine"
field is populated and binds
we add another entry through the "add option" and repeat for X amount of products we want to
add.
What actually happens
user adds new supplement by clicking the field and types "creatine" suggestion request is
sent off and populates the dropdown
user clicks on the suggestion "creatine" the field takes that value
value is actually blank
user adds another supplement but the previous selection is in the field
user clears it and types again
value is blank
What needs to happen is the user can add X amount of supplements and able to grab whatever option from the dropdown recommendation and it is added to the form group array and does not interfere with the other form group array values.
I know this is not the right way to bind the form and I don't think it's right the way i'm binding the mat input field to trigger the query and this is the reason why I'm asking the question again, to not offer a patch job.
Component code
import { Subscription } from 'rxjs/Subscription';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
import { UtilitiesService } from '../../utilities/utilities.service';
import { GetSupplementsService } from './get-supplements.service';
#Component({
selector: 'app-supplements',
templateUrl: './supplements.component.html',
styleUrls: ['./supplements.component.css'],
providers: [GetSupplementsService],
})
export class SupplementsComponent implements OnInit {
supplementForm: FormGroup;
queryField: FormControl = new FormControl();
private supplementInventoryResults: Array<ISupplementInventoryResponse>;
private eventForm: FormGroup;
private searchResults: any;
private searchSubscription: Subscription;
private addSupplementSubscription: Subscription;
subcription: Subscription;
constructor (
private bottomSheet: MatBottomSheet,
private _fb: FormBuilder,
private ref: ChangeDetectorRef,
private _utils: UtilitiesService,
private getSupplements: GetSupplementsService,
private router: Router
) { }
public ngOnInit(): void {
this.browsingStackHistory = false;
this.loading = true;
this.supplementForm = this._fb.group({ // the form in question
entryArray: this._fb.array([
this.getUnit()
])
});
this.searchSubscription =
this.queryField.valueChanges
.debounceTime(600)
.distinctUntilChanged()
.switchMap((query) => this.getSupplements.search_supplement_by_category(query))
.subscribe((result) => {
this.searchResults = result;
});
}
public ngOnDestroy(): void {
this.subcription.unsubscribe();
}
private getUnit(): FormGroup {
return this._fb.group({
supplementName: [''],
description: [''],
tags: ['']
});
}
private addUnit(): void {
const control = <FormArray>this.supplementForm.controls['entryArray'];
control.push(this.getUnit());
}
private removeUnit(i: number): void {
const control = <FormArray>this.supplementForm.controls['entryArray'];
control.removeAt(i);
}
private addSupplement(): void { // this will do the post to the service
const supplementObject = {
start: this._utils.get_upload_time(),
data: this.supplementForm.value,
rating: 0
};
}
}
Template
[![<mat-tab label="Add Stack (Test)">
<div style="padding:8px;">
<div fxLayout="row wrap">
<div fxFlex.gt-sm="50%" fxFlex="100">
<h1>Add New Supplements Stack</h1>
<form \[formGroup\]="supplementForm" class="log-workout">
<!-- Start form units array with first row must and dynamically add more -->
<div fxLayout="column" fxLayoutAlign="center center" class="row-height">
<div formArrayName="entryArray">
<mat-divider></mat-divider>
<!-- loop throught units -->
<div *ngFor="let reps of supplementForm.controls.entryArray.controls; let i=index">
<!-- row divider show for every nex row exclude if first row -->
<mat-divider *ngIf="supplementForm.controls.entryArray.controls.length > 1 && i > 0"></mat-divider>
<br>
<!-- group name in this case row index -->
<div \[formGroupName\]="i">
<!-- unit name input field -->
<div class="row">
<mat-form-field class="example-form">
<input matInput placeholder="Supplement Name" \[formControl\]="addSupplementField"
formControlName="supplementName" \[matAutocomplete\]="auto">
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let product of supplementResults" \[value\]="product?.product_name">
<img class="example-option-img" aria-hidden \[src\]="product?.product_image" height="25">
{{product?.product_name}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field class="example-form">
<input matInput placeholder="Description" formControlName="description" required>
</mat-form-field>
<mat-form-field class="example-form">
<input matInput placeholder="Tags" formControlName="tags" required>
</mat-form-field>
</div>
<!-- row delete button, hidden if there is just one row -->
<button mat-mini-fab color="warn" *ngIf="supplementForm.controls.entryArray.controls.length > 1"
(click)="removeUnit(i)">
<mat-icon>delete forever</mat-icon>
</button>
</div>
</div>
<!-- New unit button -->
<mat-divider></mat-divider>
<mat-card-actions>
<button mat-raised-button (click)="addUnit()">
<mat-icon>add box</mat-icon>
Add Other Product
</button>
</mat-card-actions>
<button mat-raised-button (click)="addSupplement()">
<mat-icon>add box</mat-icon>
Add Supplement
</button>
</div>
</div>
<!-- End form units array -->
</form>
</div>
</div>
</div>][1]][1]
Having the below when the getUnit() function is called apparently binds it in the sense it will operate independently and without conflicts.
private getUnit(): FormGroup {
const formGroup = this._fb.group({
supplementName: [''],
review: [''],
rating: [''],
notes: [''],
tags: ['']
});
formGroup.get('supplementName').valueChanges
.debounceTime(300)
.distinctUntilChanged()
.switchMap((search) => this.getSupplements.search_supplement_by_category(search))
.subscribe((products) => {
this.supplementResults = products;
});
return formGroup;
}

How to pass formarray to other component

I want to pass formarray to child component to display formarray values there. Below is the code, what I've tried so far. I am not able to find the way to display the formarray values in the child component.
app.component.html
<div [formGroup]="userForm">
<div formArrayName="users">
<div *ngFor="let user of users.controls; let i = index">
<input type="text" placeholder="Enter a Room Name" [formControlName]="i">
</div>
</div>
</div>
<button (click)="addUser()">Add Room</button>
<title [users]="users"></title>
app.component.ts
userForm: FormGroup;
constructor(private fb: FormBuilder) {}
public get users(): any {
return this.userForm.get('users') as FormArray;
}
ngOnInit() {
this.userForm = this.fb.group({
users: this.fb.array([this.fb.control('')])
});
}
addUser() {
this.users.push(this.fb.control(''));
}
title.component.html
<div *ngFor="let user of users.controls">{{ user.value }}</div>
title.component.ts
#Input() users;
ngOnChanges(changes) {
console.log(changes);
}
But above code not displaying the formarray values in the child component.
Example stackblitz is here
title is a keyword and its already defined tag with HTML. just use some different name for the component selector
selector: 'title1',
STACKBLITZ DEMO

Disable Angular 5 Input fields correct way

I have a FormGroup that was created like that:
form: FormGroup;
constructor(private _formBuilder: FormBuilder) { }
this.form = this._formBuilder.group({
name: ['', Validators.required],
email: ['', Validators.required, Validators.email]
});
When an event occurs I want to disable those inputs, so, in the HTML I added:
<input class="form-control" placeholder="Name" name="name" formControlName="name" [(ngModel)]="name" autocomplete="off" [disabled]="isDisabled" required>
<input class="form-control" placeholder="Email" name="email" formControlName="email" [(ngModel)]="email" email="true" autocomplete="off" [disabled]="isDisabled" required>
Where isDisabled is a variable I toggle to true when the said event happens.
As you can imagine, I get the message:
It looks like you're using the disabled attribute with a reactive form
directive. If you set disabled to true
when you set up this control in your component class, the disabled attribute will actually be set in the DOM for
you. We recommend using this approach to avoid 'changed after checked' errors.
Example:
form = new FormGroup({
first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
last: new FormControl('Drew', Validators.required)
});
So, with the example this warning shows and with a little search I found that I should declare my controls like:
name: [{ value: '', disabled: this.isDisabled }, Validators.required]
The problem is: It is not toggling between disabled/enabled when the variable changes between true/false
How is the correct way of having a variable controlling if an input is enabled or disabled?
I don't want to do it manually (ex: this.form.controls['name'].disable()) because it doesn't seems a very reactive way, I would have to call it inside a good amount of methods. Probably not a good practice.
Thx
You can change the assignment of the variable to a setter method so that you'd have:
set isDisabled(value: boolean) {
this._isDisabled = value;
if(value) {
this.form.controls['name'].disable();
} else {
this.form.controls['name'].enable();
}
}
One solution is creating a directive and using binding for that as described in here
import { NgControl } from '#angular/forms';
#Directive({
selector: '[disableControl]'
})
export class DisableControlDirective {
#Input() set disableControl( condition : boolean ) {
const action = condition ? 'disable' : 'enable';
this.ngControl.control[action]();
}
constructor( private ngControl : NgControl ) {
}
}
then
<input class="form-control" placeholder="Name" name="name" formControlName="name" autocomplete="off" [disableControl]="isDisabled" required>
NOTE:
Doesn't work with Ivy
For input use [readonly] rather than [disabled] and it'll work
The proper way to disable an form control. With reactive forms you should never disable an input from the template. So in whatever method in your component you are calling you should disable the input like this:
this.form.get('name').disable();
Disable TextBox in Angular 7
<div class="center-content tp-spce-hdr">
<div class="container">
<div class="row mx-0 mt-4">
<div class="col-12" style="padding-right: 700px;" >
<div class="form-group">
<label>Email</label>
<input [disabled]="true" type="text" id="email" name="email"
[(ngModel)]="email" class="form-control">
</div>
</div>
</div>
</div>
You can use this code on your ts file.
All controls:
this.form.disable()
this.form.enable()
Some controls
this.form.get('first').disable()
this.form.get('first').enable()
Or Initial set method.
first: new FormControl({value: '', disabled: true}, Validators.required)
In Reactive Form you can disable all form fields by this.form.disable().
In Template Driven Form you can disable all form fields by this.myform.form.disable() where myForm is #ViewChild('form') myForm;
Not the clean or dry'st I imagine. Bu I tried the "set method" and didn't work out of the box...
Needed some refactoring () => {simpleVersion} (hope it helps someone)
component.ts
...
// standard stuff...
form: FormGroup;
isEditing = false;
...
// build the form...
buildForm() {
this.form = this.FormBuilder.group({
key: [{value:'locked', disabled: !this.isEditing}],
name: [],
item: [],
active: [false]
})
}
// map the controls to "this" object
// => i.e. now you can refer to the controls directly (ex. this.yourControlName)
get key() { return <FormControl>this.form.get('key') }
get name() { return <FormControl>this.form.get('name') }
...
// ----------------------------------------
// THE GRAND FINALÉ - disable entire form or individual controls
// ----------------------------------------
toggleEdit() {
if(!this.isEditing) {
this.key.enable(); // controls
this.name.enable();
// this.form.enable(); // the form
this.isEditing = !this.isEditing;
} else {
this.key.disable(); // the controls
this.name.disable(); // the controls
// this.form.disable(); // or the entire form
this.isEditing = !this.isEditing;
}
}
& perhaps overkill on the HTML logic, so hope you find the bonus integrated ngClass toggle just as helpful.
component.html (toggle button)
<div class="btn-group" (click)="toggleEdit()">
<label
class="btn"
role="button"
[ngClass]="{'btn-success': isEditing,
'btn-warning': !isEditing}">toggle edit
</label>
</div>
The solution by creating a directive and using binding for that worked for me in Angular 10 is described in here
Template:
<mat-form-field>
<input matInput class="form-control" formControlName="NameText" [disableControl]="condition" type="text">
</mat-form-field>
TypeScript:
import { Directive, Input } from '#angular/core';
import { NgControl } from '#angular/forms';
#Directive({
selector: '[opDisabled]'
})
export class DisabledDirective {
#Input()
set opDisabled(condition: boolean) {
const action = condition ? 'disable' : 'enable';
setTimeout(() => this.ngControl.control[action]());
}
constructor(private ngControl: NgControl) {}
}
I have a function that enables a control on click.
controlClick(control: any) {
this.form.controls[control.ngControl.name].enable();
}
Originally i was using
control.disabled = false;
But this did not work for controls with <input> for example in my mat-chip-list.
I use FormGroup and disable each control in the constructor
constructor(
private fb: FormBuilder,
private dialogRef: MatDialogRef<EditDialogComponent>,
#Inject(MAT_DIALOG_DATA) data
) {
this.data = data;
this.multiEdit = data.multiSelect;
this.form = new FormGroup({
autoArchive: new FormControl({
value:
this.getPreFill(data.selectedPolicy.autoArchive, this.multiEdit),
disabled: true
/*, Validators.required*/
}),
...
<mat-form-field (click)="controlClick(retrieveChipList)">
<mat-chip-list #retrieveChipList formControlName="retrieveChipList">
<mat-chip
*ngFor="let email of data.selectedPolicy.retrieveEmailsToBeNotified"
(removed)="remove(email)" [selectable]="selectable"
[removable]="removable"
>
{{ email }}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input
placeholder="Retrieve Emails to be Notified"
formControlName="retrieveChipList"
[matChipInputFor]="retrieveChipList"
[matChipInputAddOnBlur]="true"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="addRetrieveEmails($event)"
/>
</mat-chip-list>
</mat-form-field>
As control can't be accessed in reactive forms. This is due to migration to Ivy. You can use can access the html attribute directly and specify your condition. See this issue #35330 for more details and alternative methods.
[attr.disabled]="true || false"
You can create set and get method to achieve conditionally enable/disable functionality for Angular material Reactive Forms:
*// 1st Step:
set isDisabled(value:boolean) {
if(value){
this.form.controls['Form controller name'].disable(); //you can keep empty if you don't add controller name
}
else{
this.form.controls['Form controller name'].enable();
}
}
// 2nd Step: Add conditions in getter
get isDisabled(){
return condition ? true : false;
}
// 3rd Step
this.form = this._formBuilder.group({
name: [{value: '', disabled: this.isDisabled }, [Validators.required]],
});
Remove [disabled]="isDisabled" from input fields and add ng-disabled="all" and in the event field add ng-model="all"
<body ng-app="">
Click here to disable all the form fields:<input type="checkbox" ng-model="all">
<input class="form-control" placeholder="Name" name="name" formControlName="name" [(ngModel)]="name" autocomplete="off" ng-disabled="all" required>
<input class="form-control" placeholder="Email" name="email" formControlName="email" [(ngModel)]="email" email="true" autocomplete="off" ng-disabled="all" required>
</body>

Categories

Resources