Can't bind JSON results to Component variable - javascript

I'm trying a simple component that has to pull data from a JSON file. I'm almost copying the functionality from generated Fountain App, but for some reason I can't get the desired results. I have a component like:
import {Component, Inject} from "#angular/core";
import {Http} from '#angular/http';
#Component({
selector: 'body-selector',
template: require('./body.html')
})
#Inject(Http)
export class BodyComponent {
constructor(http: Http) {
this.http = http;
this.getText('app/texts/main.json').subscribe(result => {
console.log(result);
this.texts = result;
});
console.log(this.texts);
}
getText(url) {
return this.http.get(url).map(response => response.json());
}
}
on the first console.log I have [Object object] [Object object] [Object object], which is correct as I have three entries in the JSON. On the second however I've got undefined, which turns into an error in the browser.
Error in ./BodyComponent class BodyComponent - inline template:3:6 caused by: Cannot read property 'title' of undefined
I'm looking at the example generated from the fountain app, but I can't get what I'm doing wrong.

You have multiple problems:
The first console.log is inside the callback, where this.texts has just been set. However the second one is outside the callback, so it won't have been. Therefore you'll always see undefined for that, because...
...you never set a default value for this.texts, and your template apparently doesn't have any e.g. *ngIf to handle it being null or undefined, causing errors prior to the callback being called.
Below is your code, refactored to start with an empty this.texts (assuming it should be an array; please adapt to taste) and simplifying the injection of Http. Also note the comments, and that I've used templateUrl to avoid the require and OnInit to trigger the HTTP call slightly later in the component lifecycle rather than doing it in the constructor.
import {Component, OnInit} from '#angular/core'; // note consistent quotes
import {Http} from '#angular/http';
#Component({
selector: 'body-selector',
templateUrl: './body.html',
})
export class BodyComponent implements OnInit {
texts: any[] = []; // start with an empty array
constructor(private http: Http) { } // inject Http here, no need to assign to this
ngOnInit() {
this.http
.get('app/texts/main.json')
.map(response => response.json())
.subscribe(result => {
console.log(result); // only log *inside* the callback
this.texts = result;
});
// outside the callback, the HTTP call hasn't yet finished
}
}
You could also solve this by having an ngIf in your HTML to prevent the element from being loaded before the data is.
<div class="main-container" *ngIf="texts">
...
</div>
I'd strongly recommend running through the basic Angular 2 tutorials to get on top of this stuff, see e.g. the Tour of Heroes.

You get undefined because this:
this.getText('app/texts/main.json')
Is an asynchronous call that gets the data and when it's done, it executes the code in the 'subscribe' block. So this.text is empty until that executes. That is the expected behavior.

Better way to use data or making API call use service:
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
#Injectable()
export class BodyService {
private _requestOptions: RequestOptions;
private static handleError(error: Response): ErrorObservable {
return Observable.throw(error.json().error || 'Server error');
}
constructor(private http: Http) {
const headers = new Headers({ 'Accept': 'application/json' });
this._requestOptions = new RequestOptions({ headers: headers});
}
/**
* [getJsonData will get data of json file: main.json ]
*/
getJsonData() {
return this.http.get('app/texts/main.json', this._requestOptions)
.map(res => res.json()) //needs to add map for converting data in json format
.catch(BodyService.handleError);
}
}
Now Inject this service in your component:
import {Component, OnInit} from '#angular/core';
import { BodyService } from './adminModules.service';
#Component({
selector: 'body-selector',
templateUrl: './body.html',
providers: [BodyService]
})
export class BodyComponent implements OnInit {
texts: any = []; // start with an empty array
errorMessage: any;
constructor(private _bodyService: BodyService) {}
ngOnInit() {
this._bodyService.getJsonData()
.subscribe(data => {
this.texts = data;
console.log('data', this.texts);
}, error => {
this.errorMessage = <any> error;
})
}
}
For calling service, you can call directly in constructor or create one method and call either in constructor or any place where you want to call.
Hope it will helpful for you :)

Related

Extract URL Query Parameters from Request URL in Angular 8

