*ngFor ion-select, value then selected used to populate next ion-select menu on same page

Capture

I populate the first drop-down menu using *ngFor, then send the value to a PHP API to get the list of students in the class selected. I do recieve the information, as displayed in the console.log, but cannot seem to get the names listed in the second drop-down menu, which is the aim!

<form (submit)="confirmClasse($event, $classes)">
  <ion-list ng-app="myApp">
    <ion-select type="text" id="classe" name="classe" (ionChange)="onSelectChange($event)"
      placeholder="Selectionnez la classe" class="inputBox" #classe>
      <ion-select-option *ngFor="let classe of classes; let i = index" [value]="">{{ classe.classe }}
      </ion-select-option>
    </ion-select>
    <br>
  </ion-list>
</form>
<form (submit)="createRdv($event)">
  <ion-list>
    <ion-select type="text" id="names" name="names" class="inputBox" #names>
      <ion-select-option *ngFor="let name of names" [value]="">{{ name.nom }}</ion-select-option>
    </ion-select>
    <br>

Is it a problem with my HTML? Or am I missing something more important?

Any help will be greatly appreciated.

For info: I have just tried using…

import {ChangeDetectorRef} from ‘@angular/core’;

private changeRef: ChangeDetectorRef

setTimeout(() => {
this.changeRef.detectChanges();
},2000)

I’ve tried the above with and without the timeout, but to no avail.

Can you post the code (presumably onSelectChange()) that modifies names?

  onSelectChange(event, i) {
if (event.target) {
  this.classes.value = true;
  console.log(event.target.value);
  const chosenClasse = event.target.value;
  const url = window.location.href;
  const id = url.substring(url.lastIndexOf('/') + 1);
  this.Auth.rdvListStudents(chosenClasse, id);
  // setTimeout(() => {
  //   this.changeRef.detectChanges();
  // },2000)
}
else {
  this.classes.value = false;
  console.log(this.classes.value, "not working");
}

}

I commented out the detectChanges() as it doesn’t seem to add any value.

I’m still not seeing the part where names gets modified. What I’m specifically looking for is whether you are (a) assigning a different array to names or (b) poking around inside names changing the things inside it without actually making a new array.

Don’t do (b) - it deceives change detection into failing to perceive that anything has changed with names. The setTimeout is unneeded and unwanted. The detectChanges will, I believe, become necessary, but only if names is actually being changed (not just its contents).

onSelectChange() goes to a PHP API and calls the name list from an SQL database.

In effect, the array of objects, called names (that I need the ngFor to iterate through), is created when the user selects which class he needs.

Below is the function called at the bottom of onSelectChange(event, i)…

rdvListStudents(chosenClasse, id){
return this.http.post('http://localhost/Attendance App/myApp/src/app/api/getRdvNameList.php?id=' + id, {
  chosenClasse
}).subscribe(data => {
  console.log(Object.values(data));
  let namesData = Object.values(data);
  const grabArray = namesData[0].classe;
  if (grabArray !== undefined) {
    let navExtras: NavigationExtras = {
      state: {
        names: namesData,
      }
    }
  };

This does work, as we can see the data listed in the console, but getting the data into the second drop-down menu has so far escaped me.

Apologies if I’m saying the same thing over and over, but:

Your second ngFor is looping over a property called names. Somewhere, that property is assigned to: this.names = .... Such an assignment must happen every time a new class is selected.

1 Like

Maybe in my constructor…

constructor(private Auth: AuthService, public http: HttpClient, private router: Router, private route: ActivatedRoute, private changeRef: ChangeDetectorRef) {
this.route.queryParams.subscribe(params => {
  if (this.router.getCurrentNavigation().extras.state) {
    this.classes = this.router.getCurrentNavigation().extras.state.classes;
  }
  if (this.router.getCurrentNavigation().extras.state) {
    this.names = this.router.getCurrentNavigation().extras.state.names;
  }
});

}

I wouldn’t think queryParams would emit until you navigate somewhere else with the router, so I would not try to use it for this purpose.

One of my rules is “always separate creation of and subscription to Observables”. rdvListStudents breaks this rule by doing both. I would refactor it thusly:

interface Student { nom: string; classe?: string };
type StudentMap = { [k: string]: Student };
rdvListStudents(...): Observable<Student[]> {
  return this.http.post<StudentMap>(...).pipe(
    map(rsp => Object.values(rsp)),
    map(students => students.length > 0 && !!students[0].classe ? students : [])
  );
}

…then I would do the subscription in the page, and use it to reset names:

onClassChange(): void {
  this.auth.rdvListStudents(...).subscribe(students => this.names = students);
}

That might even obviate the need for manually firing off change detection, because the magic of the Observables returned by HttpClient.

Thanks for your help. I’ve had a play with your code but honestly, it seems a little advanced for me - I’ve no idea how to impliment it into my code. I’ll take another look in the morning and see if things become a little clearer. Thanks again.