Reactive form angular using FormArrayName - html

In my form I need to add phones, I have seen that in the database you are saving a string array with the phone numbers that I added, but the moment the value returned from the database is set to the form, only the first array value is shown .
I would like to display all array values that contain number of phones in string format.
component.html:
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="start">
<div formArrayName="phones" fxFlex="30">
<div *ngFor="let phone of phones.controls; index as i">
<mat-form-field>
<input matInput [formControlName]="i" placeholder="Phone Number">
</mat-form-field>
</div>
</div>
<div fxFlex="30">
<button mat-raised-button class="blue" (click)="addPhone()">Add</button>
</div>
</div>
component.ts:
this.myForm = this._formBuilder.group({
phones: new FormArray([new FormControl('')]),
});
this.values$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((values) => {
// values = {phones: ['999999', '88888', '77777']}
if (values) {
this.myForm.patchValue(values, { emitEvent: false });
}
});
get phones(): FormArray { return this.myForm.get('phones') as FormArray; }
addPhone(): void { this.phones.push(new FormControl('')); }
Even returning more than one value inside the array only displays the first array value on my form, what am I doing wrong?

patchValue won't create any form controls for you, so when you call patchValue it only sets the value for the first form control in your form array, because you only created one.
You have to call addPhone for each phone number or create form inside the describe when you already know how many form controls there should be, but you still need to create as many form controls as number of elements in your array.
this.myForm = this._formBuilder.group({
phones: new FormArray(),
});
this.values$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((values) => {
if (values && values.phones) {
phones.forEach(phone => this.addPhone(phone));
}
});
get phones(): FormArray { return this.myForm.get('phones') as FormArray; }
addPhone(phone = ''): void { this.phones.push(new FormControl(phone)); }

Related

Getting error after upgrading from Angular 8 to 12 - "Property 'controls' does not exist on type 'AbstractControl' "

