Making ngrx-effects REST call - html

I am developing angular REST application using ngrx/effects, I am using example application GIT. I am trying to replace hardcoded json data in effects, from http REST end. I am getting errors "Effect "GetTodoEffects.todo$" dispatched an invalid action" . Could you please help me in solving it. Every thing is same as git code, except effects code which is i am pasting below.
Effects code:
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/withLatestFrom'
import { of } from 'rxjs/observable/of';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Action, Store } from '#ngrx/store';
import { Actions, Effect, toPayload } from '#ngrx/effects';
import * as Act from '../actions/app.actions';
import * as fromStore from '../reducers';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class GetTodoEffects {
#Effect() todo$ = this.actions$.ofType(Act.GET_TODO)
.map(toPayload)
.withLatestFrom(this.store$)
.mergeMap(([ payload, store ]) => {
return this.http$
.get(`http://localhost:4000/data/`)
.map(data => {
return [
new Act.GetTodoSuccess({ data: data })
]
})
.catch((error) => {
return [
new Act.GetTodoFailed({ error: error })
]
})
});
constructor(
private actions$: Actions,
private http$: HttpClient,
private store$: Store<fromStore.State>
) {}
}
I am using json-server as REST end point. json-server --port 4000 --watch expt-results-sample.json
expt-results-sample.json
[
{
text: "Todo 1"
},
{
text: "Todo 2"
},
{
text: "Todo 3"
}
]
})
]

First thing I suspect is the array. Try changing it to an observable.
return this.http$
.get(`http://localhost:4000/data/`)
.map(data => {
// You don't need an array because it's only 1 item
// If you want array use `Observable.from([ /* actions here */ ])`
// but then you'll need to change `map` above to
// `mergeMap` or `switchMap`
// (no big difference for this use case,
// `switchMap` is more conventional in Ngrx effects)
return new Act.GetTodoSuccess({ data: data });
})
.catch((error) => {
// You probably haven't called this yet,
// but `catch` must return `Obsrvable`
// Again, if you want an array use `Observable.from([ /* array */ ])`
return Observable.of(
new Act.GetTodoFailed({ error: error })
);
})

Related

How can I parse JSON attributes with slashes properly?

I have 2 Interfaces:
DTO.ts
export interface Report{
berichtId: number,
summary: Label
}
export interface Label{
text: string
}
I use them to type my HttpClient get request, to receive an array of Reports:
Dao.service.ts
getReports():Observable<DTO.Report[]>{
return this.http.get<DTO.Report[]>(Dao.API+'report/current')
.pipe(
retry(3),
catchError(this.handleErrors)
);
What I get is this JSON:
[
{
"berichtId":1777,
"summary":
"{\"text\":\"asdf\"}"
}
]
Now I want to read them out but these slashes dont let me convert the JSON to my pre defined interface Report object. It just translates it to a normal string.
this.dao.getReports().subscribe(report=>{
console.log(report[0].summary); // {"text":"asdf"}
console.log(report[0].summary.text) // undefined
});
What is the best way to handle that problem? There are solutions online but they are often rather counter intuitive. There must be a better way in Angular.
I think you need to use map operator from rxjs/operators before returniing data from service.
I have intercepted the data using map (rxjs) and then parsed the summary.
Demo (check console on button click)
Service
import { Injectable } from '#angular/core';
import { of, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Report } from './types';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class DataService {
constructor(private http: HttpClient) { }
getReports(): Observable<Report[]> {
return this.http.get<any[]>('api')
.pipe(
map(
values =>
values.map(val => {
val.summary = JSON.parse(val.summary)
return val;
}
)
));
}
// mock methos for demo
mockGetReports() {
return of<any[]>([
{
"berichtId":1777,
"summary":
"{\"text\":\"asdf\"}"
}
])
.pipe(
map(
values =>
values.map(val => {
val.summary = JSON.parse(val.summary)
return val;
}
)
));
}
}
I think you should try JSON.parse() or if it does not help then you can try this npm library
I hope this solves your issue.

Parse a json data from internal json file using Angular throwns error

I tried to get json from tne internal json file within angular.
with this service (village.service):
import { Injectable, OnInit } from '#angular/core';
import { Http, Response } from '#angular/http';
import { environment } from '../../environments/environment';
import { Observable } from 'rxjs'
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
#Injectable()
export class RecordsService {
data: any;
constructor(private http: Http) { }
getVillages(id) {
return this.http.get('../assets/data/villages.json')
.map(data => {
this.data = data.json();
return data.json();
}, err => {
if (err) {
return err.json();
}
});
}
}
and under commponet i put the:
ngOnInit() {
this.getVillages();
....
}
and here to load as the chain dropdown
onSubDistrictSelected(subDistrictId: number) {
if (subDistrictId) {
this.onLoading.emit(true);
this.customer.subDistrict = this.subDistricts.filter(c => (c.id == subDistrictId))[0].name;
this.customer.sdid = subDistrictId;
this.customer.subDistrictId = subDistrictId;
this.villages = this.getVillages().filter((item) => {
return item.subDistrictId === Number(subDistrictId)
});
this.onLoading.emit(false);
}
}
I got error when compile said: this.getVillages is not function, But is working correctly if i put the json value inside the component file:
getVillages() {
return [
{ json_data}
]
}
What I want to achieved is I want to used the JSon file instead put directly inside the commponet.
Thanks,
getVillages is a method in service, so you need to instantiate the service before you use it.
First you need to provide the RecordsService in a module, like,
app.module.ts
...
providers : [
RecordsService
]
...
And in your component,
abc.component.ts
constructor(public recordService : RecordsService) {
}
ngOnInit() {
this.recordService.getVillages();
}
Let me know if you still get the error or have some different error.
EDIT:
getVillages() is returning an Observable, so you need to subscribe in order to use the data returned.
this.recordService.getVillages().subscribe( data => {
console.log(data);
} )

Displaying linechart using angular2-highcharts

I want to display a line chart from JSON data. I have used angular2-highcharts. The problem is that the chart is displayed without data. I think that the issue is from extracting data from JSON.
The JSON format looks like this:
[{"_id" : ObjectId("59049a7b223f1e21ee4ee23b"),"amount" : 1,"date" :
"Mon, 18 Dec 1995 18:28:35 GMT"},{"_id" :
ObjectId("59049a7b223f1e21ee4ee23b"),"amount" : 1,"date" : "Mon, 18
Dec 1995 19:28:35 GMT"}]
I need only the "amount" in the X value and the "date" in the Y value.
Here is my code
ChartistJs.service.js
import {Injectable} from '#angular/core';
import { Headers, Http, RequestOptions, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { Data } from "./Data";
import 'rxjs/add/operator/toPromise';
private Url ='http://localhost:3000/transfer/chart';
constructor (private http: Http) {}
getData(){
return this.http.get(this.Url)
.toPromise()
.then(response => response.json())
.catch(this.handleError);
}
ChartistJs.component.ts
import {Component} from '#angular/core';
import {ChartistJsService} from './chartistJs.service';
import 'style-loader!./chartistJs.scss';
import { Observable } from "rxjs/Observable";
import { ChartModule } from 'angular2-highcharts';
import 'rxjs/Rx';
import {Observer} from 'rxjs/Observer';
import {Http, Jsonp} from '#angular/http';
#Component({
selector: 'chartist-js',
template: `
<chart [options]="options"></chart>
`,
providers : [ChartistJsService]
})
export class ChartistJs {
options: Object;
constructor(private _chartistJsService:ChartistJsService) {
var chartData = this._chartistJsService.getData();
this.options = {
title : { text : 'simple chart' },
xAxis: {
type: 'category'
},
series: [{
data: chartData
}]
};
}
}
Can you help me how to deal with JSON data in Angular 2?
As Pankaj points out you are trying to pass a promise as the data, not the actual data that the promise eventually resolves to. More broadly, though, you aren't really using the tooling that Angular provides for dealing with HTTP.
In general, I would recommend that you:
Get used to dealing with observables, which is what Angular uses natively, rather than converting everything back to promises (although I think they still show this in the docs); and
Lean into the asynchronous nature of the observables, using the AsyncPipe to resolve them into your templates and the objects that RxJS provides to manipulate the data flow.
More specifically, here's one way you could implement what you're currently trying to.
Service:
#Injectable()
class DataService {
// acts as a pipe for the data that you can push new items into
private dataSubject = ReplaySubject(1);
// takes the subject and exposes the result, read-only
chartData$ = this.dataSubject.asObservable();
constructor (private http: Http) {}
getData() {
// GETs the data and pushes it into the subject
this.http.get('http://localhost:3000/transfer/chart')
.map(response => response.json())
.subscribe(data => this.dataSubject.next(data));
}
}
Component:
#Component({
... ,
// resolves the chart options asynchronously in the template
template: `
<chart [options]="chartOptions$ | async"></chart>
`
})
export class MyChartComponent implements OnInit {
chartOptions$: Observable<any>;
constructor(dataService: DataService) {
// creates a new observable of the chart options
this.chartOptions$ = this.dataService.chartData$
.map(data => this.createChartOptions(data));
}
ngOnInit() {
// triggers a fetch of the data to feed the observable
this.dataService.getData();
}
private createChartOptions(data) {
return {
title: { text: 'simple chart' },
xAxis: { type: 'category' },
series: [{ data: data }],
};
}
}
You will probably need to do more to the JSON than just pass it as the series.data, but this hopefully gives you an idea of how to leverage the stream of events an observable can provide. I've written more about this on my blog, including a follow-up article on testing.
Also note that your components shouldn't be importing anything from '#angular/http' - leave that to the services, use them as a layer of abstraction from the source of the data - and you can load providers at the module, rather than component, level.
Actually chartData variable does hold Promise returned by getData method. Where you should keep .then over the getData method calla and assign options with chartData like shown below.
It would be more better if you can do the same in ngOnInit lifecycle event.
Code
export class ChartistJs {
options: Object;
constructor(private _chartistJsService: ChartistJsService) {}
ngOnInit() {
this._chartistJsService.getData().then(
(data) => {
this.options = {
title: {
text: 'simple chart'
},
xAxis: {
type: 'category'
},
series: [{
data: data
}]
};
}
);
}
}

