Ascena
Ascena

Reputation: 60

Angular/Typescript - Solve Async in Array

We have 2 arrays Speisekarte and Essensplan - I want to call the method printName, to print out the name, I get from the ID.

The problem is, "name" is always undefined.

"ERROR TypeError: Cannot read property 'name' of undefined"

How can i fix async in methods in templates?

Template (Async in printName(essenName) )

<div *ngFor="let i of essensplan"> <br />
  <div><b>Woche : {{i.id}}</b></div> <br />
  <button *ngFor="let id of i.essenProWoche" (click)="print(id)">Gericht 
** {{printName(id)}} ** </button> 

Component

ngOnInit() {
  console.log("ngOnInite essensplan.component")
  this.getSpeisekarte();
  this.getEssensplan();

}

printName(id: number) {
  this.essenName = this.speisekarte.find(i => i.id == id ).name

 getSpeisekarte(): void {
  this.essenService.getSpeisekarte()
  .subscribe(speisekarte => this.speisekarte = speisekarte);
}

getEssensplan(): void {
  this.essensplanService.getEssensplan()
    .subscribe(essensplan => this.essensplan = essensplan)

}

Upvotes: 0

Views: 59

Answers (3)

Felix Lemke
Felix Lemke

Reputation: 6488

Use Observables for your problem. They will significantly reduce and improve your code. Angular has a buildin pipe called async-pipe which subscribes as long as the component is alive.

export class MyEssenComponent {
  essensPlan$: Observable<Essensplan>;
  speisekarte$: Observable<Speisekarte>;

  ngOnInit(): void {
    this.essensPlan$ = this.essensplanService.getEssensplan();
    this.speisekarte$ = this.essenService.getSpeisekarte();
  }

  get name(id: string): Observable<string> {
    return this.speisekarte$.pipe(
      filter(speisekarte => speisekarte.id === id),
      map(speisekarten => speisekarten[0] ? speisekarten[0].name : '')
    );
  }
}

Then you can easily rewrite your template to

<div *ngFor="let i of essensplan$ | async"> <br />
  <div><b>Woche : {{i.id}}</b></div> <br />
  <button *ngFor="let id of i.essenProWoche" (click)="print(id)">
    Gericht ** {{name(id) | async}} **
  </button>
</div>

Also I recommend to choose more meaningful variable names as the are totally irritating. E.g. you chose essenProWoche attribute for an array of IDs, or just i as variable.

Upvotes: 0

Fernix
Fernix

Reputation: 371

You can use AsyncPipe "The async pipe subscribes to an Observable or Promise and returns the latest value it has emitted. When a new value is emitted, the async pipe marks the component to be checked for changes. When the component gets destroyed, the async pipe unsubscribes automatically to avoid potential memory leaks."

<div *ngFor="let i of essensplan | async "> <br />

or use NgIF operator to show safely the view.

Please check the Answer for the same problem.

Upvotes: 0

Vlad274
Vlad274

Reputation: 6844

I'm not sure if it is possible to delay the entire component until both HTTP calls have completed. Instead of that, you can modify printName to handle the case where this.speisekarte hasn't been loaded yet. Then, tell the component to reload once it has.

Component

constructor(private changeDetector: ChangeDetectorRef) {}

ngOnInit() {
  console.log("ngOnInite essensplan.component")
  this.getSpeisekarte();
  this.getEssensplan();
}

printName(id: number) {
  const match = this.speisekarte.find(i => i.id == id );
  return match ? match.name : "";
}

getSpeisekarte(): void {
  this.essenService.getSpeisekarte()
  .subscribe(speisekarte => {
    this.speisekarte = speisekarte;
    this.changeDetector.markForCheck();
  });
}

getEssensplan(): void {
  this.essensplanService.getEssensplan()
  .subscribe(essensplan => { 
    this.essensplan = essensplan;
    this.changeDetector.markForCheck();
  });
}

Now, regardless of the order the HTTP requests complete, the templates will not error (although the names will be blank if getEssensplan finishes before getSpeisekarte


Note: I changed printName to return a value, since that will be necessary to display it

Upvotes: 1

Related Questions