I had a fully functioning code in Angular 8. I decided to upgrade from 8 to Angular 12.
I have a dynamic reactive form. The form is of question answer format.
Based on who is login in the questions change. The answer is of Yes/No format. Based on who is login in and what answer option is chosen textbox dropdown and checkbox or all the there mentioned appreas.
Here is my code for the form
Component.ts
constructor(
private router: Router,
private datePipe: DatePipe,
private route: ActivatedRoute,
private formBuilder: FormBuilder) {
this.dynamicForm = this.formBuilder.group({
questions: new FormArray([])
});
this.CreateForm();
}
get f() { return this.dynamicForm.controls; }
get t() { return this.f.questions as FormArray; }
CreateForm() {
if (this.formdetail.questionList) {
// CREATE Questions
if (racialQues !== this.formdetail.questionList[i].quetext && this.formdetail.questionList[i].quetext !== auditQues) {
this.t.push(this.formBuilder.group({
quesIndex: [i + 1],
...
controlName: [this.formdetail.questionList[i].selectedAns, [Validators.required]]
}));
} else if (this.formdetail.questionList[i].quetext === auditQues) {
this.t.push(this.formBuilder.group({
quesIndex: [i + 1],
......
selectedValue: [this.formdetail.auditTrail.selectedValue, this.reasonValidator],
}));
} else if (this.formdetail.questionList[i].quetext === racialQues) {
this.t.push(this.formBuilder.group({
quesIndex: [i + 1],
......
selectedAuxAns: [this.formdetail.questionList[i].val, [this.auxAnsValidator]]
}));
}
}
}
Here is the html
<form [formGroup]="dynamicForm">
<!-- Question Start -->
<div *ngFor="let ticket of t.controls; let i = index">
<div [formGroup]="ticket" class="form-row">
<div class="input-group col-md-12" style="padding: .15em .5em">
<div class="input-group-prepend col-md-10" style="padding: 0; margin: 0;">
<label class="input-group-text w-15">{{i + 1}}</label>
<label class="input-group-text w-100" style="text-align: left; white-space: normal;">{{ticket.controls.name.value}}</label>
</div>
<select formControlName="controlName" class="form-control col-md-2" style="height:auto !important;"
[ngClass]="{ 'is-invalid': submitted && ticket.controls.controlName.errors }"
(change)="onChange($event.target.value, i)">
<option [ngValue]="null"> --Select-- </option>
<option *ngFor="let ansItem of formdetail.questionList[i].answerList" [ngValue]="ansItem" >{{ansItem.anstext}}</option>
</select>
<div *ngIf="submitted && ticket.controls.controlName.errors" class="invalid-feedback" style="height:auto !important;"
[ngClass]="{ 'input-group-append': submitted && ticket.controls.controlName.errors,
'col-md-1': submitted && ticket.controls.controlName.errors }">
<div *ngIf="ticket.controls.controlName.errors.required">Required</div>
</div>
.
.
.
.
</div>
/div>
</div>
<!-- Question End -->
</form>
Now I am getting error in "ticket.control"
Property 'controls' does not exist on type 'AbstractControl'.
I am not sure why I am getting this error after upgrade.
I have tried ticket.['control'] or ticket.get('controlName') as suggested by other article of similar error in stackoverflow but nothing works
Here is the snapshot of error
Any help will be appreciated.
Thanks
your t is of type FormArray, FormArray.controls array is of type AbstractControl[], thus, each ticket at
<div *ngFor="let ticket of t.controls; let i = index">
line is of type AbstractControl, which does not have controls property as is seen in TS error in your screenshot. TS does not know that each ticket actually is of type FormGroup (set here this.t.push(this.formBuilder.group({...}) and which has controls property).
So you can try to add another getter tControls:
get f() { return this.dynamicForm.controls; }
get t() { return this.f.questions as FormArray; }
get tControls() { return this.t.controls as FormGroup[]; }
and use it within *ngFor:
<div *ngFor="let ticket of tControls; let i = index">
<div [formGroup]="ticket" class="form-row">
...
Please note, that now [formGroup] will also be properly filled with FormGroup and not AbstarctControl
One of your Angular upgrades probably added
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInputTypes": true,
}
to your tsconfig.json file, you can verify it just by setting these values to false and rebuilding your app, but I recommend to leave these strict checks.
This is a generic answer (so generic code) to help fixing such an issue.
Let's suppose you have a loop through a field (call it elements) of type FormArray. This field is part of a form of type FormGroup
In your ts logic code (logic implementation) you should have something like:
form = new FormGroup({
//.. other fields
elements: new FormArray([]),
});
In your template (html file), you need to toop through the elements field:
<li
*ngFor="let element of elements.controls">
{{ element.value }}
</li>
In order to tell angular cli that elements has a property of controls (so of type FormArray), you need to format it in your ts logic code as the following (implement a getter for elements field):
get elements() {
return this.form.get('elements') as FormArray;
}
'controls' isn't a property of an AbstractControl. instead you can use your form group, so 'dynamicForm.controls.controlName'

Iterate over array of objects and display key and value for each object in ngFor in Angular 12

In an Angular 12 app the data returned from an API is
data = {
"options":[
{
"Yes": 1
},
{
"No": 0
}
]
}
My requirement is to iterate over this array and display radio buttons dynamically in the html as shown below.
<div *ngFor="let option of data.options;">
<input type="radio" class="form-control" name="rdgroup" value="{{option.key}}">
<label>{{option.value}}</label>
</div>
I want display labels for radio buttons to be 'Yes' and 'No' and their values should be 1 and 0 respectively. Currently nothing is displayed. How do I fix it?
You will need to modify you data in order to render radio buttons.
Check this stackblitz: https://stackblitz.com/edit/angular-material-with-angular-sidenav-spz9oq?file=app/app.component.html
Use the below method on your component:
options = this.data.options.map( (op) => {
const key = Object.keys(op)[0];
return {
"value" : Object.keys(op)[0],
"key": op[key]
}
});
and in template
<div *ngFor="let option of options;">
<input type="radio" class="form-control" name="rdgroup" value="{{option.key}}">
<label>{{option.value}}</label>
</div>
You can try in this way, It may help you to get expected output!
edit your component.ts file, and add below defined method.
getEntrires(object: any) {
return Object.entries(object);
}
this method will return you Object entry with it's KEY & VALUE in Array
For Example:
let output = getEntrires({"Yes": 1});
console.log(output) // [Array[2]] => it will be two dimensional array
Now in your html you can consume this in this way
<div *ngFor="let option of data.options;">
<input type="radio" class="form-control" name="rdgroup" value="{{getEntrires(option)[0][1]}}">
<label>{{getEntrires(option)[0][0]}}</label>
</div>

