Reputation: 1753
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.
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) { }
}
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'),
};
// 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.
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. This should be accomplished in the line:
this.updateportsService.getPorts().subscribe(ports => this.ports = ports);
In the tour of heroes example ports in the function getPorts should be populated. I have confirmed from both Wireshark and some debug output that the HTTP get request is functioning as expected. 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. The value of ports is always an empty array with zero elements. However, I haven't been able to figure out why.
Upvotes: 0
Views: 2390
Reputation: 31105
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. So by the time you do console.log(this.ports)
, it isn't assigned any value. But when you use myObserver
it works because you are printing inside the subscription, as it's supposed to be. The exact equivalent using ports
would be the following
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 is recommended in most cases because it takes care of unsubscribing from the observables so as to avoid memory leak issues. When subscribing manually to an observable, it is better to unsubscribe from it in the OnDestroy
hook.
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. However there are exceptions. For eg., if the user navigates away from the link that made the HTTP call, it might still be open. So it is always recommended to close the subscription manually.
There is also an another elegant way of handling the unsubscription using 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();
}
}
Upvotes: 5
Reputation: 11283
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. The value of ports is always an empty array with zero elements. However, I haven't been able to figure out why.
Well you consumed your observable with subscribe(myObserver);
You can either use pipe
and tap
as @user3791775 suggested
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.
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;
}
Upvotes: 0
Reputation: 471
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):
Lose the debug observer and inside your getPorts method do this:
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
Upvotes: 0