I have an API which returns JSON data about football. The data is then passed to the frontend (angular) but when passing it to the array, the array is still remaining undefined.
JSON Data:
match_id":"194200",
"country_id":"41",
"country_name":"England",
"league_id":"148",
"league_name":"Premier League",
"match_date":"2019-04-01",
"match_status":"Finished",
"match_time":"21:00",
"match_hometeam_id":"2617",
"match_hometeam_name":"Arsenal",
"match_hometeam_score":"2 ",
"match_awayteam_name":"Newcastle",
"match_awayteam_id":"2630",
"match_awayteam_score":" 0",
This is the angular code to parse the JSON data and put in the array to display:
export class ResultComponent implements OnInit {
searchFilter: string;
resultArr: FootballModel[];
constructor(private footballService: FootballService, private route:
ActivatedRoute) {}
ngOnInit() {
this.footballService.getResults().subscribe(x => this.resultArr = x);
console.log(this.resultArr);
}
When I console.log the x passed in subscribe, the JSON information is returned. So till the x part it is passing well but when it is passing to resultArray and console.log that part, it is returning undefined. Wonder if anyone can help.
This is the model:
export class FootballModel {
countryName: string;
leagueName: string;
matchDate: string;
matchHomeTeamName: string;
matchAwayTeamName: string;
matchHomeTeamScore: string;
matchAwayTeamScore: string;
}
EDIT:
Also I am trying to display that data in a table, but somehow it is not showing. Pretty sure it's an easy mistake as well.
<tbody>
<tr *ngFor="let result of results">
<td>{{result.countryName}}</td>
<td>{{result.leagueName}}</td>
<td>{{result.matchDate}}</td>
<td>{{result.homeTeam}}</td>
<td>{{result.awayTeam}}</td>
<td>{{result.homeTeamScore}}</td>
<td>{{result.awayTeamScore}}</td>
</tr>
Http requests return Observable on Angular. Observables has async callback function and you can get data by subscribing it as you did. But when you try to reach data outside of callback function before .subscribe worked at least one time it must be undefined. Because it is writing to the console before your API send response. If you change your ngOnInit function like that it must work.
ngOnInit() {
this.footballService.getResults().subscribe(x => {
this.resultArr = x;
console.log(this.resultArr);
});
}
Also check the documentation for Observables
Here is an additional example for this case:
ngOnInit() {
console.log("a");
this.footballService.getResults().subscribe(x => {
console.log("c");
this.resultArr = x;
console.log(this.resultArr);
});
console.log("b");
}
Expected result on console is
"a" "b" "c"
Related
I would like to add and populate additional fields (which are not sent by backend service) in my http model. Catch is that I am not able to populate (map) those fields in the place where http response is being received since I am using internal framework.
Is there a possibility in Typescript (Angular) to somehow override JSON Deserialisation flow/Instance creation and populate mentioned fields. For example:
interface ElectricDevice {
energy_meter_start: number; // received from backend service
energy_meter_stop: number; // received from backend service
energy_spent: number; // not received by backend service, but needs to be populated as energy_meter_stop - energy_meter_start
// ...
/* I would like to somehow populate energy_spent as energy_meter_stop-energy_meter_end on instance creation (deserialisation) */
}
You need a HttpInterceptor, in which you can manipulate data.
#Injectable()
export class CustomJsonInterceptor implements HttpInterceptor {
constructor(private jsonParser: JsonParser) {}
intercept(httpRequest: HttpRequest<any>, next: HttpHandler) {
if (httpRequest.responseType === 'json') {
// If the expected response type is JSON then handle it here.
return this.handleJsonResponse(httpRequest, next);
} else {
return next.handle(httpRequest);
}
}
Read more about it in the tutorials: https://angular.io/api/common/http/HttpInterceptor
I have asked you for the especific names of your services.
But, in the meantime, I give you a 'general' answer to your question.
You just need to do this:
this.yourService.yourGetElectriceDevices
.pipe(
map (_resp: ElectricDevice => _resp.energy_spent = _resp.energy_meter_stop - _resp.energy_meter_start
)
.subscribe( resp => { //your treatment to the response });
This above, only works for a rapid test.
If you want to do somethig more 'elaborated', you could transform your interface into a class, and add your calculated attribute, something like this:
export interface ElectricDevice {
energy_meter_start: number; // received from backend service
energy_meter_stop: number; // received from backend service
}
export Class ElectricDeviceClass {
energy_meter_start: number;
energy_meter_stop: number;
energy_spent: number;
constructor (data: ElectricDevice) {
this.energy_meter_start = data.energy_meter_start;
this.energy_meter_stop= data.energy_meter_stop;
this.energy_spent = this.energy_meter_stop - this.energy_meter_start;
}
And for using it, just:
import { ElectricDeviceClass, ElectricDevice } from './../model/...' // There where you have yours interfaces and model classes
this.yourService.yourGetElectriceDevices
.pipe(
map (_resp: ElectricDevice => new ElectricDeviceClass(_resp)
)
.subscribe( resp => { //your treatment to the response });
I have this sample TypeScript code that is supposed to deserialize a simple JSON into an instance of class Person and then call foo method on it, but it doesn't work:
class Person {
name!: string;
age!: number;
foo() {
console.log("Hey!");
}
}
fetch("/api/data")
.then(response => {
return response.json() as Promise<Person>;
}).then((data) => {
console.log(data);
data.foo();
});
The output of console show that object is in a proper shape, but it is not recognized as Person:
Object { name: "Peter", age: 44 }
age: 44
name: "Peter"
Thus when it tries to call foo method it fails:
Uncaught (in promise) TypeError: data.foo is not a function
http://127.0.0.1:8000/app.js:14
promise callback* http://127.0.0.1:8000/app.js:12
How can I fix it? Should I use Object.assign or there is another better/native solution?
let x = (<any>Object).assign(Object.create(Person.prototype), data);
x.foo();
Remember, TypeScript is just a way of annotating JavaScript code with type guards. It doesn't do anything extra. For example, saying that the object returned by response.json() should be treated as a Promise<Person> does not mean it will invoke the constructor of your Person class. Rather, you'll just be left with a plain old JavaScript object that has a name and an age.
It looks to me like you'll need to create a constructor for your Person class which can create a new instance of a Person based on an object that matches its interface. Something like this, perhaps?
interface PersonLike {
name: string;
age: string;
}
class Person implements PersonLike {
constructor(data: PersonLike) {
this.name = data.name;
this.age = data.age;
}
name: string;
age: string;
foo() {
console.log("Hey!");
}
}
fetch("/api/data")
.then(response => {
return response.json() as Promise<PersonLike>;
}).then((data) => {
const person = new Person(data);
person.foo();
});
I'd also recommend using a type guard instead of the as keyword, in case the API you're fetching data from changes. Something like this, perhaps:
function isPersonLike(data: any): data is PersonLike {
return typeof data?.name === 'string' && data?.age === 'string';
}
fetch("/api/data")
.then(response => {
return response.json();
}).then((data: unknown) => {
if (isPersonLike(data)) {
const person = new Person(data);
person.foo();
}
});
... is supposed to deserialize a simple JSON into an instance of class Person and then ...
Unfortunately, generic type in TypeScript only works as some kind of model design assistant. It will never be compiled into JavaScript file. Take your "fetch" code for example:
fetch("/api/data")
.then(response => {
return response.json() as Promise<Person>;
}).then((data) => {
console.log(data);
data.foo();
});
After compile the above TypeScript file into JavaScript, we can find the code as Promise<Person> is completely removed:
fetch("/api/data")
.then(function (response) {
return response.json();
}).then(function (data) {
console.log(data);
data.foo();
});
To implement "type safe deserialization", you need to save class/prototype information during serialization. Otherwise, these class/prototype information will be lost.
... or there is another better/native solution? ... BTW, what if a class field has a custom type, so it is an instance of another class?
No, there is no native solution, but you can implement "type safe" serialization/deserialization with some libraries.
I've made an npm module named esserializer to solve this problem automatically: save JavaScript class instance values during serialization, in plain JSON format, together with its class name information. Later on, during the deserialization stage (possibly in another process or on another machine), esserializer can recursively deserialize object instance, with all Class/Property/Method information retained, using the same class definition. For your "fetch" code case, it would look like:
// Node.js server side, serialization happens here.
const ESSerializer = require('esserializer');
router.get('/api/data', (req, res) => {
// ...
res.json(ESSerializer.serialize(anInstanceOfPerson));
});
// Client side, deserialization happens here.
const ESSerializer = require('esserializer');
fetch("/api/data")
.then(response => {
return response.text() as Promise<string>;
}).then((data) => {
const person = ESSerializer.deserialize(data, [Person, CustomType1, CustomType2]);
console.log(person);
person.foo();
});
I am using a public API to fetch movie data. And the following is my service method for getting that data from API:
getMovieList(): Observable<Movie[]> {
return this.http.get(this.moviesURL).pipe(
map((data: Movie[]) => data),
catchError(this.handleError),
);
}
And this is the method in the component for subscribing that data:
getMovieList(): void {
this.movieApiService.getMovieList()
.subscribe(
(data: Movie[]) => {
this.movieList = data.results;
}
);
}
The problem is that the API returns an object which has 4 properties: page, results, total_pages, total_results. And I only need the results property. But when I try to assign data.results to my component's property or send data.results from my service method instead of data then angular cli gives an error of "results is an undefined property of data". My question is how do I get the results property directly without having to touch the data object and i also need to assign Movie[] type to the results. But currently I am setting the type to the data object.
The problem lies in your model, you defined that you expect array of Movies but you receive the object with 4 properties which one of them called results are the model you defined, so the solution is:
Define the interface like this:
export interface IDataFromApi {
results: Movie[];
page: number;
total_pages: number;
total_results: number;
}
Then the first function will be:
getMovieList(): Observable<IDataFromApi> {
return this.http.get(this.moviesURL).pipe(
map((data: IDataFromApi) => data),
catchError(this.handleError),
);
And method in component:
getMovieList(): void {
this.movieApiService.getMovieList()
.subscribe(
(data: IDataFromApi) => {
this.movieList = data.results;
}
);
}
I want to get an array from Spring Boot API and I can't convert data into object properly
It's my model:
export class Uczen {
constructor(
public firstname: string,
public lastname: string,
public email: string,
public gender: string,
) { }
}
service:
getPersons() {
return this.http.get('http://localhost:8080/myApp/persons');
}
It's my component.ts code
osoby: Uczen[] = [];
ngOnInit() {
this.personService.getPersons().subscribe((result: Uczen[]) => {
for (const item of result) {
this.osoby.push(item);
}
});
console.log(this.osoby[1]);
console.log(this.osoby.length);
}
im getting "undefined" and "0" as display,there is problem with conversion json to object array prodably ;/
consoles should be inside the subscription since this is an asynchronous procedure
this.personService.getPersons().subscribe((result: Uczen[]) => {
for (const item of result) {
this.osoby.push(item);
}
console.log(this.osoby[1]);
console.log(this.osoby.length);
});
In your ts file, you have ngOnInit() method, ngOnInit is component's life cycle hook which runs when component is being initialized.
1) Initially the control calls the getPersons() method and it will take some time (some milli seconds) to get response from your server.
2)Due to asynchronous behaviour, before we get response from remote server the control goes to the console statement line and it is executed.
At this point of time the variable osoby is still an empty array, which is why you are getting undefined , accessing first element of empty array.
3)If you write the same console lines inside subscription, the control to those lines will go only after receiving the response from your server .
this.personService.getPersons().subscribe((result: Uczen[]) => {
for (const item of result) {
this.osoby.push(item);
}
console.log(this.osoby[1]);
console.log(this.osoby.length);
});
Consider this simple snippet of an AngularJS 2 application:
TestObject
export class TestObject {
id: number;
name: string;
}
TestService
[...]
export class TestService {
constructor(private http: Http) {}
test(): Observable<TestObject> {
return this.http
.get("http://www.example.com")
.map(this.save)
.catch(this.fail);
}
private save(response: Response) {
let testObject: TestObject = <TestObject> response.json();
return testObject || {};
}
private fail(error: any) {
return Observable.throw("error!");
}
}
AppComponent
[...]
export class AppComponent implements OnInit {
testObject: TestObject;
constructor(private testService: testService) {}
ngOnInit() {
this.testService.test().subscribe(
data => {
this.testObject = new TestObject();
console.log(this.testObject); // prints (empty) TestObject
this.testObject = data;
console.log(this.testObject); // prints object, not TestObject?
},
error => { }
);
}
}
Here my questions:
1) Why does my application print out (using Chrome Inspector) object and not TestObject as type?
2) The property testObject of class AppComponent should be of type TestObject. Why does my application not fail?
3) How can I achieve that I really get TestObject? What would be the best way to do it? Of course I could just manually fill up my TestObject, but I hoped there is some way of automatically mapping the json to my object.
Here is an answer that I wrote to a question which explained the handling of observables in angular2.
Angular 2 http post is returning 200 but no response is returned
Here you can see how I am handling the Response object as returned by the service. It is very important that you return your response object from the map function in service.
Similarly you can convert your response object to typescript type by casting your response object. The example can be:
this._loginService.login(this.username, this.password)
.subscribe(
(response) => {
//Here you can map the response to a type.
this.apiResult = <IUser>response.json();
//You cannot log your object here. Here you can only map.
},
(err) => {
//Here you can catch the error
},
() => {
//this is fired after the api requeest is completed.
//here you can log your object.
console.log(this.apiResult);
//result will only be shown here.
}
);
Here, it can be clearly seen that I am casting the response object to IUser type.
Another thing is while handling apiresponse in your component it is to be noted that the subscribe function has three arguments and if you will like to log your object, you must do it in the last function of subscribe.
Hope this helps!
your call must be like
ngOnInit() {
this.testService.test().subscribe(
(data) => {
this.testObject = new TestObject();
console.log(this.testObject); // prints (empty) TestObject
//only mapping
this.testObject = data;
},
error => { },
() => {
console.log(this.testObject);
}
);
}