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
Related
I'm working on building a set of filters, so I'm just trying to make use of the salesChannels array content in my view, which only gets populated when clicking the button with the test() function. The log in ngOnInit outputs an empty array the first time, but works correctly after pressing the button.
The getOrganisationChannels returns an observable.
What causes this behavior and how do I handle it properly? I tried using an eventEmitter to try and trigger the populating but that doesn't work.
TYPESCRIPT
export class SalesChannelFilterComponent implements OnInit {
constructor(
public organizationService: OrganizationService
) { }
#Input() organizationId: any;
salesChannels: Array<any> = [];
selectedChannels: Array<any> = [];
allSelected: Array<any> = [];
ngOnInit() {
this.getChannels();
console.log(this.salesChannels);
}
getChannels() {
this.organizationService.getOrganizationChannels(this.organizationId).subscribe(
salesChannels => {
this.salesChannels = salesChannels;
})
}
test() {
console.log(this.salesChannels);
}
}
HTML
<div>
{{ salesChannels | json }}
</div>
<button (click)="test()">test</button>
<div *ngFor="let channel of salesChannels; let i = index;" class="checkbox c-checkbox">
<label>
<input type="checkbox">
<span class="fa fa-check"></span>{{channel.name}}
</label>
</div>
This is expected behaviour since you are populating the salesChannel in the subscription of an Observable. It's recommended that you use aysnc pipe to let angular check for changes and update the view accordingly.
Component.ts :
export class SalesChannelFilterComponent implements OnInit {
constructor(
public organizationService: OrganizationService
) { }
#Input() organizationId: any;
salesChannels$!: Observable<Array<any>>;
selectedChannels: Array<any> = [];
allSelected: Array<any> = [];
ngOnInit() {
this.getChannels();
console.log(this.salesChannels);
}
getChannels() {
this.salesChannels$ = this.this.organizationService.getOrganizationChannels(this.organizationId);
}
test() {
console.log(this.salesChannels);
}
}
In your template:
<button (click)="test()">test</button>
<div *ngFor="let channel of salesChannels$ | async; let i = index;" class="checkbox c-checkbox">
<label>
<input type="checkbox">
<span class="fa fa-check"></span>{{channel.name}}
</label>
</div>
More details: https://angular.io/api/common/AsyncPipe
I recommend using AsyncPipe here:
<div>{{ salesChannels | async}}</div>
and in .ts:
salesChannels = this.organizationService.getOrganizationChannels(this.organizationId)
I'm trying to implement a reactive Angular form, but, I can't access the properties of the array on HTML, I never worked with reactive form, if anyone could guide me I would be grateful! I'm using Angular 10 and I have the following code:
TS
operationModel: IScriptOperationsModel;
formOperation: FormGroup;
constructor(
private fb: FormBuilder,
...
) {}
ngOnInit() {
this.operationModel = new IScriptOperationsModel();
this.operationModel.scriptOperationOrders = [];
this.buildForm(new IScriptOperationsModel());
this.scriptOperationsService.findScriptOperation(this.operationId).subscribe((operation) => {
this.operationModel = operation.data as IScriptOperationsModel; // api return
this.buildForm(this.operationModel); // I pass the return of the api to the form
});
}
buildForm(operation: IScriptOperationsModel) {
this.formOperation = this.fb.group({
descriptionCode: [operation.descriptionCode],
description: [operation.description],
workStations: this.fb.array([])
});
this.formOperation.setControl('workStations', this.fb.array(this.operationModel.scriptOperationOrders));
}
get workStations(): FormArray {
return this.formOperation.get('workStations') as FormArray;
}
HTML
<div
class="card"
[ngClass]="{'bg-principal': idx === 0, 'bg-alternative': idx !== 0}"
formArrayName="workStations"
*ngFor="let workstation of workStations.controls; index as idx"
>
<div class="card-body" [formGroupName]="idx">
<div class="form-row">
<div class="form-group col-md-1">
<label>Id Oper.</label>
<input
type="text"
name="idOperation"
class="form-control"
disabled
formControlName="rank" <!-- whatever with or without binding gives error -->
/>
</div>
<div class="form-group col-md-2">
<label>Time</label>
<input
type="time" class="form-control" name="defaultTime"
[formControlName]="defaultTime" <!-- whatever with or without binding gives error -->
/>
</div>
</div>
</div>
</div>
Models
export class IScriptOperationsModel extends Serializable {
public description: string;
public descriptionCode: string;
public scriptOperationOrders: IScriptOperationOrdersModel[]; // array which I need
}
export class IScriptOperationOrdersModel extends Serializable {
public rank: number;
public defaultTime: string;
public asset?: IAssetModel;
public provider?: IProviderModel;
}
error-handler.service.ts:87 Error: Cannot find control with path: 'workStations -> 0 -> rank' # undefined:undefined
NOTE: I already looked at some answers here on the site such as this, this and this , but none of them solved this problem!
your problem is here :
this.formOperation.setControl(
'workStations',
this.fb.array(this.operationModel.scriptOperationOrders) <== here the problem
);
you are passing an array of IScriptOperationOrdersModel instead of array of form group.
To make your code working, you have to loop on every element of this.operationModel.scriptOperationOrders array , and instanciate a new FormControl object then push it in the workStations form array.
To access its elements, you can use controls[indexOfGroup].rate
You can take a look at this simple example you will understand everything.
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;
}
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 would create a form with the possibility to add inputs dynamically
I found a question about the same problem in angular 2 but I can't make it working in my exemple
Here's my component ts file :
export class AjoutProjetComponent implements OnInit {
isLinear = false;
firstFormGroup: FormGroup;
secondFormGroup: FormGroup;
constructor(private _formBuilder: FormBuilder) {}
ngOnInit() {
this.secondFormGroup = this._formBuilder.group({
pers: [this._formBuilder.array([this.createItem()])]
});
}
createItem(): FormGroup {
return this._formBuilder.group({
name: ['', Validators.required]
poste: ['', Validators.required],
});
}
addItem(): void {
const control = < FormArray > this.secondFormGroup.controls['pers'];
control.push(this.createItem());
}
}
then HTML file
<mat-step [stepControl]="secondFormGroup">
<form [formGroup]="secondFormGroup">
<ng-template matStepLabel>Constituez votre équipe</ng-template>
<div formArrayName="pers">
<mat-form-field *ngFor="let control of secondFormGroup.controls.pers.controls; let i= index">
<input matInput placeholder="Nom collaborateur" formControlName="name" required>
</mat-form-field>
</div>
</form>
</mat-step>
<div>{{secondFormGroup.value | json}}</div>
When I click in my favorite icon I get this error :
ERROR TypeError: control.push is not a function at AjoutProjetComponent.addItem
How can I make adding dynamically inputs working ?
UPDATE
I have updated my html code so that I could print two inputs but when I run my code I get this error now
ERROR Error: Cannot find control with path: 'pers -> name'
You did not declare your FormArray properly. You use arrays only to initialize simple FormControls, not FormGroups or FormControls, change to :
this.secondFormGroup = this._formBuilder.group({
pers: this._formBuilder.array([this.createItem()]) // remove opening and closing brackets
});
To see the inputs added dynamically to the html, you need to use an ngFor loop. I think you somewhat misunderstood the usage of formArrayName, which only adds context to the template to use with FormArrays. Try this:
<ng-container formArrayName="pers">
<input placeholder="Address"
*ngFor="let control of secondFormGroup.controls.pers.controls"
[formControl]="control.controls.name" required />
</ng-container>
And read more about FormArrayName directive here