How to test effects in ngrx? - redux

I just want to get my getUser effect below.
I'm using angular5, typescript and ngrx.
I'm open to alternative examples to what I have below.
This is my effect:
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/catch';
import { Effect, Actions } from '#ngrx/effects';
import { HttpErrorResponse } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { map, switchMap } from 'rxjs/operators';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import * as fromUser from '../actions/user-state.actions';
import { UserService } from '../services/user-state.service';
import { UserStateState } from '../models/user-state.interfaces';
#Injectable()
export class UserStateEffects {
#Effect({ dispatch: true })
getUser$: Observable<any> = this.actions$
.ofType(fromUser.GET_USER)
.pipe(
switchMap(() => this.userService.getUser()),
map((result: any) => new fromUser.UserSuccess(result)))
.catch((error: HttpErrorResponse) => of(new fromUser.UserFailure(error)));
constructor(
private actions$: Actions,
private userService: UserService
) {}
}
This is my spec for the effect test:
import 'rxjs/add/observable/throw';
import { Actions } from '#ngrx/effects';
import { cold, hot } from 'jasmine-marbles';
import { empty } from 'rxjs/observable/empty';
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { TestBed } from '#angular/core/testing';
import * as actions from '../actions/user-state.actions';
import { UserService } from '#tradingapplication/user-state/src/state/services/user-state.service';
import { UserStateEffects } from '#tradingapplication/user-state/src/state/effects/user-state.effects';
import { UserStateState } from '../models/user-state.interfaces';
import { UserStateConstants } from '../../user-state.constants';
export class TestActions extends Actions {
constructor(){
super(empty());
}
set stream(source: Observable<any>){
this.source = source
}
}
export function getActions() {
return new TestActions();
}
fdescribe('UserStateEffects', function () {
let actions$: TestActions;
let service: UserService;
let effects: UserStateEffects;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ],
providers: [
UserService,
UserStateConstants,
UserStateEffects,
{ provide: Actions, useFactory: getActions },
],
});
actions$ = TestBed.get(Actions);
service = TestBed.get(UserService);
effects = TestBed.get(UserStateEffects);
spyOn(service, 'getUser').and.returnValue(of(null));
});
it('should return user from GetUser', () => {
const action = actions.GET_USER;
const completion = new actions.UserSuccess({});
actions$.stream = hot('-a', { a: action });
const expected = cold('-b', { b: completion });
expect(effects.getUser$).toBeObservable(expected);
});
});

You might want to check out the NGRX example app from the NGRX/Google team. It utilizes Jasmine-Marbles for testing streams, as used in NGRX Effects. This way you can test the streams for being closed or not after execution and how many elements passed through them before the expected value arrived. And all of that with just a few dashes (ticks).

Related

The dom is not reflective of the actual value wen using onPush strategy with ngrx store subscription

