Angular observable subscribe JSON parsing issue - json

I have a service performing http.get on a Drupal API and retrieving JSON data.
The component utilising that JSON data keeps generating the following error:
ERROR in src/app/form-test/form-test.component.ts(18,28): error TS2551: Property 'included' does not exist on type 'Question[]'. Did you mean 'includes'?
From the following code:
constructor(private dataService: QuizService) { }
ngOnInit() {
this.dataService.fetch().subscribe(data => {
this.jsondata = data.included[0].attributes.field_json;
console.log(data, ': DATA');
});
}
I don't understand why there is a problem with the JSON and why it's trying to find includes instead of included in the JSON structure. Below is a screenshot of a sample of the JSON:
I have confirmed the structure of the JSON data (as confirmed from the image above), also from console logging the JSON data and that the API URL is live at the time Ay angular app is attempting to call it.
Can anyone advice what is the cause of this error and how can I resolve it?
UPDATE:
quiz.service.ts:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
export interface Question {
// title: string;
question: string;
included: any[];
}
#Injectable({
providedIn: 'root'
})
export class QuizService {
// tslint:disable-next-line: max-line-length
private quizURL: string = 'http://drupal-8-composer-drupal-test.com/jsonapi/node/quiz/31f020f7-34d9-4b9a-bd2b-0d567eb285dc/?include=field_questions&fields%5Bnode--quiz%5D=title,drupal_internal__nid,body&fields%5Bnode--question%5D=title,field_processed,body,field_options,field_json';
constructor(private httpClient: HttpClient) { }
fetch(): Observable<Question[]> {
return this.httpClient.get<Question[]>( this.quizURL );
}
}

The error states that data has type Question[]. It is an array, not an object. Typescript compiler tries to find an included variable in array and there's none. So it gives you an error.
Your JSON structure contains an array of questions in the included field. So the type which the fetch returns should be like { included: Question[] }:
fetch(): Observable<{ included: Question[] }> {
return this.httpClient.get<{ included: Question[] }>( this.quizURL );
}
Or you can process the response in service and return questions only:
fetch(): Observable<Question[]> {
return this.httpClient.get(this.quizURL)
.pipe(map((data: { included: Question[] }) => data.included));
}
.map operator gets the whole response object, extracts only questions and returns them as array.

Related

ANGULAR - Mapping nested JSON data from API

so I've been struggling for the past day or so with mapping the response from a mock API - I think I'm mapping it correctly but when I try to access the data it doesn't return anything in the HTML.
Please find my code below:
data.service.ts
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { ConsentData, Prompt } from '#app/models/consent-data';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root',
})
export class ConsentWebviewDataService {
constructor(private httpClient: HttpClient) {}
getConsentData(): Observable<ConsentData<Prompt>> {
return this.httpClient.get<ConsentData<Prompt>>(
'MY_API_URL',
);
}
}
data.ts (interface)
export interface ConsentData<Prompt> {
prompts: Prompt[];
}
export interface Prompt {
promptId: number;
headline: string;
body: string;
imageUrl: string;
consents: string[];
type: string;
}
app.component.ts
export class PromptComponent implements OnInit {
consentData: any;
constructor(private consentWebviewDataService: ConsentWebviewDataService) {}
ngOnInit(): void {
this.consentWebviewDataService.getConsentData().subscribe(data => {
this.consentData = data.prompts.map(consents => {
return {
promptId: consents.promptId,
headline: consents.headline,
body: consents.body,
imageUrl: consents.imageUrl,
consents: consents.consents,
type: consents.type,
};
});
});
}
}
Lastly here is the API JSON response:
{"prompts":[{"promptId":100,"headline":"Headline","body":"Body text.","imageUrl":"https://picsum.photos/200","consents":["Consent 1","Consent 2","Consent 3"],"type":"ConsentCampaign"}]}
From what I understand so far, after countless tutorials and resources, the getCosentData() function sends request to API, then in the component I subscribe to it, get the response, assign the response to the consentData variable, then map the contents of the response based on the interface / how the JSON response looks.
However, the problem is that I cannot access the mapped data in the HTML. I don't need it in a table, just need to get the mapped data.
I tried all variations such as {{ consentData.promptId }} which I mapped, and it returns ERROR TypeError: ctx.consentData is undefined. Tried {{ consents.promptId }} as well, etc. but nothing works.
What am I missing here? And apologies for the long question && thanks in advance for any help!
You mapped the response into a new array and trying to access it as an object
Try {{ consentData[0].promptId }} to get the id of first element

