简体   繁体   English

Angular 订阅行为不符合预期 - 数组为空

[英]Angular subscribe not behaving as expected - array is empty

I'm following the Angular tour of heroes examples and have constructed (I think) my version of the code identically, but am not receiving the behavior I expect.我正在关注 Angular 英雄示例之旅,并以相同的方式构建(我认为)我的代码版本,但没有收到我期望的行为。

My service我的服务

import { Injectable } from '@angular/core';
import { PORTS } from './mock-ports'
import { Port } from './port'
import { Observable, of } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})

export class UpdateportsService {

  private controller_url = '/gelante/getports/150013889525632'
  private controller_ip = 'http://localhost:8080'

  getPorts(): Observable<Port[]> {
    return this.http.get<Port[]>(this.controller_ip + this.controller_url)
  }

  constructor(private http: HttpClient) { }
}

myObserver (used for debugging) myObserver(用于调试)

const myObserver = {
  next: x => console.log('Observer got a next value: ' + x),
  error: err => console.error('Observer got an error: ' + err),
  complete: () => console.log('Observer got a complete notification'),
};

getPorts (subscribes to the observable service) getPorts(订阅可观察服务)

// This is part of the same class as getPorts
ports: Port[] = [];

getPorts(): void {
    // To subscribe to an observable, you take the declared observable, which in
    // this case is getPorts (which returns an array of ports) and then you
    // subscribe to it. Anytime the observable is called it will emit values
    // which are then sent to all subscribers.
    console.log(this.ports)
    this.updateportsService.getPorts().subscribe(ports => this.ports = ports);

    // This prints all of my port data as expected
    this.updateportsService.getPorts().subscribe(myObserver);

    console.log(this.ports)
  }

Full output from Debug Console来自调试控制台的完整 output

Array(0) []
switch.component.ts:76
Array(0) []
switch.component.ts:82
Angular is running in the development mode. Call enableProdMode() to enable the production mode.
core.js:40917
Observer got a next value: [object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]
switch.component.ts:13
Observer got a complete notification
switch.component.ts:15
[WDS] Live Reloading enabled.

Goal目标

The goal is to take a listing of switch interfaces I'm receiving from a REST API (separate from Angular) and assign them to a list of dictionaries called ports.目标是列出我从 REST API(与 Angular 分开)接收的交换机接口列表,并将它们分配给称为端口的字典列表。 This should be accomplished in the line:这应该在以下行中完成:

this.updateportsService.getPorts().subscribe(ports => this.ports = ports);

Problem问题

In the tour of heroes example ports in the function getPorts should be populated.在英雄之旅示例端口中 function getPorts 应该被填充。 I have confirmed from both Wireshark and some debug output that the HTTP get request is functioning as expected.我已经从 Wireshark 和一些调试 output 确认 HTTP 获取请求按预期运行。 Specifically, you can see the line:具体来说,你可以看到这一行:

this.updateportsService.getPorts().subscribe(myObserver);

That it receives a big array of objects (as expected).它接收大量对象(如预期的那样)。 However, for whatever reason the assignment in ports => this.ports = ports does not seem to work.但是,无论出于何种原因, ports => this.ports = ports中的分配似乎都不起作用。 The value of ports is always an empty array with zero elements. ports 的值始终是一个零元素的空数组。 However, I haven't been able to figure out why.但是,我一直无法弄清楚为什么。

This is a simple case of trying to access asynchronous data before it is assigned a value.这是一个在为异步数据赋值之前尝试访问异步数据的简单案例。 In this case, this.ports is assigned asynchronously.在这种情况下, this.ports是异步分配的。 So by the time you do console.log(this.ports) , it isn't assigned any value.因此,当您执行console.log(this.ports)时,它没有被分配任何值。 But when you use myObserver it works because you are printing inside the subscription, as it's supposed to be.但是,当您使用myObserver时,它可以工作,因为您在订阅内进行打印,这是应该的。 The exact equivalent using ports would be the following使用ports的确切等价物如下

