Angular подписка ведет себя не так, как ожидалось - массив пуст - PullRequest
0 голосов
/ 01 мая 2020

Я слежу за примерами Angular о героях и сконструировал (я думаю) мою версию кода идентично, но не получаю ожидаемого поведения.

Мой сервис

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 (используется для отладки)

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 (подписывается на наблюдаемую службу)

// 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)
  }

Полный вывод из консоли отладки

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.

Цель

Цель состоит в том, чтобы составить список интерфейсов коммутатора, которые я получаю от REST API (отдельно от Angular), и назначить их в список словарей, называемых портами. Это должно быть выполнено в строке:

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

Задача

В примере с героями должны быть заполнены примеры портов в функции getPorts. Как из Wireshark, так и из некоторых отладочных выходных данных я подтвердил, что HTTP-запрос get работает должным образом. В частности, вы можете увидеть строку:

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

, что он получает большой массив объектов (как и ожидалось). Однако по какой-либо причине назначение в ports => this.ports = ports, похоже, не работает. Значением портов всегда является пустой массив с нулевыми элементами. Однако я не смог понять, почему.

Ответы [ 3 ]

2 голосов
/ 01 мая 2020

Это простой случай попытки доступа к асинхронным данным до того, как им будет присвоено значение. В этом случае this.ports назначается асинхронно. Таким образом, к тому времени, когда вы делаете console.log(this.ports), ему не присваивается никакого значения. Но когда вы используете myObserver, это работает, потому что вы печатаете внутри подписки, как и должно быть. Точный эквивалент использования 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
  }
);

См. здесь , чтобы узнать больше об асинхронных запросах.

async труба против подписки в контроллере

async канал рекомендуется в большинстве случаев, потому что он заботится о том, чтобы отписаться от наблюдаемых, чтобы избежать проблем утечки памяти. При ручной подписке на наблюдаемое, лучше отписаться от него в хуке 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();
    }
  }
}

Обычно unsubscribe игнорируется при использовании HttpClient, потому что он обрабатывает отписку и избегает утечки памяти. Однако есть исключения. Например, если пользователь отходит от ссылки, которая сделала HTTP-вызов, она все еще может быть открыта. Поэтому всегда рекомендуется закрыть подписку вручную.

Существует также еще один элегантный способ обработки отказа от подписки с использованием 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();
  }
}
0 голосов
/ 01 мая 2020

что он получает большой массив объектов (как и ожидалось). Однако по какой-либо причине назначение в ports => this.ports = ports, похоже, не работает. Значением портов всегда является пустой массив с нулевыми элементами. Тем не менее, я не смог понять, почему.

Ну, вы съели свою наблюдаемую с subscribe(myObserver);

Вы можете использовать pipe и tap как @ user3791775 предложил

или расширить свой наблюдатель и присвоить ему значение.

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'),
};

- Редактировать

На самом деле есть другое решение

Вы можете создать 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;
  }
0 голосов
/ 01 мая 2020

Я не понимаю отладку вашего наблюдателя, но я думаю, что вы должны сделать это более примерно так (вы должны избегать ручной подписки и использовать асин c канал, но это не ваш вопрос):

Потеряйте наблюдателя отладки и внутри вашего метода 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()

надеюсь, это поможет отладке

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...