How can I use the data obtained from a local json file in Angular?

I am using Angular and have stored a json file with data in it locally.
I am accessing the json file by importing it into my component through:
import * as data from '../../data/countries.json';
In my tsconfig.json file, I have set the following:
"resolveJsonModule": true
I am running into issues when using the data set.
The following works:
console.log(data[0].Country); and this returns me the name of the first country in the list, printing it to the chrome console.
However, when I attempt to use this data within the component.ts code, I get the following errors:
Code:
for (let i = 0; i < 50; i++) {
let name :string = data[i].Country;
this.addCoordinates(name, data[i].latitude, data[i].longitude);
}
Error:
core.js:6210 ERROR TypeError: Cannot read property 'Country' of undefined
at GlobeComponent.changeCountry (globe.component.ts:208)
at GlobeComponent.ngAfterViewInit (globe.component.ts:75)
at callHook (core.js:2573)
at callHooks (core.js:2542)
at executeInitAndCheckHooks (core.js:2493)
at refreshView (core.js:9537)
at refreshComponent (core.js:10637)
at refreshChildComponents (core.js:9263)
at refreshView (core.js:9516)
at renderComponentOrTemplate (core.js:9580)
Any help would be much appreciated! Thanks in advance.
I found a solution:
I've created a service that reads the JSON file through the HttpClient and returns the array of objects stores in your file.
This is the service:
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class LocalJSONService {
constructor(private http: HttpClient) { }
getLocalJSON$(): Observable<any>{
return this.http.get(`../assets/countries.json`);
}
}
And this is the globe.component.ts:
First inject the new service on the controller:
constructor(private localJSON: LocalJSONService) {
...
}
And then on the ngOnInit (you may place it on the ngAfterViewInit probably) I call a function called getLocalJSON$:
getLocalJSON$(): void {
this.localJSON
.getLocalJSON$()
.pipe(first())
.subscribe((countries: any[]) => {
countries.map((country: any) => console.log(country));
})
}
Instead of iterate over the countries you can store the values or call another function, whatever you need.

Angular 5, rxjs map to json, 'json' does not exist on the the type 'object

Trying to follow an online video, then this appears, I am new to angular, other solutions are not helping me out.
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/map';
/*
Generated class for the WeatherProvider provider.
See https://angular.io/guide/dependency-injection for more info on providers
and Angular DI.
*/
#Injectable()
export class WeatherProvider {
apikey='7d2dc7a226a78c14';
url;
constructor(public http: HttpClient) {
console.log('Hello WeatherProvider Provider');
this.url='http://api.wunderground.com/api/'+this.apikey+'/conditions/q'
}
getWeather(city,state){
return this.http.get(this.url+'/'+state+'/'+city+'.json')
.map(res => res.json() );
}
}
If you're using the new HttpClient you don't need to parse JSON because it's decoded automatically for you:
https://angular.io/guide/http#type-checking-the-response
The HttpClient.get() method parsed the JSON server response into the anonymous Object type. It doesn't know what the shape of that object is.
Also https://angular.io/guide/http#requesting-non-json-data.
With angular 5 and httpClient, you don't need to use the map part anymore.
Read more here: https://angular.io/guide/http#type-checking-the-response
getWeather(city,state){
return this.http.get(this.url+'/'+state+'/'+city+'.json');
}
If you want to get data in specific format, You can tell HttpClient the type of the response to make consuming the output easier and more obvious.
export interface Config {
heroesUrl: string;
textfile: string;
}
And then :
getConfig() {
// now returns an Observable of Config
return this.http.get<Config>(this.configUrl);
}

Loading data from a json file with an Interface

I'm writing a little ionic app for learning purposes and I would like to load data from a json file and assign it to an Interface that describes the data. But I'm struggling with getting it the right way:
import { Component } from "#angular/core";
import { HttpClient} from "#angular/common/http";
export interface PhonebookEntry {
name: string,
telephone: string,
description: string
}
#Component({
selector: 'page-phonebook',
templateUrl: 'phonebook.html'
})
export class PhonebookPage {
entries: Array<PhonebookEntry>;
constructor(public http: HttpClient) {
this.load_entries('assets/json/phonebook.json');
};
load_entries(filePath: string) {
return this.http.get(filePath)
.subscribe(
data => this.entries = data
);
};
}
I think only the line data => this.entries = data is wrong (also the IDE is telling me that), but I don't know to do this right and can't find documentation describing the correct way. If there actually is some I would be glad to know where I can find ressources about this.
subscribe return the response as an object, not as an array. So entries type should be changed.
entries: PhonebookEntry;
In the subscribe, need to assign a type for response data.
load_entries(filePath: string) {
return this.http.get(filePath)
.subscribe(
(data: PhonebookEntry) => this.entries = data // or use any type
);
};
Demo

