0

I am new to Angular and trying to add a mat-select with option groups to my Angualr 6 application. I have an existing web API that has 2 URLS. One URL returns the Groups, the other returns the Items in each Group given a groupId.

This page goes into a infinite loop when it is loaded. To troubleshoot I tried to add a logger for this.groups in ngOnInit () so I could construct the array that the HTML uses, but it looks like this.groups/this.items are not initialized til invoked by the HTML page.

I must be approaching this wrong. I only only trying to add a HTML mat-select where the mat-optgroups are determined by 1 webservice/the mat-options are determined by another webservice.

I have contructed this based upon this example (https://material.angular.io/components/select/overview#creating-groups-of-options):

navigation.component.ts

import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import {HttpErrorResponse} from '@angular/common/http';
import {Component, OnInit} from '@angular/core';
import { Group } from '../group';
import { GroupService } from '../group.service';
import { Item } from '../item';
import { ItemService } from '../item.service';

@Component({
  selector: 'app-navigation',
  templateUrl: './navigation.component.html',
  styleUrls: ['./navigation.component.css']
})
export class NavigationComponent implements OnInit {

  groups: Group [] = [];
  items: Item [] = [];
  pokemonControl = new FormControl();


  constructor(private groupService: GroupService, private itemService: ItemService) {}


  ngOnInit () {
    this.getGroups();
    console.log("length: " + this.groups.length); // logs 0 as length
  }

  getGroups(): void {
    this.groupService.getGroups().subscribe(
      data => {
        this.groups = data as Group[];
        console.log(data);
      },
      (err: HttpErrorResponse) => {
        console.log (err.message);
      }
    );

  }

  getItems(department: number): void {
    this.itemService.getItems(department).subscribe(
      data => {
        this.items = data as Item[];
        console.log(data);
      },
      (err: HttpErrorResponse) => {
        console.log (err.message);
      }
    );
  }

}

group.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';

import { Group } from './group';

@Injectable({
  providedIn: 'root'
})
export class GroupService {

  private groupsUrl = 'http://localhost:8180';

  constructor(private http: HttpClient) { }

   getGroups (): Observable<Group[]> {
    const url = `${this.groupsUrl}/groups`;
    return this.http.get<Group[]>(url);
  }

  private handleError<T> (operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
    console.error(error);
    console.log(`${operation} failed: ${error.message}`);
    return of(result as T);
    };
  }

}

item.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';

import { Item } from './item';

@Injectable({
  providedIn: 'root'
})
export class ItemService {

  private itemsUrl = 'http://localhost:8180';

  constructor(
    private http: HttpClient) { }


   getItems (groupId: number): Observable<Item[]> {
    const url = `${this.itemsUrl}/groups/${groupId}/items`;
    return this.http.get<Item[]>(url);
  }

  private handleError<T> (operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
      console.error(error);
      console.log(`${operation} failed: ${error.message}`);
      return of(result as T);
    };
  }

}

HTML

<mat-select [formControl]="pokemonControl">
  <mat-optgroup *ngFor="let group of group.groupList" [label]="group.groupName"
                [disabled]="group.disabled">
    <mat-option *ngFor="let item of getItems(group.groupId).itemList" [value]="item.itemId">
      {{item.itemName}}
    </mat-option>
  </mat-optgroup>
</mat-select>
Steve M.
  • 11
  • 2

1 Answers1

0

Check this line:

let item of getItems(group.groupId).itemList

This instruction is inside another *ngFor, so it gets executed as many time as the length of group.groupList.

If it is 10 elements long, the getItems(...) methods will get called 10 times, and each time it will produce a HTTPRequest and, after the asynchronous answer, it will overwrite the items variable.

So the behavior is impredictable and the items variable is not usable since it changes several times in few seconds. What you say to experience like an infinite loops, it's likely just the change detection which responds to the new change producing a new change.

Solution:

Don't subscribe several observables if you need to use them synchronously!

Observables are asynchronous. You can't guess when it will execute the subscription code, which is the order or whenever it will.

Rxjs provide several operators to face this problem. You can check the combining operators here.

Community
  • 1
  • 1