I'm a newbie to Angular-7, RxJS-6 and Visual Studio Code and I'm having difficulty debugging an Observable that is being returned to a subscriber with the result that I have a runtime "TypeError" being raised by the subscriber. From research, it would appear that I'm not alone with tricky problems like this. Can you either suggest how I can determine what the subscriber is "observing" or can you spot the error in my code below?
In Detail
I'm writing a very simple proof of concept using Visual Studio Code and the Angular-7 CLI to retrieve the current system date/time from a server using angular's httpclient
and display it.
Refer to the method instrument.service.ts::getSystemTimeDate() below. The HTTP layer is fine in that the JSON response is obtained ...
{
"SystemDateTime": "2018-11-26T08:54:06.894Z"
}
Within the map
operator, this response is first converted to an object of type SystemDateTimeResponse
and then to a Date
and the method should return an Observable<Date>
to any subscribers. What I am having trouble with is the component's subscription to Observable<Date>
. At runtime, this subscriber in the method onTimeDateBtnClick()
throws an error:
ERROR
TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
message: "You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable."
stack: "TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
at subscribeTo (http://localhost:4200/vendor.js:75870:15)
at subscribeToResult (http://localhost:4200/vendor.js:76027:76)
at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber._innerSub (http://localhost:4200/vendor.js:70784:90)
at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber._tryNext (http://localhost:4200/vendor.js:70778:14)
at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber._next (http://localhost:4200/vendor.js:70761:18)
at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (http://localhost:4200/vendor.js:65218:18)
at TapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/tap.js.TapSubscriber._next (http://localhost:4200/vendor.js:73228:26)
at TapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (http://localhost:4200/vendor.js:65218:18)
at TakeSubscriber.push../node_modules/rxjs/_esm5/internal/operators/take.js.TakeSubscriber._next (http://localhost:4200/vendor.js:72950:30)
at TakeSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (http://localhost:4200/vendor.js:65218:18)"
__proto__: Error {constructor: , name: "TypeError", message: "", …}
constructor: function TypeError() { … }
message: ""
name: "TypeError"
toString: function toString() { … }
__proto__: Object {constructor: , name: "Error", message: "", …}
I believe that I am not properly returning an Observable and may be screwing up my use of the map operator. What am I missing?
The Code
The software references of this snippet include:
timedate.component.html: contains the simple template of
<p>
Last time I checked, it was : {{today | date:'medium'}}
</p>
<button mat-button (click)="onTimedateBtnClick()">Update</button>
timedate.component.ts: contains the display property definition of today
and the event handler onTimedateBtnClick()
which uses a data service to manage the HTTP request/response, retrieving the current date/time from a server.
import { Component, OnInit } from '@angular/core';
import { InstrumentService } from '../instrument.service';
import { Observable } from 'rxjs';
@Component({
selector: 'app-timedate',
templateUrl: './timedate.component.html',
styleUrls: ['./timedate.component.css']
})
export class TimedateComponent implements OnInit {
/** Display property */
today: Date;
/**
* Constructor
* @param - data service
*/
constructor(private dataService: InstrumentService) {
}
ngOnInit() {
this.today = new Date(); /// initialise with client's date/time
}
/**
* User event handler requesting system time/date from the server
*/
onTimedateBtnClick() {
const http$: Observable<Date> = this.dataService.getSystemTimeDate();
http$.subscribe(
res => this.today = res,
);
}
}
instrument.service.ts: contains the getSystemTimeDate()
method which returns an Observable<Date>
. Again, I've simplified the code (although it still fails) and exaggerated the map in order to better see what I'm doing.
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
// App imports
import { SystemDateTimeResponse, SystemDateTimeUrl } from './instrument.service.httpdtos';
@Injectable({
providedIn: 'root'
})
export class InstrumentService {
constructor(private http: HttpClient) { }
/**
* Return the server date and time
*/
public getSystemTimeDate(): Observable<Date> {
// Convert the server response object into an observable date
const responseObject: Observable<Date> =
this.http.get<SystemDateTimeResponse>(SystemDateTimeUrl.Url).
pipe(
map(jsonResponse => {
const newDto = new SystemDateTimeResponse(jsonResponse.SystemDateTime);
const d = new Date(newDto.SystemDateTime);
return d;
}),
);
return responseObject;
}
}
instrument.service.httpdtos.ts : Contains data transfer object definitions.
/** URL - Instrument system date/time */
export class SystemDateTimeUrl {
public static readonly HttpVerb = 'GET';
public static readonly Url = 'api/instrument/systemdatetime';
public static readonly Summary = 'Query the instrument current date/time';
}
/** Response DTO */
export class SystemDateTimeResponse {
constructor(
public SystemDateTime: string // In UTC format
) { }
}
You have two options. If you are using Chrome for developing this application, you could go in developer tools, find your source code in webpack, and within the service you could add multiple breakpoints for debugging purposes. The tricky part is to find the sources. From there it should be strait forward.
The second option, if you use Intellij IDEA / WebStorm, you could debug the app in your editor. To do that, you must install JetBrains IDE Support extension in Chrome / Firefox and after that you must configure your editor to add a new Configuration : Editor Configuration -> Javascript Debugger. You must specify the correct port (if your app runs on a different port)
Once you've started both the app and the debugging configuration, add a new breakpoint within your code (callback on your subscription / map function) and you should be able to inspect what variables you have.
If you have any other questions, don't hesitate to ask.