Is there any way I could fetch the GET URL Query String Parameters from any certain URL in my Angular Service?
For E.g. Suppose I have a URL = "http:localhost/?id=123&name=abc";
or URL = "http://www.myexamplewebsite.com?id=123&name=abc";
// in my service.ts
public myFunction(): Observale<any>
{
let getVariable= this.http.get(URL);
return getVariable.pipe(map(response => <Object>response), catchError(error => this.handleError(error)));
}
So either in my component.ts or service.ts is there any way I could extract this id & name? I am new with this topic.
Note: I am not running that URL in my route. So this.route.snap.params function didn't help.
Try to use Angular's HttpParams. Import this: import { HttpClient,HttpParams } from '#angular/common/http'; and use get(paramName)or getAll()as String[].
Here is another example of how you can get the Params: https://www.tektutorialshub.com/angular/angular-pass-url-parameters-query-strings/
You can get the params by injecting Activatedroute in the component constructor:
constructor(activatedRoute: ActivatedRoute) {
let id = activatedRoute.snapshot.queryParams['id'];
let name = activatedRoute.snapshot.queryParams['name'];
}
in your component do like this.
import it.
import { ActivatedRoute } from '#angular/router';
then in constructor
private route: ActivatedRoute,
and then you can get param like this.
this.route.queryParams.subscribe(data => {
console.log(data)
});
working demo
try this url and check console. you will get query params.
let me know if you still have issue.
you can also check on this url - https://angular-routing-query-params.stackblitz.io/?abc=test&xyz=test2
output will be like this
The ActivatedRoute class has a queryParams property that returns an observable of the query parameters that are available in the current url.
export class ProductComponent implements OnInit {
order: string;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.queryParams
.filter(params => params.order)
.subscribe(params => {
console.log(params); // {order: "popular"}
this.order = params.order;
console.log(this.order); // popular
});
}
}
Click here for complete article, its easy and best

Adding an src from an API in Angular7

