Angular的subscribe无法读取未定义的属性,位于interval中。

huangapple go评论79阅读模式
英文:

Angular subscribe cannot read properties of undefined at interval

问题

我正在尝试每五秒自动更新mat-table。在首次加载站点时,PrijslijstComponentgetDrinks()方法可以正常工作并加载表格。

但是,在初始加载后的每五秒内从interval调用它时,它会出现以下错误:

Angular的subscribe无法读取未定义的属性,位于interval中。

关于这种类型错误有一些帖子,但似乎没有解决我的问题的内容。我尝试在getDrinksByGroup()方法内部做控制台日志记录,但似乎在抛出错误之前根本没有执行任何内容。

我想知道我做错了什么,因为第一个getDrinks()调用可以正常工作,但之后的调用都不行。另外,我不确定这是否是定期更新mat-table的正确方法?

prijslijst.component.ts


import { Component } from '@angular/core';
import { MatTableDataSource, MatTableDataSourcePaginator } from '@angular/material/table';
import { DrinksService } from '../drinks/drinks.service';
import { Drink } from '../drinks/drink';

@Component({
  selector: 'app-prijslijst',
  templateUrl: './prijslijst.component.html',
  styleUrls: ['./prijslijst.component.css']
})
export class PrijslijstComponent {
  private alcohols: Drink[] = [];
  private nonAlcohols: Drink[] = [];
  private interval: any;
  dataSourceAlcohol!: MatTableDataSource<Drink, MatTableDataSourcePaginator>;
  dataSourceNonAlcohol!: MatTableDataSource<Drink, MatTableDataSourcePaginator>;
  displayedColumns: string[] = ['name', 'startPrice', 'currentPrice', 'numberSold'];

  constructor(private drinksService: DrinksService) { 
  }

  ngOnInit(): void {
    this.getDrinks();
    this.interval = setInterval(() => this.getDrinks(), 5000);
  }

  ngOnDestroy() {
    if (this.interval) {
      clearInterval(this.interval);
    }
  }

  getDrinks() {
    console.log('获取饮品');
    this.drinksService
        .getDrinksByGroup()
        .subscribe(({ alcohols, notAlcohols }) => {
          if (alcohols !== undefined) {
            this.alcohols = alcohols;
          }
          if (notAlcohols !== undefined) {
            this.nonAlcohols = notAlcohols;
          }
          this.dataSourceAlcohol = new MatTableDataSource(this.alcohols);
          this.dataSourceNonAlcohol = new MatTableDataSource(this.nonAlcohols);
    });
  }

  addOrder(drink: Drink) {
    drink.numberInOrder = drink.numberInOrder + 1;
  }
}

drinks.service.ts


import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { map, Observable, of } from 'rxjs';
import { Drink } from './drink';

@Injectable({
  providedIn: 'root'
})
export class DrinksService {
  constructor(private http: HttpClient) { }

  private url: string = 'http://localhost:3000/drinks';
  drinks: Drink[] = [];

  setupDrinks(): void {
    this.getDrinks().subscribe(drinks => {
      drinks.forEach(drink => this.drinks.push(drink))
    });
  }

  getDrinks(): Observable<Drink[]> {
    console.log('从后端获取饮品');
    return this.http.get<Drink[]>(this.url);
  }

  getDrinksByGroup(): Observable<{ alcohols: Drink[], notAlcohols: Drink[] }> {
    return this.getDrinks().pipe(map(drinks => {
      const alcoholDrinks: Drink[] = [];
      const notAlcoholDrinks: Drink[] = [];
      
      drinks.forEach(drink => {
        if (drink.alcohol) {
          alcoholDrinks.push(drink);
        } else {
          notAlcoholDrinks.push(drink)
        }
      });
      
      return { alcohols: alcoholDrinks, notAlcohols: notAlcoholDrinks };
    }));
  }

  setSoldOut(drinkName: string) {
    this.drinks.filter((drink) => {
      if (drink.name === drinkName) {
        drink.soldOut = true;
      }
    })
  }

  setDrinks(drinks: Drink[]) {
    this.drinks = drinks;
  }
}

英文:

I'm trying to automatically update a mat-table every five seconds. Upon first loading the site, the getDrinks() method of the PrijslijstComponent works without issue and loads the table.

When calling it from the interval every five seconds after initial loading though, it gives the following error:

Angular的subscribe无法读取未定义的属性,位于interval中。

There were a few posts on this type of errors, but nothing which seemed to solve my problem. I tried to do a console log inside the getDrinksByGroup() method, but nothing at all gets executed there, it seems, before throwing the error.

I'm wondering where I'm going wrong as the first getDrinks() call works, but none after that. Also, I'm not sure if this is how I should update the mat-table regularly?

prijslijst.component.ts


import { Component } from &#39;@angular/core&#39;;
import {MatTableDataSource, MatTableDataSourcePaginator} from &#39;@angular/material/table&#39;;
import { DrinksService } from &#39;../drinks/drinks.service&#39;;
import { Drink } from &#39;../drinks/drink&#39;;