Accessing JSON in Angular2

I'm having a tough time understanding how to access different aspects of an JSON object in Angular2. Particularly, I have a web API that I built that returns the following JSON object regarding the hard drive details on my server:
The image is a screenshot of my console in Chrome after using an httpService and Observable to push it to the console but understanding how to get to a specific piece of info is getting lost on me.
If someone could point me in the right direction, it would be greatly appreciated.
After having subscribed to the http Observable you have already got the actual object.
Assuming your http get request looks like this:
this.httpService.get(this.apiUrl);
you can use the power of rxjs Observables, for example map over the object like this:
this.httpService.get(this.apiUrl)
.map(res => res.json())
.map(body => body.Data)
.map(data => data[0].AvailableSpace)
which after subscribing to would return the AvailableSpace.
.subscribe(availablespace => console.log(availablespace);
Watch out for accessing arrays like this, this is just to give you an example on how to access and manipulate objects in observables.
Check this site out for more information on different observable
operators, other than map.
https://www.learnrxjs.io/
Let me try my luck. Hope it will help people understand better. Particularly, I will talk about how to perform get request in Angular 2. It is always better to have a get and post request in a separate file called service.ts as mentioned in the official documentation.
We will have three files, namely example.component.ts, example.service.ts and Model file examplemodel.ts
example.component.ts
import {OnInit, Component} from "#angular/core";
import {ExampleService} from "./example.service"; // import service
import {ResponseFromGet, ErrorMessage} from "./examplemodel"; // import your model
#Component({
providers: [ExampleService], // add your service here in order to use in component file
templateUrl: './example.template.html'
})
export class ExampleComponent implements OnInit{
//Specify Url for Get request
Private _getRequestUrl = "http://entergetrequesturlhere";
// Make variable for stroing get method reponse which can be used in ur template file
responseFromGetMethod: ResponseFromGet; // this can be ur model file which represnts ur JSON model
// For storing Error
errorMessage: ErrorMessage;
//use Constructor to inject your service in component file
constructor(private _exampleService: ExampleService){}
// Since we implemented OnInit we need to override its method ngOnInit
// this method is called when page is loaded
ngOnInit(): any{
this.callGetMethod(this._getRequestUrl);
}
// callGetMethod outside OnInit but inside class ExampleComponent
callGetMethod(getUrl: string){
this._exampleService.getMethodName(getUrl)
.subscribe(
responseFromGetMethod => {
this.responseFromGetMethod = responseFromGetMethod; // Store response from getmethod in your local variable
},
error => this.errorMessage = <any>error // Store error message receiver from server
);
}
}
example.service.ts
import {Http, Response} from "#angular/http";
import {Injectable} from "#angular/core";
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
import {ResponseFromGet} from "./examplemodel";
#Injectable()
export class ExampleService{
constructor(private _http: Http) { }
// GET request To access data and specify observable type from model
getMethodName(getUrl): Observable<ResponseFromGet>{
return this._http.get(getUrl)
.map(this.extractData) // to check for the status code
.catch(this.handleError); // to check error
}
// Extracts from response
private extractData(res: Response) {
if (res.status < 200 || res.status >= 300) {
throw new Error('Bad response status: ' + res.status);
}
let response = res.json();
return response || {};
}
// To handle Error
private handleError(error: Response) {
console.error(error);
return Observable.throw(error.json() || 'Server error');
}
}
examplemodel.ts
export interface ResponseFromGet{
id: number;
name: string;
}
export interface ErrorMessage{
message: string;
}
And finally the HTML file
example.template.html
<div>
<h2>{{responseFromGetMethod?.name}}</h2> // this will print the name from the json file
<h3>{{errorMessage?.message}}</h3> // this will print the error if any
</div>
Lastly, this is the model of my JSON file
{
"id": 789;
"name": "Angular2";
}