this.updateportsService.getPorts().subscribe(
  ports => { 
    this.ports = ports;
    console.log(this.ports);
  },
  error => {
     // it is always good practice to handle error when subscribing to HTTP observables
  }
);

See here to learn more about asynchronous requests.请参阅此处以了解有关异步请求的更多信息。

async pipe vs subscription in the controller async pipe 与 controller 中的订阅

async pipe is recommended in most cases because it takes care of unsubscribing from the observables so as to avoid memory leak issues.在大多数情况下建议使用async pipe,因为它负责取消订阅 observables 以避免 memory 泄漏问题。 When subscribing manually to an observable, it is better to unsubscribe from it in the OnDestroy hook.当手动订阅 observable 时,最好在OnDestroy钩子中取消订阅。

import { Subscription } from 'rxjs';

export class AppComponent implements OnInit, OnDestroy {
  obsSubscription: Subscription;

  ngOnInit() {
    this.obsSubscription = this.service.observable.subscribe(value => { // handle value });
  }

  ngOnDestroy() {
    if (this.obsSubscription) {
      this.obsSubscription.unsubscribe();
    }
  }
}

Usually the unsubscribe is overlooked when using the HttpClient because it handles the unsubscription and avoids memory leaks.通常在使用HttpClient时会忽略unsubscribe ,因为它会处理取消订阅并避免 memory 泄漏。 However there are exceptions.但是也有例外。 For eg., if the user navigates away from the link that made the HTTP call, it might still be open.例如,如果用户离开发出 HTTP 调用的链接,它可能仍处于打开状态。 So it is always recommended to close the subscription manually.因此,始终建议手动关闭订阅。

There is also an another elegant way of handling the unsubscription using takeUntil .还有另一种使用takeUntil处理取消订阅的优雅方式。

import { Subject, pipe } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

export class AppComponent implements OnInit, OnDestroy {
  closeActive = new Subject<void>();

  ngOnInit() {
    this.obsSubscription = this.service.observable
      .pipe(takeUntil(this.closeActive))
      .subscribe(value => { // handle value });
  }

  ngOnDestroy() {
    this.closeActive.next();
    this.closeActive.complete();
  }
}

I don't understand your observer debug, but I think you should do it more like this (you should try to avoid manual subscription and use the async pipe but that's not your question):我不明白你的观察者调试,但我认为你应该这样做(你应该尽量避免手动订阅并使用异步 pipe 但这不是你的问题):

Lose the debug observer and inside your getPorts method do this:失去调试观察者并在您的 getPorts 方法中执行以下操作:

this.updateportsService.getPorts().pipe(
 tap(ports => console.log(ports),
 tap(ports => this.ports = ports),
 catchError(e => console.log(e)) // I suspect you'll catch some kind of error
).subscribe()

hope this helps to debug希望这有助于调试

That it receives a big array of objects (as expected).它接收大量对象(如预期的那样)。 However, for whatever reason the assignment in ports => this.ports = ports does not seem to work.但是,无论出于何种原因,ports => this.ports = ports 中的分配似乎都不起作用。 The value of ports is always an empty array with zero elements. ports 的值始终是一个零元素的空数组。 However, I haven't been able to figure out why.但是,我一直无法弄清楚为什么。

Well you consumed your observable with subscribe(myObserver);好吧,您使用subscribe(myObserver);

You can either use pipe and tap as @user3791775 suggested您可以使用pipe并按照@user3791775 的建议tap

or extend your observer and assign value there.或扩展您的观察者并在那里分配价值。

const myObserver = {
  next: ports => {
    this.ports = ports;
    console.log('Observer got a next value: ' + ports)
  },
  error: err => console.error('Observer got an error: ' + err),
  complete: () => console.log('Observer got a complete notification'),
};

--Edit - 编辑

Actually there is another solution其实还有另一种解决方案

You can create Subject which allows you to handle multiple subscriptions.您可以创建允许您处理多个订阅的Subject

getPorts(): Subject<Port[]> {
    const subject = new Subject<Port[]>();
    return this.http.get<Port[]>(this.controller_ip + this.controller_url).subscribe(ports => subject.next(ports));
    return subject;
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM