Angular reactive form - checkboxes - Initialize formArray with exisitng array - javascript

I have an dynamic array of lkBoardTypeList list fetched from server. I want to push that array to form array on initialization, basically i want to show checkboxes based on Board name or id in array. I know how to push an empty array in reactive forms.but how to initialize with an existing array.Actually its an update/edit Component
export class BoardTypeList{
id number;
descr string;
}
component :
lkBoardTypeList: LkBoardType[] = [];
SelectedLkBoardTypeList: LkBoardType[] = [];
this.boardDashboardForm = this.formBuilder.group({
id: new FormControl(this.boardObj.id, Validators.required),
location: new FormControl(this.boardObj.location),
boardType: new FormArray([])
});
this.addCheckBoxes();
..//..//
addCheckBoxes(){
this.lkBoardTypeList.forEach(() => this.boardTypeFormArray.push(new FormControl(false)));
}
get boardTypeFormArray(){
return this.boardDashboardForm.controls.boardType as FormArray;
}
loadSelected(event){
this.boardService.getBoardTypeById(event.value).subscribe( posts =>{
this.data = posts;
console.log('getBoardTypeById',this.data);
},
error => {
console.log('getBoardTypeById - error',error);
this._errorService.handleError(error);
},
() => {
this.SelectedLkBoardTypeList = this.data.boardTypes;
for (let i = 0; i < this.lkBoardTypeList.length; i++) {
..///.. what code goes here
}
});
}
And in the HTML
<div class="row">
<div class="col-sm-2">
<p-dropdown [options]="boardArray" formControlName="id" (onChange)="loadSelected($event)" placeholder=" Select "></p-dropdown>
</div>
</div>
<div class="form-group" >
<div formArrayName="boardType" *ngFor="let c of boardTypeFormArray.controls; let i = index">
<input class="col-sm-1" type="checkbox" [formGroupName]="i"/>
<span class="col-sm-5" >{{lkBoardTypeList[i].descr}}</span>
</div>
</div>
RightNow when I inspect element I see formArrayName="boardType" and checkbox element as follows:
<input class="col-sm-1 ng-untouched ng-pristine ng-valid" type="checkbox" ng-reflect-name="0">
<input class="col-sm-1 ng-untouched ng-pristine ng-valid" type="checkbox" ng-reflect-name="1">
<input class="col-sm-1 ng-untouched ng-pristine ng-valid" type="checkbox" ng-reflect-name="2">
I am guessing the ng-reflect-name="0" is nothing but value of 'i'. What am trying to set value of the array with id from lkBoardTypeList which is 10,20,30.
Also please let me know how to set the values for checked in the checkboxes.
SelectedLkBoardTypeList - say for example the entire list has values (10,20,30). selectedList contains only 10 and it has to be checked.

First of all, you should use [formControlName]="i" instead of [formGroupName]="i"
ng-reflect-${name} attribute is added for debugging purposes and shows the input bindings that a component/directive has declared in its class.
FormControlName directive declares
#Input('formControlName') name!: string|number|null;
So, you're right: ng-reflect-name="0" describes value you're passing in formControlName input.
If you want to prepopulate FormArray with SelectedLkBoardTypeList then you can just check if specific item exists in SelectedLkBoardTypeList. Let's say you have:
SelectedLkBoardTypeList: LkBoardType[] = [
{
id: 10,
descr: 'Type 1'
}
];
then your addCheckBoxes method should be as follows:
addCheckBoxes() {
this.lkBoardTypeList.forEach((type) => {
const selected = this.SelectedLkBoardTypeList.some(x => x.id === type.id);
this.boardTypeFormArray.push(new FormControl(selected))
});
}
Finally, if you want to get selected items during post execution you can map selected values in FormArray to your original array:
save() {
const model = this.boardDashboardForm.value;
const selectedTypes = model.boardType.filter(Boolean).map((_, i) => this.lkBoardTypeList[i]);
console.log(selectedTypes);
}
Ng-run Example

Related

Unable to add defaults items to angular dropdown