Angular change displayed value

I have an input that retrieve a numeric value. I would like to change the display on the html side, basically if the variable equals to let s say 7 the value displayed becomes 'abcdefg' instead of 7. On the contrary if I type abcdefg I would like for the form to understand that the value that should be stored is 7 and not the string. Is there a way to do that only on the HTML side ?
<div class="col-6">
<mat-form-field>
<input matInput placeholder="Cardinality Max" required #asbiepCardinalityMax
(change)="changeMax(asbiepCardinalityMax.value)"
(focus)="focusInMax(asbiepCardinalityMax.value)"
[(ngModel)]="asAsbiepDetail().cardinalityMax"
(ngModelChange)="onChange()"
[disabled]="!isEditable() || !asAsbiepDetail().used"
(keypress)="numberOnly($event)">
</mat-form-field>
</div>
You want to separate out the logic for controller to view binding and view to controller. You want to add separate functions for updating and reading. You can either add an event handler to set the value onChange and remove the () from the ngModel (removing the view -> controller updating), or use typescript getters and setters to bind to one value, with different logic.
Option 1:
HTML:
<div class="col-6">
<mat-form-field>
<input matInput placeholder="Cardinality Max" required #asbiepCardinalityMax
(change)="changeMax(asbiepCardinalityMax.value)"
(focus)="focusInMax(asbiepCardinalityMax.value)"
[ngModel]="fieldValue()"
(ngModelChange)="onChange()"
(change)="setFieldValue($event)"
[disabled]="!isEditable() || !asAsbiepDetail().used"
(keypress)="numberOnly($event)">
</mat-form-field>
</div>
Controller:
#Component({})
export class Controller {
realValue: number = 7;
setFieldValue($event) {
let newValue = $event.target.value;
//Code here to turn newValue into something correct for real value
if (newValue == 'abcdefg') {
realValue = 7;
}
}
fieldValue(): string {
if (realValue === 7) {
return 'abcdefg';
}
};
}
Option 2:
HTML:
<div class="col-6">
<mat-form-field>
<input matInput placeholder="Cardinality Max" required #asbiepCardinalityMax
(change)="changeMax(asbiepCardinalityMax.value)"
(focus)="focusInMax(asbiepCardinalityMax.value)"
[(ngModel)]="fieldValue"
(ngModelChange)="onChange()"
[disabled]="!isEditable() || !asAsbiepDetail().used"
(keypress)="numberOnly($event)">
</mat-form-field>
</div>
Controller:
#Component({})
export class Controller {
realValue: number = 7;
set FieldValue(newValue): void {
//Code here to turn newValue into something correct for real value
if (newValue == 'abcdefg') {
realValue = 7;
}
}
get fieldValue(): string {
if (realValue === 7) {
return 'abcdefg';
}
};
}
And then you use realValue where you want.
The advantage of the first is you can pick what event you want to fire the function that sets the value on the controller - you might want change, keyup, keydown, blur - you can have any of the classic javascript events. The second approach requires onChange.

input values does not fill correctly in FormArray