component file:
// Angular and 3rd party libs imports
import { ChangeDetectionStrategy, Component, OnInit } from '#angular/core';
import { Store } from '#ngrx/store';
import { UntilDestroy, untilDestroyed } from '#ngneat/until-destroy';
// Utils
import { ApiLoadInfo, ApiStateEnum } from 'src/app/shared/utils/states';
// Services
import { TestPortalService } from '../../../testportal.service';
import { SharedClient } from 'src/app/shared/services/shared.service';
// Redux
import {
CandidateInstructionsState,
Quiz,
Instruction,
PageEnum,
LandingPageData
} from '../redux/candidate-instructions.state';
import * as instructionActions from '../redux/candidate-instructions.action';
import * as instructionSelects from '../redux/candidate-instructions.selector';
import { ActivatedRoute } from '#angular/router';
#UntilDestroy()
#Component({
selector: 'candidate-instructions-landing',
templateUrl: './instructions-landing.component.html',
styleUrls: ['./instructions-landing.component.scss', '../common.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CandidateInstructionsLandingComponent implements OnInit {
// Exposing constants to html template
ApiStateEnum = ApiStateEnum;
PageEnum = PageEnum;
// Variables
initDataLoadState: ApiLoadInfo;
data: LandingPageData;
constructor(private _store: Store<CandidateInstructionsState>,
private _activatedRoute: ActivatedRoute,
private _testPortalService: TestPortalService,
) {
_store
.select(instructionSelects.selectInitDataLoadState)
.pipe(untilDestroyed(this))
.subscribe((initDataLoadState) => {
console.log('is same ref?:', this.initDataLoadState === initDataLoadState)
this.initDataLoadState = initDataLoadState;
console.log(initDataLoadState)
console.log('----------')
});
_store
.select(instructionSelects.selectLandingData)
.pipe(untilDestroyed(this))
.subscribe((data) => {
this.data = data;
});
}
ngOnInit() {
this.loadInstructions();
}
loadInstructions() {
this._store.dispatch(instructionActions.setInitData()); // sets state to 'loading'
this._testPortalService.getTestInstructions(
this._activatedRoute.snapshot.params.quizOrInviteId,
(error, response) => {
if (error) {
// sets state to 'error'
this._store.dispatch(instructionActions.setInitDataFail({ errmsg: error.toString() }));
} else {
// sets state to 'loaded'
this._store.dispatch(instructionActions.setInitDataSuccess({ instructions: response }));
console.log(response);
}
}
);
}
}
html:
{{ initDataLoadState.state }}
console output:
ui:
I thought when onPush is set, the template will re-render if the variable ref is changed. And since redux store is immutable that is always supposed to happen (confirmed by logging in the console). But still the actual component data is not in sync with the UI ie. component value = "loaded" but value in ui = "loading". Why is it so?
If you don't want to or can't use the pushPipe you could do something like this to subscribe to the store data:
import { Component, OnDestroy, OnInit } from '#angular/core';
import { Subscription } from 'rxjs';
import { Store } from '#ngrx/store';
import { getData } from 'path/to/store';
import { YourType } from 'path/to/type';
#Component({
selector: 'subscribing-component',
templateUrl: './subscribing.component.html'
})
export class SubscribingComponent implements OnInit, OnDestroy {
data: YourType;
dataSubscription: Subscription;
constructor(store: Store) {}
ngOnInit(): void {
this.dataSubscription = this.store.select(getData).subscribe((data) => {
this.data = data;
});
}
// don't forget to unsubscribe
ngOnDestroy(): void {
if (this.dataSubscription) {
this.dataSubscription.unsubscribe();
}
}
}

Angular-Redux Epics

