*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.