i'm trying to display some default selected data to my angular dropdown menu but for some reason if i push() the items from inside the subscribe() method the items does not being selected and if i try adding the items outside of this specific subscribe method it adds the items to the dropdown without any problems.
NOTE: If i log my array, it gives me all the items, including the items that i added from inside the subscribe()
Part of my component:
public students = []
public loadedSelectedStudents = []
public selectedStudents = []
// Get all selected students
this.studentService.getStaffStudents(+loadedUserId).subscribe(async selectedStudents => {
for (var i = 0; i < selectedStudents.length; i++) {
var selectedStudentId: number = selectedStudents[i].studentId
var studentModel: StaffStudentModel = new StaffStudentModel()
studentModel.userId = +loadedUserId
studentModel.studentId = selectedStudentId
var resStudent: StudentModel = await this.studentService.getStudent(selectedStudentId).toPromise()
// THIS IS NOT WORKING
this.loadedSelectedStudents.push({ student_id: studentModel.studentId, student_name: resStudent.name })
this.selectedStudents.push({ student_id: studentModel.studentId, student_name: resStudent.name})
this.changeDetectorRef.detectChanges();
}
})
// THIS IS WORKING
this.selectedStudents.push({student_id: 5, student_name: "Jonh"})
Part of my service:
getStaffStudents(userId: number) : any {
return this.http.get<StudentModel[]>(this.baseUrl + 'Api/GetStaffStudents/' + userId).pipe(map(res => res))
}
My dropdown settings and FormGroup:
this.studentsDropDownSettings = {
idField: 'student_id',
textField: 'student_name',
allowSearchFilter: true,
noDataAvailablePlaceholderText: "No Available Students"
}
this.studentsDropDownForm = this.fb.group({
studentsItems: [this.selectedStudents]
});
And my HTML element:
<div *ngIf="showStudensMenu" class="form-group">
<form [formGroup]="studentsDropDownForm">
<label for="exampleFormControlSelect1">Students *</label>
<ng-multiselect-dropdown
[settings]="studentsDropDownSettings"
[data]="students"
(onSelect)="onStudentSelected($event)"
(onSelectAll)="onAllStudentsSelected()"
(onDeSelect)="onStudentDeSelected($event)"
(onDeSelectAll)="onAllStudentsDeSelected()"
formControlName="studentsItems">
</ng-multiselect-dropdown>
</form>
</div>
But the multiselect is consuming [data]="students" shouldn't you push the new items to students[]? If the selected values are indeed read from this.selectedStudents you could trigger change detection by resetting the items using spread ... e.g this.selectedStudents = [...this.selectedStudents].
This happens because the html part of the component loads but the data
hasn't come from the api yet i.e. subscription is not yet complete.
You need to set a flag, say isLoaded as true on successful subscription and update the form as well. In html, use a ngIf statement so that the dropdown does not load without the data.
<div *ngIf="isLoaded && showStudensMenu" class="form-group">
<form [formGroup]="studentsDropDownForm">
<!-- your code -->
</form>
</div>
Subscription:
this.studentService.getStaffStudents(+loadedUserId).subscribe(async selectedStudents => {
//your code
this.isLoaded=true;
//Update the Form
this.studentsDropDownForm = this.fb.group({
studentsItems: [this.selectedStudents]
});
}
})
Don't forget to initialize isLoaded as false;

Angular dynamic checkbox Filtering implementation

thanks in advance
my Requirement is to make a custom filter with name wise search(done) and checkboxes which filters a Table's Rows(array of objects) by matching the checkbox value with the Row['tags'] (array of strings) and returns row if the tags array consist of value in a checkbox ,
The problem is that the filters(checkbox) is obtained from DB and Dynamically populated thus I cannot use ngmodel
Any implementation ideas are highly appreciated, I've seen a lot of questions with static filters and some filters using pipes but how to handle the dynamic case
so far my implementation,
Template:
<div id="searchByTag" *ngFor="let tag of tagList">
<input
type="checkbox"
(change)="filterByTags(tag, $event)"
/>{{ tag }}
</div>
Ts:
rows=[{},{}] //from db
temp = rows // copied when getting row from db
filterByTags(FilterTag, event) {
if (event.target.checked) {
const filteredRow = this.rows.filter((obj) => {
return tag.includes(FilterTag.toLowerCase());
});
this.rows = filteredRow;
} else {
return (this.rows = this.temp);
}
}
a Row object:
{
"xx":'yyy',
....,
"tags" : [
"org",
"pcb",
]
}
other problem is that the filtering technique currently returns only one row which matches the condition (cleared), but the main thing is the dynamic implementation of tags
you can have ngModel:
if this is your checkboxes = ["org", "pcb"];
then all you need is a record to bind checkboxes values to it:
checkboxes: {[id: string]: {value: any}} = {};
for(let tag of this.tags) {
this.checkboxes[tag] = {value: false}
}
now in your template:
<input type="checkbox" *ngFor="let item of tags"
[(ngModel)]="checkboxes[item].value">
you can see this in this stackblitz:
stackblitz