I have trouble with filling the right values into input fields of a FormArray. Here is my html:
<div formArrayName="roles" *ngFor="let role of roles.controls; let i=index">
<div [formGroupName]="i">
<div class="form-group row">
<label class="col-md-2 col-form-label" attr.for="{{'roleNameId' + i}}">Role Name {{i+1}}</label>
<div class="col-md-6">
<input class="form-control"
attr.id="{{'roleNameId' + i}}"
type="text"
formControlName="roleName">
</div>
<label class="col-md-2 col-form-label" attr.for="{{'identifierId' + i}}">
<input
type="checkbox"
attr.id="{{'identifierId' + i}}"
formControlName="identifier"> identifier
</label>
<div class="col-md-2">
<button type="button" (click)="removeRole(i)" class="btn btn-danger oi oi-trash"></button>
</div>
</div>
</div>
</div>
So this html is being rendered x times depending on the number of roles i have. This works just fine and no errors are shown.
But here is the Problem: When i remove one Role which is not the last of the Array, the input fields filled with the right roles change.
Here are the roles:
When i remove role 2 for example, the form looks like this:
role2 was being removed correctly, but role3 disappeared and the last two roles have the name of the last role (role8)
Why this happens? When i look to the formArray where all roles are saved, the array has the correct values, here is the evidence:
why the correct values are not shown in the view? Thank you for your help
EDIT: Here is the relevant typescript code:
this.sourcesForm = this.fb.group({
sourceName: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(255)]],
sourceType: ['QUERY'],
dsl: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(255)]],
list: [''],
roles: this.fb.array([this.buildRole()]),
database: [''],
});
// save roles to temporary variable to map for the roles Formarray
let roles = [];
dslStatement.roles.forEach(role => {
let oneRole = {
roleName: role.name,
identifier: true
}
if (role.role === 'IDENTIFIER')
oneRole.identifier = true;
else
oneRole.identifier = false
roles.push(oneRole);
});
// convert the variable to FormGroups, then the FormGroups to one FormArray
// and at last set the FormArray to the roles of sourcesForm
const rolesFormGroups = roles.map(roles => this.fb.group(roles));
const rolesFormArray = this.fb.array(rolesFormGroups);
this.sourcesForm.setControl('roles', rolesFormArray);
buildRole(): FormGroup {
return this.fb.group({
roleName: '',
identifier: true
});
}
addRole() {
this.roles.push(this.buildRole());
}
removeRole(i: number) {
this.roles.removeAt(i);
console.log(this.roles.value);
console.log(this.roles.controls);
}
UPDATE
I built a stackblitz attempting to demonstrating the issue here: https://stackblitz.com/edit/angular-dk-formarray
It uses Angular 5.0 and does NOT have the problem.
I was able to reproduce this with the app from my course referenced above (Angular 4):
After deleting the "a" row, I get this:
Notice that the "a" is gone, but now I have two "c"s instead of "b" and "c".
Interestingly, if I save and then come back to the page, it displays correctly with "b" and "c".
So it appeared to be a bug that was fixed in Angular 5.
I was able to find a "work around" that worked for me in Angular 4.x:
deleteTag(index: number): void {
this.tags.removeAt(index);
this.productForm.setControl('tags', this.fb.array(this.tags.value || []));
}
You may be able to do something similar to this (but I'm not sure your roles are set up just like my tags?)
But you are probably better off moving to Angular 5.
try this:
addRole() {
this.roles = this.sourcesForm.get('roles') as FormArray;
this.roles.push(this.buildRole());
}
instead of
addRole() {
this.roles.push(this.buildRole());
}

How to set two decimal places of input fields?

I'm working with Angular2 and TypeScript. I have input fields values set in .ts file. When I run my application it displays them as whole numbers and I will like to display them as two decimals for example 8.00 instead of just 8.
Here is part of my .ts file:
ngOnInit() {
if (!this.phast.operatingCosts) {
let defaultCosts: OperatingCosts = {
fuelCost: 8.00,
steamCost: 10.00,
electricityCost: .080
}
this.phast.operatingCosts = defaultCosts;
this.startSavePolling();
}
}
I'm using ngModel. Here is one input field .html:
<div class="input-group">
<input name="electricityCost" type="number" step="any" class="form-control" id="electricityCost" (input)="startSavePolling()"
[(ngModel)]="phast.operatingCosts.electricityCost">
<span class="units input-group-addon">$/kWh</span>
</div>
You can use a custom filter like
function decimalValue($filter) {
return (input, size) => {
return $filter('number')(input, size)
};
}