Unable to add defaults items to angular dropdown - javascript

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;

Related

ngFor don`t show all items immediately after place_changed event from google place autocomplete

I need to add result to the list after place_changed event. I display the list below the input in which I find locations. Event works and result is pushed to array items. But the problem is that new added item don`t display immediately. It displayed after some time or when I click on form where this input is displayed.
.ts:
#ViewChild('locationInput', { static: true }) input: ElementRef;
autocomplete;
items = [];
ngOnInit() {
this.autocomplete = new google.maps.places.Autocomplete(this.input.nativeElement, this.localityOptions);
this.autocomplete.addListener('place_changed', () => {
this.addToListSelectedItem();
});
}
public addToListSelectedItem() {
if (this.input.nativeElement.value) {
this.items.push(this.input.nativeElement.value);
this.input.nativeElement.value = '';
}
}
.html:
<input
#locationInput
class="shadow-none form-control"
formControlName="locality"
placeholder=""
[attr.disabled]="locationForm.controls['region'].dirty ? null : true"
/>
<div *ngFor="let item of items; let index = index">
<div class="listOfLocation">
<div class="itemList">{{ item }}</div>
<img [src]="icons.cross" class="delete-button-img" alt="edit-icon" (click)="deleteTask(index)" />
</div>
</div>
Thanks for the help!
Probably your component Change Detection Strategy is OnPush or google autocomplete is running outside zone.js:
changeDetection: ChangeDetectionStrategy.OnPush
And since items is array and it is stored in memory by reference you need to run manually change detenction:
constructor(private cdr: ChangeDetectorRef)
public addToListSelectedItem() {
...
this.input.nativeElement.value = '';
this.cdr.detectChanges();
Even better would be to work with an RxJS Subject, an Observable for items$ and use the async pipe. The async pipe works like magic what concerns updating the template :-)!
#ViewChild('locationInput', { static: true }) input: ElementRef;
autocomplete;
itemsSubject$ = new Subject<any[]>();
items$ = this.itemsSubject$.asObservable();
// Use a separate array to hold the items locally:
existing = [];
ngOnInit() {
this.autocomplete = new google.maps.places.Autocomplete(this.input.nativeElement, this.localityOptions);
this.autocomplete.addListener('place_changed', () => {
this.addToListSelectedItem();
});
}
public addToListSelectedItem() {
if (this.input.nativeElement.value) {
// Use spread syntax to create a new array with the input value pushed at the end:
this.existing = [...this.existing, this.input.nativeElement.value];
// Send the newly created array to the Subject (this will update the items$ Observable since it is derived from this Subject):
this.itemsSubject$.next(this.existing);
this.input.nativeElement.value = '';
}
}
// I added the deleteTask implementation to show you how this works with the subject:
deleteTask(index: number) {
// The Array "filter" function creates a new array; here it filters out the index that is equally to the given one:
this.existing = this.existing.filter((x, i) => i !== index);
this.itemsSubject$.next(this.existing);
}
And in the template:
<input
#locationInput
class="shadow-none form-control"
formControlName="locality"
placeholder=""
[attr.disabled]="locationForm.controls['region'].dirty ? null : true"
/>
<!-- Only difference here is adding the async pipe and using the items$ Observable instead -->
<div *ngFor="let item of items$ | async; let index = index">
<div class="listOfLocation">
<div class="itemList">{{ item }}</div>
<img [src]="icons.cross" class="delete-button-img" alt="edit-icon" (click)="deleteTask(index)" />
</div>
</div>
For a working example of the RxJS Subject in this concept, see https://stackblitz.com/edit/angular-ivy-dxfsoq?file=src%2Fapp%2Fapp.component.ts.
Maybe beyond this question, but since you're using a reactive form, why not use this.locationForm.get('locality').setValue('...') to set the input instead of using a ViewChild for working with the input? More control this way then using the ViewChild.

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

Display user data from api based on search value using angular primeng

I am working on Angular 7 application using angular primeng and new to Angular. This is the scenario.
a) Fetched user data from external api but i need to display user based on the input search. I tried to read the following documentation "Multiple" select https://www.primefaces.org/primeng/#/autocomplete but not able to display value based on the search.
b) I have added Add button on the right side of search section. Upon clicking the add button, user should be displayed below it.
Please see the code snippet.
home.component.html
<div class="ui-g">
<div class="ui-g-9">
<p-autoComplete [(ngModel)]="patients" [suggestions]="filteredUsersMultiple" (completeMethod)="filterCountryMultiple($event)"
[minLength]="1" placeholder="Users" field="name" [multiple]="true">
</p-autoComplete>
<ul>
<li *ngFor="let c of patients">{{c}}</li>
</ul>
</div>
<div class="ui-g-3">
<button pButton type="button" label="Add" class="ui-button-secondary"></button>
</div>
</div>
country.service.ts
export class CountryService {
constructor(private http: HttpClient) { }
getUsers() {
return this.http.get('https://jsonplaceholder.typicode.com/users');
}
}
home.component.ts
filterCountryMultiple(event) {
this.userService.getUsers().subscribe(
(val: any[]) => {
this.filteredUsersMultiple = val.map(user => user.username);
console.log(this.filteredUsersMultiple);
}
)
}
It would be really helpful if somebody could help me to find on how to display user data based on searching and display it when clicking Add button.
As per the doc https://www.primefaces.org/primeng/#/autocomplete use completeMethod to query for the results. The event contains the query text. Then create a search function and compare both query and the filteredUsersMultiple.
filterCountryMultiple(event) {
let query = event.query;
this.userService.getUsers().subscribe(
(val: any[]) => {
this.filteredUsersMultiple = val.map(user => user.username);
this.checkUsers = this.mySearch(query, this.filteredUsersMultiple);
});
}
mySearch(query, filteredUsersMultiple: any[]):any[] {
let filtered : any[] = [];
for(let i = 0; i < filteredUsersMultiple.length; i++) {
let data = filteredUsersMultiple[i];
if(data.toLowerCase().indexOf(query.toLowerCase()) == 0) {
filtered.push(data);
}
}

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
}

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