How to do pop operation in javascript when unchecked checkbox?

I have just the two checkbox whose codes looks like:
<div *ngFor="let item of documents">
<label>
<input type="checkbox" value="{{item.docId}}" [(ngModel)]="item.checked" [name]="item.docName"
(change)="editPartyRolesSubmit($event)"
/>
<span innerHTML="{{item.docName}}"></span>
</label>
</div>
Here i have only used two checkbox as:
The function editPartyRolesSubmit($event) called is:
public documents: Array<Document> = []
public onlyTruedocuments: Array<Document> = [];
editPartyRolesSubmit(event) {
this.documents.filter(x => x.checked).map(x => {
console.log("entered in check");
this.onlyTruedocuments.push(x);
console.log(x);
})
}
The Json data is pushed three times as it should be only pushed two times though i have just clicked two times.
But when i print them in the .html page then though the checkbox is two,It is printed three times :
<li *ngFor="let ot of onlyTruedocuments; index as i">
{{ot.docName}}
</li>
It is printing like this:
How can i remove this redundant data?
If I understand correctly, I would do in this way,
I have used (ngModelChange) instead of (change)
I have passed the current item to the ngModelChange function.
HTML:
<div *ngFor="let item of documents">
<label>
<input type="checkbox" value="{{item.docId}}" [(ngModel)]="item.checked" [name]="item.docName"
(ngModelChange)="editPartyRolesSubmit($event,item)"/> // will pass an item to the function
<span innerHTML="{{item.docName}}"></span>
</label>
</div>
<li *ngFor="let ot of onlyTruedocuments; index as i">
{{ot.docName}}
</li>
TS file:
export class YourComponent {
documents = [{
docId: 1,
checked: false,
docName: 'Prashant'
},
{
docId: 2,
checked: false,
docName: 'Venkat'
}
, {
docId: 2,
checked: false,
docName: 'Perry'
}];
public onlyTruedocuments: any = [];
editPartyRolesSubmit(event, obj) {
// Take the index of an Item checked
let index = this.onlyTruedocuments.indexOf(obj);
// Check for event i.e it is checked or unchecked
if (event) {
if (index == -1) {
// If the index is -1 then that means its not a duplicate so push into an array
this.onlyTruedocuments.push(obj);
}
}
else {
// If it is unchecked then we surely know that the item has to be removed from the array so by an index of the particular item we can [splice][1] the item
this.onlyTruedocuments.splice(index, 1)
}
}
}
No need to filter the source array to get the checked items.
A Working StackBlitz Example with Sample data.
Have a unique check before you push to onlyTruedocuments. This way even when user click many times the object would still have unique values as expected.
editPartyRolesSubmit(event) {
this.documents.filter(x => x.checked).map(x => {
console.log("entered in check");
const exists = this.onlyTruedocuments.filter((f)=>f.docId == x.docId);
if(exists.length==0){
this.onlyTruedocuments.push(x);
}
console.log(x);
})
}
One way to collect unique elements only is to define this.onlyTruedocuments not as an array, but as a Set, initialised as:
this.onlyTrueDocuments = new Set();
Then in the event handler do:
editPartyRolesSubmit(event) {
this.documents.forEach(x => x.checked && this.onlyTruedocuments.add(x));
console.log([...this.onlyTrueDocuments]); // Use spread syntax to get converson to array
}

Hide other elements in list