@Component({
  selector: &#39;app-prijslijst&#39;,
  templateUrl: &#39;./prijslijst.component.html&#39;,
  styleUrls: [&#39;./prijslijst.component.css&#39;]
})
export class PrijslijstComponent {
  private alcohols: Drink[] = [];
  private nonAlcohols: Drink[] = [];
  private interval: any;
  dataSourceAlcohol!: MatTableDataSource&lt;Drink, MatTableDataSourcePaginator&gt;;
  dataSourceNonAlcohol!: MatTableDataSource&lt;Drink, MatTableDataSourcePaginator&gt;;
  displayedColumns: string[] = [&#39;name&#39;, &#39;startPrice&#39;, &#39;currentPrice&#39;, &#39;numberSold&#39;];

  constructor(private drinksService: DrinksService) { 
  }

  ngOnInit(): void {
    this.getDrinks();
    this.interval = setInterval(this.getDrinks, 5000);
  }


  ngOnDestroy() {
    if (this.interval) {
      clearInterval(this.interval);
    }
 }

  getDrinks() {
    console.log(&#39;Getting drinks&#39;)
    this.drinksService
        .getDrinksByGroup()
        .subscribe(({alcohols, notAlcohols}) =&gt; {
          if(alcohols !== undefined) {
            this.alcohols = alcohols;
          }
          if(notAlcohols !== undefined) {
            this.nonAlcohols = notAlcohols;
          }
          this.dataSourceAlcohol = new MatTableDataSource(this.alcohols);
           this.dataSourceNonAlcohol = new MatTableDataSource(this.nonAlcohols);
    });
  }

  addOrder(drink: Drink) {
    drink.numberInOrder = drink.numberInOrder + 1;
  }
}


drinks.service.ts


import { HttpClient } from &#39;@angular/common/http&#39;;
import { Injectable } from &#39;@angular/core&#39;;
import { map, Observable, of } from &#39;rxjs&#39;;
import { Drink } from &#39;./drink&#39;;

@Injectable({
  providedIn: &#39;root&#39;
})
export class DrinksService {
  constructor(private http:HttpClient) { }

  private url: string = &#39;http://localhost:3000/drinks&#39;;
  drinks: Drink[] = [];

  setupDrinks(): void {
    this.getDrinks().subscribe(drinks =&gt; {
      drinks.forEach(drink =&gt; this.drinks.push(drink))
    });
  }

  getDrinks(): Observable&lt;Drink[]&gt; {
    console.log(&#39;Getting drinks from backend&#39;)
    return this.http.get&lt;Drink[]&gt;(this.url);
  }

  getDrinksByGroup(): Observable&lt;{alcohols: Drink[], notAlcohols: Drink[] }&gt; {
    return this.getDrinks().pipe(map(drinks =&gt; {
      const alcoholDrinks: Drink[] = [];
      const notAlcoholDrinks: Drink[] = [];
      
      drinks.forEach(drink =&gt; {
        if(drink.alcohol) {
          alcoholDrinks.push(drink);
        } else {
          notAlcoholDrinks.push(drink)
        }
      });
      

      return { alcohols: alcoholDrinks, notAlcohols: notAlcoholDrinks };
    }));
  }

  setSoldOut(drinkName: string) {
    this.drinks.filter((drink) =&gt; {
      if(drink.name === drinkName) {
        drink.soldOut = true;
      }
    })
  }

  setDrinks(drinks: Drink[]) {
    this.drinks = drinks;
  }

}


答案1

得分: 2

你的问题出在这一行:

this.interval = setInterval(this.getDrinks, 5000);

只需替换为:

this.interval = setInterval(() => this.getDrinks(), 5000);

这样就能正常工作。

英文:

Your issue is on this line:

this.interval = setInterval(this.getDrinks, 5000);

Just replace it by:

this.interval = setInterval(() =&gt; this.getDrinks(), 5000);

and it will work

答案2

得分: 1

问题出在this对象上。这就是你得到未定义值的原因。

基本上,当setInterval回调触发时,它将进入getDrinks函数,但该函数中的this对象并不是你所认为的那样。

你可以选择像这样做:

// 明确绑定`this`对象到你想要的作用域(组件)
this.interval = setInterval(this.getDrinks.bind(this), 5000);

或者:

// 箭头函数会自动将`this`对象绑定到其词法(包装范围)
this.interval = setInterval(() => this.getDrinks(), 5000);

或者以“正确”的方式来做,那就是使用响应式方法(rxjs 适用于此),例如:

destroy$ = new Subject();

ngOnInit() {
  // 每5秒触发一次计时器(第一个参数为0时,将立即触发第一次,无延迟)
  timer(0, 5000)
    .pipe(
      // 调用你的服务
      switchMap(_ => this.drinksService.getDrinksByGroup()),
      // 在ngDestroy时取消订阅
      takeUntil(this.destroy$))
    .subscribe(result => {
      // 处理结果
    });
}

ngOnDestroy() {
  this.destroy$.next();
  this.destroy$.complete();
}
英文:

The problem here is in this object. Thats why you got undefined value.

Basically when setInterval callback fires, it will go in getDrinks function but this object in that function is not what you think it is.

You can either do something like:

// Explicitly bind `this` object to the scope that you want (component)
this.interval = setInterval(this.getDrinks.bind(this), 5000);

or :

// Arrow function automatically bind `this` object to its lexical (wrapping scope)
this.interval = setInterval(() = this.getDrinks(), 5000);

Or do it in a "right" way, and that is to use reactive approach (rxjs is for that) such as:

  destroy$ = new Subject();
ngOnInit() {
// Trigger timer each 5seconds (with 0 as first argument it will trigger first time without delay)
timer(0, 5000)
.pipe(
// Call your service
switchMap(_ =&gt; this.drinksService.getDrinksByGroup()),
// Unsubscribe on ngDestroy
takeUntil(this.destroy$))
.subscribe((result) =&gt; {
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}

huangapple
  • 本文由 发表于 2023年5月28日 17:56:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76350895.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定