Getting the following error message in console when using the angular-redux library. Also, Redux won't catch or listen for actions after the error occurs. I've searched, including the documentation but nothing points out to fix the error.
Am I missing something?
Error
core.js:1427 ERROR Error: Actions must be plain objects. Use custom middleware for async actions.
at Object.performAction (<anonymous>:3:2312)
at liftAction (<anonymous>:2:27846)
at dispatch (<anonymous>:2:31884)
at eval (createEpicMiddleware.js:67)
at SafeSubscriber.dispatch [as _next] (applyMiddleware.js:35)
at SafeSubscriber.__tryOrUnsub (Subscriber.js:240)
at SafeSubscriber.next (Subscriber.js:187)
at Subscriber._next (Subscriber.js:128)
at Subscriber.next (Subscriber.js:92)
at SwitchMapSubscriber.notifyNext (switchMap.js:127)
Here's code
Component
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { select } from '#angular-redux/store';
import { ScheduleActions } from '../store/actions'
#Component({
selector: 'app-page2',
templateUrl: './page2.component.html',
styleUrls: ['./page2.component.css']
})
export class Page2Component implements OnInit {
#select(['schedule', 'scheduleList']) values: any;
constructor(public actions: ScheduleActions) { }
ngOnInit() {
this.actions.loadSchedule();
}
}
Actions
//schedule-actions.ts
import { Injectable } from '#angular/core';
import { NgRedux } from '#angular-redux/store';
import { Schedule } from '../../model/schedule.model';
#Injectable()
export class ScheduleActions {
static readonly LOAD_SCHEDULE = 'LOAD_SCHEDULE';
static readonly LOAD_SCHEDULE_SUCCESS = 'LOAD_SCHEDULE_SUCCESS';
constructor(private ngRedux: NgRedux<any>){}
loadSchedule(){
this.ngRedux.dispatch({
type: ScheduleActions.LOAD_SCHEDULE
});
}
}
Reducer
//schedule-reducer.ts
import { ScheduleActions } from '../actions';
export interface SCHEDULE_STATE {
scheduleList: any,
scheduleDetail: any
}
const initialState: SCHEDULE_STATE = {
scheduleList: [],
scheduleDetail: {}
}
export const ScheduleReducer = (state: SCHEDULE_STATE = initialState, action): SCHEDULE_STATE => {
switch(action.type){
case ScheduleActions.LOAD_SCHEDULE_SUCCESS:
return {...state, scheduleList: action.payload };
case ScheduleActions.LOAD_SCHEDULE_DETAIL_SUCCESS:
return {...state, scheduleList: action.payload };
case ScheduleActions.CREATE_SCHEDULE_SUCCESS:
return {...state, scheduleDetail: action.payload };
default:
return state;
}
}
Epics
//schedule-epic.ts
import { Injectable } from '#angular/core';
import { ActionsObservable, ofType } from 'redux-observable';
import { ScheduleService } from '../services';
import { ScheduleActions } from '../actions';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class ScheduleEpic {
constructor(private service: ScheduleService,
private actions: ScheduleActions
){}
loadScheduleEpic = (action$: ActionsObservable<any>) => {
return action$.ofType(ScheduleActions.LOAD_SCHEDULE)
.mergeMap(action => {
return this.service.loadSchedule().map(result => {
this.actions.loadScheduleSuccess(result)
})
})
}
}
Service
//schedule-service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Schedule } from '../../model/schedule.model';
#Injectable()
export class ScheduleService {
private API_URL: String = "http://mockserver.io/v2";
constructor(private http: HttpClient){}
loadSchedule(){
return this.http.get(this.API_URL + '/5a6225153100004f2bde7f27').map(res => res)
}
}
That error means you dispatched something that was not an action--in this case, your epic emitted something that wasn't an action.
Thankfully, it's an easy fix! You're just missing a return statement in your map
return this.service.loadSchedule().map(result => {
this.actions.loadScheduleSuccess(result)
})
// change it to this:
return this.service.loadSchedule().map(result => {
return this.actions.loadScheduleSuccess(result)
})

Unable to broadcast messages from http extender class to App Component in Angular 2

project Structure
Error Information
This is the error i am getting , when i broadcast the message from http extender service to the app component.
Loading Interceptor(http extender)
this is my http extender ,i am unable to broadcast the messages to App component from here ,but i am able to broadcast the messages from the child components to App component ,please see the image for the error information and project structure
import { Injectable } from '#angular/core';
import { Http, RequestOptions, RequestOptionsArgs, Response, ConnectionBackend } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { EventsEmitter } from './eventsEmitter';
#Injectable()
export class LoadingInterceptor extends Http {
private currentRequests: number = 0;
public constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions, private eventsEmitter: EventsEmitter) {
super(_backend, _defaultOptions);
}
public get(url: string, options?: RequestOptionsArgs): Observable<Response> {
this.incrementRequestCount();
var response = super.get(url, options);
response.subscribe(null, error => {
this.decrementRequestCount();
}, () => {
this.decrementRequestCount();
});
return response;
}
private decrementRequestCount() {
if (--this.currentRequests == 0) {
this.eventsEmitter.broadcast('loading-complete');
}
}
private incrementRequestCount() {
if (this.currentRequests++ == 0) {
this.eventsEmitter.broadcast('loading-started');
}
}
}
App Component
I am listening the events broadcasted in the app component to show the loader gif on the screen
import { Component } from '#angular/core';
import { EventsEmitter } from './assets/scripts/services/eventsEmitter';
import { ToasterService } from 'angular2-toaster';
#Component({
selector: 'my-app',
templateUrl:'app/app.component.html'
})
export class AppComponent {
private toasterService: ToasterService;
private message: any;
private active: any;
constructor(toasterService: ToasterService, private eventsEmitter: EventsEmitter) {
this.toasterService = toasterService;
this.eventListners();
}
eventListners() {
this.eventsEmitter.on<string>('loading-complete')
.subscribe(message => {
this.active = false;
});
this.eventsEmitter.on<string>('loading-started')
.subscribe(message => {
this.active = true;
});
}
}
Event Emitter
this is the event emittter i am using to broadcast the events
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
interface EventsEmitterInterface {
key: any;
data?: any;
}
export class EventsEmitter {
private _eventBus: Subject<EventsEmitterInterface>;
constructor() {
this._eventBus = new Subject<EventsEmitterInterface>();
}
broadcast(key: any, data?: any) {
this._eventBus.next({ key, data });
}
on<T>(key: any): Observable<T> {
return this._eventBus.asObservable()
.filter(event => event.key === key)
.map(event => <T>event.data);
}
}
App Module
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { LocationStrategy, HashLocationStrategy } from '#angular/common';
import { HttpModule, JsonpModule, Http, RequestOptions, XHRBackend, RequestOptionsArgs, Response, ConnectionBackend} from '#angular/http';
import { AppRoutingModule } from './app.routes';
import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';
import { LoadingInterceptor } from './assets/scripts/services/loadingInterceptor';
import { EventsEmitter } from './assets/scripts/services/eventsEmitter';
import { ToasterModule, ToasterService } from 'angular2-toaster';
#NgModule({
imports: [AppRoutingModule, BrowserModule, FormsModule, ReactiveFormsModule, HttpModule, JsonpModule, ToasterModule ],
declarations: [AppComponent, LoginComponent],
bootstrap: [AppComponent],
providers: [EventsEmitter,LoadingInterceptor,
{
provide: Http,
useFactory: (xhrBackend: XHRBackend, requestOptions: RequestOptions, eventsEmitter: EventsEmitter) => new LoadingInterceptor(xhrBackend, requestOptions, eventsEmitter),
deps: [XHRBackend, RequestOptions]
},{ provide: LocationStrategy, useClass: HashLocationStrategy }]
})
export class AppModule { }
I am stuck here for many days, it would be really helpful if you could help me resolve this issue
You forgot to add EventsEmitter dependency within your useFactory provider:
deps: [XHRBackend, RequestOptions]
It shoul be:
deps: [XHRBackend, RequestOptions, EventsEmitter]
That's why your LoadingInterceptor constructor gets undefined for EventsEmitter dependency

Angular 2 cannot read property 'get' of undefined

I have seen this error on SO quite a few times, all I can find on it is that I need to make sure that I have my service Provided in my app.modules, and then call it in my constructor of my component. I have done this and am still getting the error. Also I have both http and HTTPMODULES in my application. The error only occurs when I use the delete functionality in my application. Here is the error error_handler.js:45 EXCEPTION: Cannot read property 'get' of undefined, here is some relevant code....
app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { HttpModule, JsonpModule } from '#angular/http'; <------------HTTP
import { AppComponent } from './app.component';
import { PostRantComponent } from './postRant.component';
import { PostDataService } from './PostData.Service'; <------------service
import { Constants } from './app.const.service';
import { Routing } from './app.routes';
import { FormsModule } from '#angular/forms';
#NgModule({
imports: [NgbModule.forRoot(), BrowserModule, Routing, FormsModule, HttpModule, JsonpModule],
declarations: [AppComponent,,PostRantComponent],
providers: [PostDataService, Constants],
bootstrap: [AppComponent]
})
export class AppModule { }
Service (tried cutting it down to just show relevant code)
import { Injectable } from '#angular/core';
import { Http, Response, Headers } from '#angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { Observable } from 'rxjs/Observable';
import { PostViewModel } from './models/Post';
import { Constants } from './app.const.service';
#Injectable()
export class PostDataService{
private actionUrl: string;
private headers: Headers;
constructor( private _http: Http, private _constants: Constants ){
this.actionUrl = _constants.ServerWithApi;
this.headers = new Headers();
this.headers.append('Content-Type', 'application/json');
this.headers.append('Accept','application/json');
}
public GetAll = (): Observable<PostViewModel[]> => {
return this._http.get(this.actionUrl)
.map((response: Response) => <PostViewModel[]>response.json())
.catch(this.handleError);
}
public Delete = (id: string) =>{
return this._http.delete(this.actionUrl + id)
.map(res => res.json())
.catch(this.handleError);
}
}
Component
import { Component, Attribute, OnInit,ViewChild } from '#angular/core';
import { PostViewModel } from './models/Post';
import { PostDataService } from './PostData.Service';
import { Constants } from './app.const.service';
#Component({
selector: 'postRant',
templateUrl: 'html/postRant.html',
})
export class PostRantComponent implements OnInit {
txtTitle: string;
txtDescription: string;
public myPosts : Array<PostViewModel>;
public newPost : PostViewModel = new PostViewModel();
constructor(private auth:Auth, private _dataservice: PostDataService){
}
ngOnInit(){
this.getAllItems();
}
private getAllItems():void {
this._dataservice
.GetAll()
.subscribe((Post: Array<PostViewModel>) => this.myPosts = Post,
error => console.log(error),
() => console.log('get all items complete'))
}
delete(id){
console.log(id);
this._dataservice.Delete(id)
.subscribe((res) => {
this.myPosts = res;
});
var index = this.myPosts.findIndex(x => x.id == id);
this.myPosts.splice(index, 1);
}
}
If you are interested in all the code I have it posted on my git located here, however it is rather large.
EDIT
picture of error....
it appears that the error is produced by line 52 of PostData.Service.ts
i.e. var applicationError = error.headers.get('Application-Error');
this makes me guess that your GetAll Http call is erroring out, but the server you are asking for data is not returning data in the format of error.headers
Add a debugger; to the handleError and check the object that it is receiving.

How to catch exception correctly from http.request()?