I have the below code:
<li *ngFor="let item of Array let i = index">
<span>
<label (dblclick)="editTag($event,i)">
{{item.tag}}
</label>
<input type="text" #tagInput />
</span>
</li>
The code is in a for loop. When I click on a label, all labels should be hidden and the input should be visible. Currently, when I click on each label, the other remain open. How do I hide the other span when clicking on any item?
I have below code in .ts
#ViewChild('tagInput') tagNameTextInput: ElementRef;
editTag(event: any,index: any) {
//console.info(event);
this.tagNameTextInput.nativeElement.hidden = true;
this.tagNameTextInput.nativeElement.previousElementSibling.hidden = false;
let initialValue = event.target.childNodes[0].nodeValue.trim();
event.target.hidden = true;
event.target.nextElementSibling.hidden = false;
event.target.nextElementSibling.value = initialValue;
console.log(index);
// this.checkListNameHidden = true;
// this.checkListNameTextInput.nativeElement.value = initialValue;
// this.checkListNameTextInput.nativeElement.focus();
event.stopPropagation();
}
How to solve this?
You have multiple children, So you need to use #ViewChildren instead of #ViewChild.
Also in your ngFor loop you do not have unique template reference #tagInput. Use QueryList with ElementRef.
Try : #ViewChildren('tagInput') tagNameTextInput: QueryList<ElementRef>;
instead of
#ViewChild('tagInput') tagNameTextInput: ElementRef;.
Import QueryList from #angular/core.
Like this import { Component, QueryList } from '#angular/core';
the best aproach is add a new property to "item", (e.g. called "editing") so
<li *ngFor="let item of Array let i = index">
<span>
<label *ngIf="!item.editing" (dblclick)="item.editing=true;">
{{item.tag}}
</label>
<input *ngIf="item.editing" [(ngModel)]="item.tag" type="text" (blur)="item.editing=false" />
</span>
</li>
See several things:
1.-in a click of label, the variable becomes true, so the inpĆ¹t is showed
2.-in blur of item, the variable becomes false, so the label is showed
3.-Use [(ngModel)] to relation between the input and the value

Angular 4 nested form update

I've got the following 'triple level' nested form:
FormGroup->ArrayOfFormGroups->FormGroup
Top level (myForm):
this.fb.group({
name: '',
description: '',
questions: this.fb.array([])
});
Nested form array element for 'questions':
this.fb.group({
priority: ['1'],
params: this.fb.group({parameter: ['']})
});
Nested form group element for 'params' is a key:value object of random length.
I'm using the following ngFor to go through elements:
<tr *ngFor="let questionConfigForm of myForm.controls.questions.controls; let i=index" [formGroupName]="i">
...
<div *ngFor="let param of objectKeys(questionConfigForm.controls.params.controls)" formGroupName="params">
<input type="text" [formControlName]="param">
I've got the following behavior:
When I'm updating any of the fields on first two form levels I could instantly see changes in corresponding form controls values with {{myForm.value | json}}.
But if I input something in one of 'params' controls I couldn't see any changes in myForm values, but the form data for 'params' controls will be updated if I will make any changes in corresponding 'questions' form.
For me it looks like 'param' form control receives input data, but doesn't trigger some update event, and I don't know how to fix that, except writing my own function to react on (change) and patchValue in form..
So my question is how to make 'params' controls update myForm without that strange behavior?
UPD:
initQuestionConfig() {
return this.fb.group({
priority: ['1'],
params: this.fb.group({parameter: ['']}),
});
}
addQuestionConfig() {
const control = <FormArray>this.myForm.controls['questions'];
const newQuestionCfg = this.initQuestionConfig();
control.push(newQuestionCfg);
}
Finally the problem is solved.
The root of this issue was the way I've cleaned up already existing 'params'.
To remove all parameters from 'questions' I used the following code:
const control = <FormArray>this.myForm.controls['questions'];
control.controls[index]['controls'].params = this.fb.group([]);
And the reason of those glitches was this new 'fb.group' instance.
Now I'm removing params one by one, keeping original formGroup instance and it works as expected:
const control = <FormArray>this.myForm.controls['questions'];
const paramNames = Object.keys(control.controls[index]['controls'].params.controls);
for (let i = 0; i < paramNames.length; i++) {
control.controls[index]['controls'].params.removeControl(paramNames[i]);
}
#MilanRaval thanks for your time again :)
Try this: Give formArrayName and formGroupName like below...
<div formArrayName="testGroup">
<div *ngFor="let test of testGroup.controls; let i=index">
<div [formGroupName]="i">
<div class="well well-sm">
<label>
<input type="checkbox" formControlName="controlName" />
</div>
</div>
</div>
</div>

Categories

Resources