This is my component.ts where when it's loaded I get the data from the api, which I can see in the console.log, I do infact get my array of 10 objects (they come in groups of 10 on the api). I have the correct path in the API for the source code of the first image in the array of 10 which I typed to out the correct path for in normal http/javascript format of data.hits.hits[n]._source.images[n].urls.original. However when I try to put it in angular it can't read the data value as it is right now since it's out of scope, but I can't figure out how to word it in a better way.
import { Component, OnInit } from '#angular/core';
import { ConfigService } from '../../config.service';
import { Observable } from 'rxjs';
#Component({
selector: 'app-property-binding',
templateUrl: './property-binding.component.html',
styleUrls: ['./property-binding.component.css']
})
export class PropertyBindingComponent implements OnInit {
private isHidden : boolean;
public zeroImage : string;
private Photos : Observable<Object>;
constructor(private configService: ConfigService) { }
ngOnInit() {
//doing the API call
this.Photos = this.configService.getConfig();
this.Photos.subscribe((data) => console.log(data));
}
toggle() : void {
this.isHidden = !this.isHidden;
if(this.isHidden){
//var zeroImg = document.createElement("img");
this.zeroImage.src = data.hits.hits[0]._source.images[0].urls.original;
}
}
}
Here is the Angular html page that should property bind the src with the variable that I want.
<p>
View Artworks
</p>
<button class="btn btn-info" (click)="toggle()">Show Artwork</button>
<div class="col-md-4" *ngIf="isHidden">
<img [src]="zeroImage">
</div>
Here is the service method that I have the method that makes the API call
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { HttpHeaders } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class ConfigService {
private httpOptions = {
headers: new HttpHeaders({
'ApiKey': 'my_personal_key'
})
};
private configUrl = 'https://api.art.rmngp.fr/v1/works';
constructor(private http: HttpClient) { }
getConfig(){
let obs = this.http.get(this.configUrl, this.httpOptions)
console.log("Inside the getConfig method, before subscribe, doing API call" +
obs);
//might not need to subscribe here??
//obs.subscribe((response) => console.log(response))
return obs;
//return this.http.get(this.configUrl, this.httpOptions);
}
}
And slightly unrelated code, this is the normal http/javascript where I wrote the code Outside of Angular, which works perfectly fine.
function displayPhoto(){
fetch('https://api.art.rmngp.fr/v1/works/, {headers: {ApiKey: "my_personal_key"}})
.then(function(response){
return response.json();
})
.then(function(data){
document.getElementById("zeroImg").src = data.hits.hits[0]._source.images[0].urls.original;
Again, the API call in Angular works, I can see I am pulling the data successfully, I however can not set the image to the first image in the set of data and have been struggling with it. any help will help
You are not doing anything with the data when you subscribe
this.Photos.subscribe((data) => console.log(data));
You have not done anything with the data here.
zeroImg.src = data.hits.hits[0]._source.images[0].urls.original;
zeroImg is a string and makes no sense to set a src property on it and data is undefined at the point. The only place there is a data variable is in your subscription function but it is not available here.
The following will set the src of the image
this.Photos.subscribe((data) => {
this.zeroImg = data.hits.hits[0]._source.images[0].urls.original;
});
Make the toggle function just toggle the isHidden flag and get rid of the rest.
ngOnInit() {
//doing the API call
this.Photos = this.configService.getConfig();
this.Photos.subscribe((data) => {
this.zeroImg = data.hits.hits[0]._source.images[0].urls.original;
});
}
toggle() : void {
this.isHidden = !this.isHidden;
}

add data to the end of a behavior object array Angular 5

I have some data that I want to be shared with my entire app so I have created a service like so..
user.service
userDataSource = BehaviorSubject<Array<any>>([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(data);
}
then in my component Im getting some data from an api and then sending that data to userDataSource like so..
constructor(
private: userService: UserService,
private: api: Api
){
}
ngOnInit() {
this.api.getData()
.subscribe((data) => {
this.userService.updateUserData(data);
})
}
now that all works but.. I want to be able to add data to the end of the array inside the userDataSource so basically the equivalent of a .push am I able to just call the updateUserData() function and add more data or will doing that overwrite what is currently in there?
Any help would be appreciated
You can add a new method to your service like addData in which you can combine your previous data with new data like.
import {Injectable} from '#angular/core';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class UserService {
userDataSource: BehaviorSubject<Array<any>> = new BehaviorSubject([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(data);
}
addData(dataObj) {
const currentValue = this.userDataSource.value;
const updatedValue = [...currentValue, dataObj];
this.userDataSource.next(updatedValue);
}
}
For someone that may come accross this issue with a BehaviorSubject<YourObject[]>.
I found in this article a way to properly add the new array of YourObject
import { Observable, BehaviorSubject } from 'rxjs';
import { YourObject} from './location';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ObjService {
private theObjData: BehaviorSubject<YourObject[]> = new BehaviorSubject<YourObject[]>(null);
constructor() {
}
public SetObjData(newValue: YourObject[]): void {
this.theObjData.next(Object.assign([], newValue));
}
}
How to update data:
// inside some component
this.api.userData().subscribe((results:YourObject) =>
this.objService.SetObjData(results);
)
How to observe changes on other component
// inside another component
ngOnInit() {
this.objService.GetAccountStatements().subscribe((results) =>
...
)
}
Normally Observables and Subjects are meant to be streams of data, not an assignment of data. BehaviorSubjects are different because they hold their last emitted value.
Normally Subjects or BehaviorSubjects inside of a contained class (like a Service) do not want to expose themselves publicly to any other classes, so it's best practice to access their properties with getters or methods. This keeps the data stream cold to all subscribers.
However, since the BehaviorSubject holds the last emitted value, there's a few options here. If all subscribers need a concatenated stream of data from every emission, you could access the last emitted value and append to it:
userDataSource = BehaviorSubject<any[]>([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(this.userDataSource.value.push(data));
}
...or, in what might be considered better practice, Subscribers to this Subject could do their own transformation on the stream:
this.api.userData()
.scan((prev, current) => prev.push(current). [])
.subscribe((data) => {
this.concatenatedUserData = data;
});
Use concat to add object
userDataSource = BehaviorSubject<Array<any>>([]);
updateUserData(data) {
this.userDataSource.next(this.userDataSource.value.concat(data));
}
Use filter to remove object
removeUserData(data) {
this.userDataSource.next(this.userDataSource.value.filter(obj => obj !== data));
}

Angular 5 Unit Testing cannot read property 'http' of undefined

I am attempting to write a helper function in an Angular test leveraging HttpTestingController. The reason being is because I will eventually have a series of endpoints within my Angular service, RequestService, that I want to test within this testing file. I do not want to repeatedly inject my RequestService and HttpTestingController instances into each test function that tests the service. That is redundant. Rather, I would prefer to have a single test function that takes the injected RequestService and HttpTestingController instances and repeatedly passes them into the helper function I have created, requestHelper. This way when I want to test additional endpoints, all I need to do is make a call to the helper function and provide the parameters that are needed.
The problem I am bumping into is that when the helper function runs, the service's instance for some reason does not appear to exist, Even though the test is able to access the service's functions. When it reaches my service method's call to the http.get within the callEndpoint function, it gives me the below error:
Failed: Cannot read property 'http' of undefined
This does not make sense to me because the this keyword is referring to the instance of the Angular service, and the test case is able to reach the service's function, so how could this possibly be undefined?
Here is my test spec:
import { TestBed, async, inject } from '#angular/core/testing';
import { HttpClientModule, HttpRequest, HttpParams } from '#angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '#angular/common/http/testing';
import { RequestService } from './request.service';
describe(`RequestService`, () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
HttpClientModule,
HttpClientTestingModule
],
providers: [
RequestService
]
});
}));
afterEach(async(inject([HttpTestingController], (backend: HttpTestingController) => {
backend.verify();
})));
it(`TESTING INJECTION`, async(inject([RequestService, HttpTestingController],
(service: RequestService, backend: HttpTestingController) => {
requestHelper(service.callEndpoint,'https://endpointurl.com',backend);
})));
function requestHelper(serviceCall: Function, url: string, backendInstance: any) {
serviceCall(...serviceParams).subscribe();
backendInstance.expectOne((req: HttpRequest<any>) => {
return req.url === url
&& req.method === 'GET';
}, 'GET');
}
});
And the respective service that the spec is testing
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class RequestService {
private requestOptions = {
headers: new HttpHeaders({'Locale': 'en_US'})
};
constructor(private http: HttpClient) { }
callEndpoint(state: string, countryCode: string): Observable<Object> {
return this.http.get(`https://endpointurl.com`,this.requestOptions);
}
}
Thank you for your help!
You can bind the context in the it block:
it(`TESTING INJECTION`, async(inject([RequestService, HttpTestingController],
(service: RequestService, backend: HttpTestingController) => {
requestHelper(service.callEndpoint.bind(service),'https://endpointurl.com',backend);
})));

Angular - How can I mock an HTTP request?

I have a simple code which invokes a real HTTP request :
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{person?.id}}</h2>
</div>
`,
})
export class App {
name:string;
constructor(public http: Http) {
this.http.get('https://jsonplaceholder.typicode.com/posts/1')
.map(res => res.json()).subscribe(res => {
this.person = res;
},()=>{},()=>console.log('complete'));
}
}
But now I want to mock the request so that it will fetch data from a file containing :
export arrFakeData:any = {id:1};
I don't want to use a service . I want to mock the request.
Some examples shows to use XHRBackend and some shows how to extend the HTTP class, but they doesn't say how can I force the data to retrieve
I know that I should use
providers:[ /*{ provide: XHRBackend, useClass: MockBackend }*/]
But I don't know how.
Question:
How can I mock http request and return (for GET) the array from arrFakeData ?
PLUNKER
Personally, I would replace the this.http.get method call with an Observable.of so that you can continue programming against the same interface (Observable) without impacting the development of your components.
However, if you really want to do this then you will have to create a service that attaches a listener to all the incoming requests and returns an appropriate mock response using the tools provided by the #angular/http/testing module.
The service will look something as such:
import {Injectable} from "#angular/core";
import {MockBackend, MockConnection} from "#angular/http/testing";
import {arrFakeData} from "./fakeData";
import {ResponseOptions, Response} from "#angular/http";
#Injectable()
export class MockBackendService {
constructor(
private backend: MockBackend
) {}
start(): void {
this.backend.connections.subscribe((c: MockConnection) => {
const URL = "https://jsonplaceholder.typicode.com/posts/1";
if (c.request.url === URL) { // You can also check the method
c.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify(arrFakeData)
})));
}
});
}
}
Once you have done this, you need to register all the services and make sure that the Http module is using the MockBackend instead of the XHRBackend.
#NgModule({
imports: [BrowserModule, HttpModule,],
declarations: [App],
providers: [
MockBackend,
MockBackendService,
BaseRequestOptions,
{
provide: Http,
deps: [MockBackend, BaseRequestOptions],
useFactory: (backend: MockBackend, options: BaseRequestOptions) => {
return new Http(backend, options);
}
}
],
bootstrap: [App]
})
export class AppModule {
}
Last but not least, you have to actually invoke the start method, which will make sure that you will actually receive the mock data from the MockBackend. In your AppComponent you can do the following.
constructor(public http: Http, public mockBackendService: MockBackendService) {
this.mockBackendService.start();
this.http.get('https://jsonplaceholder.typicode.com/posts/1')
.map(res => res.json())
.subscribe(res => {
this.person = res;
});
}
I hope this helps! See the plunker for the full example. https://plnkr.co/edit/h111to5PxbI97FIyKGJZ?p=preview
You can just make the http endpoint a JSON file containing whatever data you need. This is exactly how we did it on my last project for Google, and how I do it in my own side projects. We didn't bother mocking up http services and so on, we just pointed at a json file and left everything else the same.

Categories

Resources