Part of my code:
import {Injectable} from 'angular2/core';
import {Http, Headers, Request, Response} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class myClass {
constructor(protected http: Http) {}
public myMethod() {
let request = new Request({
method: "GET",
url: "http://my_url"
});
return this.http.request(request)
.map(res => res.json())
.catch(this.handleError); // Trouble line.
// Without this line code works perfectly.
}
public handleError(error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
myMethod() produces exception in console of browser:
ORIGINAL EXCEPTION: TypeError: this.http.request(...).map(...).catch is not a function
Perhaps you can try adding this in your imports:
import 'rxjs/add/operator/catch';
You can also do:
return this.http.request(request)
.map(res => res.json())
.subscribe(
data => console.log(data),
err => console.log(err),
() => console.log('yay')
);
Per comments:
EXCEPTION: TypeError: Observable_1.Observable.throw is not a function
Similarly, for that, you can use:
import 'rxjs/add/observable/throw';
New service updated to use the HttpClientModule and RxJS v5.5.x:
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import { catchError, tap } from 'rxjs/operators';
import { SomeClassOrInterface} from './interfaces';
import 'rxjs/add/observable/throw';
#Injectable()
export class MyService {
url = 'http://my_url';
constructor(private _http:HttpClient) {}
private handleError(operation: String) {
return (err: any) => {
let errMsg = `error in ${operation}() retrieving ${this.url}`;
console.log(`${errMsg}:`, err)
if(err instanceof HttpErrorResponse) {
// you could extract more info about the error if you want, e.g.:
console.log(`status: ${err.status}, ${err.statusText}`);
// errMsg = ...
}
return Observable.throw(errMsg);
}
}
// public API
public getData() : Observable<SomeClassOrInterface> {
// HttpClient.get() returns the body of the response as an untyped JSON object.
// We specify the type as SomeClassOrInterfaceto get a typed result.
return this._http.get<SomeClassOrInterface>(this.url)
.pipe(
tap(data => console.log('server data:', data)),
catchError(this.handleError('getData'))
);
}
Old service, which uses the deprecated HttpModule:
import {Injectable} from 'angular2/core';
import {Http, Response, Request} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
//import 'rxjs/Rx'; // use this line if you want to be lazy, otherwise:
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do'; // debug
import 'rxjs/add/operator/catch';
#Injectable()
export class MyService {
constructor(private _http:Http) {}
private _serverError(err: any) {
console.log('sever error:', err); // debug
if(err instanceof Response) {
return Observable.throw(err.json().error || 'backend server error');
// if you're using lite-server, use the following line
// instead of the line above:
//return Observable.throw(err.text() || 'backend server error');
}
return Observable.throw(err || 'backend server error');
}
private _request = new Request({
method: "GET",
// change url to "./data/data.junk" to generate an error
url: "./data/data.json"
});
// public API
public getData() {
return this._http.request(this._request)
// modify file data.json to contain invalid JSON to have .json() raise an error
.map(res => res.json()) // could raise an error if invalid JSON
.do(data => console.log('server data:', data)) // debug
.catch(this._serverError);
}
}
I use .do() (now .tap()) for debugging.
When there is a server error, the body of the Response object I get from the server I'm using (lite-server) contains just text, hence the reason I use err.text() above rather than err.json().error. You may need to adjust that line for your server.
If res.json() raises an error because it could not parse the JSON data, _serverError will not get a Response object, hence the reason for the instanceof check.
In this plunker, change url to ./data/data.junk to generate an error.
Users of either service should have code that can handle the error:
#Component({
selector: 'my-app',
template: '<div>{{data}}</div>
<div>{{errorMsg}}</div>`
})
export class AppComponent {
errorMsg: string;
constructor(private _myService: MyService ) {}
ngOnInit() {
this._myService.getData()
.subscribe(
data => this.data = data,
err => this.errorMsg = <any>err
);
}
}
There are several ways to do this. Both are very simple. Each of the examples works great. You can copy it into your project and test it.
The first method is preferable, the second is a bit outdated, but so far it works too.
1) Solution 1
// File - app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { HttpClientModule } from '#angular/common/http';
import { AppComponent } from './app.component';
import { ProductService } from './product.service';
import { ProductModule } from './product.module';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [ProductService, ProductModule],
bootstrap: [AppComponent]
})
export class AppModule { }
// File - product.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
// Importing rxjs
import 'rxjs/Rx';
import { Observable } from 'rxjs/Rx';
import { catchError, tap } from 'rxjs/operators'; // Important! Be sure to connect operators
// There may be your any object. For example, we will have a product object
import { ProductModule } from './product.module';
#Injectable()
export class ProductService{
// Initialize the properties.
constructor(private http: HttpClient, private product: ProductModule){}
// If there are no errors, then the object will be returned with the product data.
// And if there are errors, we will get into catchError and catch them.
getProducts(): Observable<ProductModule[]>{
const url = 'YOUR URL HERE';
return this.http.get<ProductModule[]>(url).pipe(
tap((data: any) => {
console.log(data);
}),
catchError((err) => {
throw 'Error in source. Details: ' + err; // Use console.log(err) for detail
})
);
}
}
2) Solution 2. It is old way but still works.
// File - app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { HttpModule } from '#angular/http';
import { AppComponent } from './app.component';
import { ProductService } from './product.service';
import { ProductModule } from './product.module';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpModule
],
providers: [ProductService, ProductModule],
bootstrap: [AppComponent]
})
export class AppModule { }
// File - product.service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
// Importing rxjs
import 'rxjs/Rx';
import { Observable } from 'rxjs/Rx';
#Injectable()
export class ProductService{
// Initialize the properties.
constructor(private http: Http){}
// If there are no errors, then the object will be returned with the product data.
// And if there are errors, we will to into catch section and catch error.
getProducts(){
const url = '';
return this.http.get(url).map(
(response: Response) => {
const data = response.json();
console.log(data);
return data;
}
).catch(
(error: Response) => {
console.log(error);
return Observable.throw(error);
}
);
}
}
The RxJS functions need to be specifically imported. An easy way to do this is to import all of its features with import * as Rx from "rxjs/Rx"
Then make sure to access the Observable class as Rx.Observable.
in the latest version of angular4 use
import { Observable } from 'rxjs/Rx'
it will import all the required things.

Resources