Ensure json configuration is loaded in Angular2 [duplicate]

Is there a way to pass arguments rendered on the backend to angular2 bootstrap method? I want to set http header for all requests using BaseRequestOptions with value provided from the backend. My main.ts file looks like this:
import { bootstrap } from '#angular/platform-browser-dynamic';
import { AppComponent } from "./app.component.ts";
bootstrap(AppComponent);
I found how to pass this arguments to root component (https://stackoverflow.com/a/35553650/3455681), but i need it when I'm fireing bootstrap method... Any ideas?
edit:
webpack.config.js content:
module.exports = {
entry: {
app: "./Scripts/app/main.ts"
},
output: {
filename: "./Scripts/build/[name].js"
},
resolve: {
extensions: ["", ".ts", ".js"]
},
module: {
loaders: [
{
test: /\.ts$/,
loader: 'ts-loader'
}
]
}
};
update2
Plunker example
update AoT
To work with AoT the factory closure needs to be moved out
function loadContext(context: ContextService) {
return () => context.load();
}
#NgModule({
...
providers: [ ..., ContextService, { provide: APP_INITIALIZER, useFactory: loadContext, deps: [ContextService], multi: true } ],
See also https://github.com/angular/angular/issues/11262
update an RC.6 and 2.0.0 final example
function configServiceFactory (config: ConfigService) {
return () => config.load();
}
#NgModule({
declarations: [AppComponent],
imports: [BrowserModule,
routes,
FormsModule,
HttpModule],
providers: [AuthService,
Title,
appRoutingProviders,
ConfigService,
{ provide: APP_INITIALIZER,
useFactory: configServiceFactory
deps: [ConfigService],
multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule { }
If there is no need to wait for the initialization to complete, the constructor of `class AppModule {} can also be used:
class AppModule {
constructor(/*inject required dependencies */) {...}
}
hint (cyclic dependency)
For example injecting the router can cause cyclic dependencies.
To work around, inject the Injector and get the dependency by
this.myDep = injector.get(MyDependency);
instead of injecting MyDependency directly like:
#Injectable()
export class ConfigService {
private router:Router;
constructor(/*private router:Router*/ injector:Injector) {
setTimeout(() => this.router = injector.get(Router));
}
}
update
This should work the same in RC.5 but instead add the provider to providers: [...] of the root module instead of bootstrap(...)
(not tested myself yet).
update
An interesting approach to do it entirely inside Angular is explained here https://github.com/angular/angular/issues/9047#issuecomment-224075188
You can use APP_INITIALIZER which will execute a function when the
app is initialized and delay what it provides if the function returns
a promise. This means the app can be initializing without quite so
much latency and you can also use the existing services and framework
features.
As an example, suppose you have a multi-tenanted solution where the
site info relies on the domain name it's being served from. This can
be [name].letterpress.com or a custom domain which is matched on the
full hostname. We can hide the fact that this is behind a promise by
using APP_INITIALIZER.
In bootstrap:
{provide: APP_INITIALIZER, useFactory: (sites:SitesService) => () => sites.load(), deps:[SitesService, HTTP_PROVIDERS], multi: true}),
sites.service.ts:
#Injectable()
export class SitesService {
public current:Site;
constructor(private http:Http, private config:Config) { }
load():Promise<Site> {
var url:string;
var pos = location.hostname.lastIndexOf(this.config.rootDomain);
var url = (pos === -1)
? this.config.apiEndpoint + '/sites?host=' + location.hostname
: this.config.apiEndpoint + '/sites/' + location.hostname.substr(0, pos);
var promise = this.http.get(url).map(res => res.json()).toPromise();
promise.then(site => this.current = site);
return promise;
}
NOTE: config is just a custom config class. rootDomain would be
'.letterpress.com' for this example and would allow things like
aptaincodeman.letterpress.com.
Any components and other services can now have Site injected into
them and use the .current property which will be a concrete
populated object with no need to wait on any promise within the app.
This approach seemed to cut the startup latency which was otherwise
quite noticeable if you were waiting for the large Angular bundle to
load and then another http request before the bootstrap even began.
original
You can pass it using Angulars dependency injection:
var headers = ... // get the headers from the server
bootstrap(AppComponent, [{provide: 'headers', useValue: headers})]);
class SomeComponentOrService {
constructor(#Inject('headers') private headers) {}
}
or provide prepared BaseRequestOptions directly like
class MyRequestOptions extends BaseRequestOptions {
constructor (private headers) {
super();
}
}
var values = ... // get the headers from the server
var headers = new MyRequestOptions(values);
bootstrap(AppComponent, [{provide: BaseRequestOptions, useValue: headers})]);
In Angular2 final release, the APP_INITIALIZER provider can be used to achieve what you want.
I wrote a Gist with a complete example: https://gist.github.com/fernandohu/122e88c3bcd210bbe41c608c36306db9
The gist example is reading from JSON files but can be easily changed to read from a REST endpoint.
What you need, is basically:
a) Set up APP_INITIALIZER in your existent module file:
import { APP_INITIALIZER } from '#angular/core';
import { BackendRequestClass } from './backend.request';
import { HttpModule } from '#angular/http';
...
#NgModule({
imports: [
...
HttpModule
],
...
providers: [
...
...
BackendRequestClass,
{ provide: APP_INITIALIZER, useFactory: (config: BackendRequestClass) => () => config.load(), deps: [BackendRequestClass], multi: true }
],
...
});
These lines will call the load() method from BackendRequestClass class before your application is started.
Make sure you set "HttpModule" in "imports" section if you want to make http calls to the backend using angular2 built in library.
b) Create a class and name the file "backend.request.ts":
import { Inject, Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Rx';
#Injectable()
export class BackendRequestClass {
private result: Object = null;
constructor(private http: Http) {
}
public getResult() {
return this.result;
}
public load() {
return new Promise((resolve, reject) => {
this.http.get('http://address/of/your/backend/endpoint').map( res => res.json() ).catch((error: any):any => {
reject(false);
return Observable.throw(error.json().error || 'Server error');
}).subscribe( (callResult) => {
this.result = callResult;
resolve(true);
});
});
}
}
c) To read the contents of the backend call, you just need to inject the BackendRequestClass into any class of you choice and call getResult(). Example:
import { BackendRequestClass } from './backend.request';
export class AnyClass {
constructor(private backendRequest: BackendRequestClass) {
// note that BackendRequestClass is injected into a private property of AnyClass
}
anyMethod() {
this.backendRequest.getResult(); // This should return the data you want
}
}
Let me know if this solves your problem.
Instead of having your entry point calling bootstrap itself, you could create and export a function that does the work:
export function doBootstrap(data: any) {
platformBrowserDynamic([{provide: Params, useValue: new Params(data)}])
.bootstrapModule(AppModule)
.catch(err => console.error(err));
}
You could also place this function on the global object, depending on your setup (webpack/SystemJS). It also is AOT-compatible.
This has the added benefit to delay the bootstrap, whenit makes sense. For instance, when you retrieve this user data as an AJAX call after the user fills out a form. Just call the exported bootstrap function with this data.
The only way to do that is to provide these values when defining your providers:
bootstrap(AppComponent, [
provide(RequestOptions, { useFactory: () => {
return new CustomRequestOptions(/* parameters here */);
});
]);
Then you can use these parameters in your CustomRequestOptions class:
export class AppRequestOptions extends BaseRequestOptions {
constructor(parameters) {
this.parameters = parameters;
}
}
If you get these parameters from an AJAX request, you need to bootstrap asynchronously this way:
var appProviders = [ HTTP_PROVIDERS ]
var app = platform(BROWSER_PROVIDERS)
.application([BROWSER_APP_PROVIDERS, appProviders]);
var http = app.injector.get(Http);
http.get('http://.../some path').flatMap((parameters) => {
return app.bootstrap(appComponentType, [
provide(RequestOptions, { useFactory: () => {
return new CustomRequestOptions(/* parameters here */);
}})
]);
}).toPromise();
See this question:
angular2 bootstrap with data from ajax call(s)
Edit
Since you have your data in the HTML you could use the following.
You can import a function and call it with parameters.
Here is a sample of the main module that bootstraps your application:
import {bootstrap} from '...';
import {provide} from '...';
import {AppComponent} from '...';
export function main(params) {
bootstrap(AppComponent, [
provide(RequestOptions, { useFactory: () => {
return new CustomRequestOptions(params);
});
]);
}
Then you can import it from your HTML main page like this:
<script>
var params = {"token": "#User.Token", "xxx": "#User.Yyy"};
System.import('app/main').then((module) => {
module.main(params);
});
</script>
See this question: Pass Constant Values to Angular from _layout.cshtml.

How to get json data from service method call to test case using angular2 unit testing with karma-Jasmine?

I have trying to get json data from when i calling service method. I got a return object but i didn't get json data. Which part i have to change?
"goods.services.ts"
getAllData(): Observable<Product>{
let jwtTok1 ="Something";
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Token ' + jwtTok1);
let options = new RequestOptions({ headers: headers, body: {} });
return this.http.get(BASE_URL_GOODS, options)
.map((res:Response) => {return Observable.of ({type: "success", payload: res.json().data})})
.catch(error => this.handleError(error));
}
Another is test case file "goods.component.spec.ts"
import {
inject,
TestBed,fakeAsync,tick
} from '#angular/core/testing';
import { Component } from '#angular/core';
import {
BaseRequestOptions,
ConnectionBackend,
Http
} from '#angular/http';
import { MockBackend,MockConnection } from '#angular/http/testing';
// Load the implementations that should be tested
import { Observable } from 'rxjs/Rx';
import { AppState } from '../app.service';
import { ActionReducer, Action, Store } from '#ngrx/store';
import { AppStore } from '../models/appstore.model';
import {goodsDescriptionComponent } from './goods-desc.component';
import { goodsService } from '../common/service/goods.service';
import { goodsReducer } from '../common/reducers/goods.reducer';
import {provideStore} from '#ngrx/store';
import { goods} from '../common/models/goods.model';
import { Input } from '#angular/core';
describe('GoodsDescriptionComponent', () => {
//let store:Store<AppStore>;
beforeEach(() => TestBed.configureTestingModule({
providers: [
BaseRequestOptions,
MockBackend,
{
provide: Http,
useFactory: function(backend: ConnectionBackend, defaultOptions: BaseRequestOptions) {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
},
goodsService, provideStore({goodsData: goodsReducer}),goodsDescriptionComponent
]}));
it('should url will be same ',
inject(
[goodsService, MockBackend],
fakeAsync((service:ProductsService, backend: MockBackend) => {
backend.connections.subscribe((connection: MockConnection) => {
expect(connection.request.url).toBe(
'http://localhost:3001/goodsMS/');
});
service.getAllData();
console.log("goods what we got: ",service.getAllData());
})));
});
Getting Response Result is,
This Response object getting from console in google chrome. Still i can't get correct solution for getting json data from service method call.i can't reach json server. my json server URL is , "http://localhost:3001/goods". please anyone help me. Thanks in advance.
You need to set a response on the connection
backend.connections.subscribe((connection: MockConnection) => {
expect(connection.request.url).toBe(
'http://localhost:3001/goodsMS/');
connection.mockRepond(new Response(new ResponseOptions({
body: `{"some":"json", "response":"body"}`
}));
});
You need to subscribe to your service call
service.getAllData().subscribe((result) => {
expect(somethingWithData)
})
tick()
since you are using fakeAsync, you need to tick to force completion of the asynchronous observable. If you use async instead of fakeAsync, then you don't need to call tick
You should not be returning an Observable in the service method map function. Just return a normal object.
.map((res:Response) => {type: "success", payload: res.json().data})