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);
});
Related
I'm new to web development and as part of a project have made a Django React Redux app. The frontentd has a component with form entry - when a form is submitted, the component calls an action that sends an axios request to a python twitter crawler script in the backend. The script returns a response containing a string array (the tweets are inside the array). I then try to dispatch the array contained in the response.data, as a payload to a reducer. However I keep getting an error saying that the payload of what I have dispatched is undefined.
This is the return statement of the python method that is called - corpus is a string array.
return JsonResponse({"results:": corpus})
This is the code for the action that sends a GET request to the python method. It's then returned the string array inside a json object.
// GET_TWEETS - get list of tweets from hashtag search
export const getTweets = (hashtag) => dispatch => {
axios
.get('http://localhost:8000/twitter_search', {
params: {text: hashtag}
})
.then(res => {
console.log(res.data); //check if python method returns corpus of tweets
//const results = Array.from(res.data.results);
dispatch({
type: GET_TWEET,
payload: res.data.results
});
})
.catch(err => console.log(err));
}
The console log shows that the object is returned successfully from the python script.
console log
This is the code for my reducer. I want my action to contain the string array and then assign that string array to 'tweets' in the state, so that I can return this to a component and then access the array from the component and then display contents of thisx array of tweets on the frontend
import { GET_TWEET } from '../actions/types';
const initialState = {
tweets: []
}
export default function(state = initialState, action) {
console.log(action.payload)
switch(action.type) {
case GET_TWEET:
return {
...state,
tweets: [...action.payload]
}
default:
return state;
}
}
Also, this is some of the code for the component that I want to receive the string array, I hope I have set this part up properly:
export class Tweets extends Component {
static propTypes = {
tweets: PropTypes.array.isRequired,
getTweets: PropTypes.func.isRequired
}
...
const mapStateToProps = state => ({
tweets: state.TweetsReducer.tweets
});
export default connect(mapStateToProps, { getTweets })(Tweets);
Here is the error I get from the console. Console logging the payload of the action also shows that its value is undefined: undefined error
I've been trying to solve this for several days now, but am completely lost and I have a hunch the solution to this is pretty simple...
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 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"
I suppose that the answer will be very obvious, but still it evades me. I'm new on working with observables, and now I'm facing issues assigning a value from one. I had success if I define it (this._apps) as an Observable and asking from the view to the service using subscribe (But for my taste is was way convoluted (three levels inside a map just to return another observable with the array and then another function to subscribe the previous to assign the variable and another subscription in the view to finally show the information), inefficient and on top of that I could not get it "right" again). The task is very simple. Given the class Application
export class Application {
name: string;
baseUrl: string;
deprecated: boolean;
}
And the service (just the relevant code)
private _apps: Application[] = [];
constructor(private _http: HttpClient) {
this.getAllApplications().subscribe(apps => {
console.log('Apps subscriber');
this._apps = apps;
console.log('Apps subscriber Ends ' + apps);
},
err => {
console.log(err.status); // 401
console.log(err.error.error); // undefined
console.log(JSON.parse(err.error).error); // unauthorized
});
}
private getAllApplications() {
return this._http.get<Application[]>('http://development:4300/api/v1/apps');
}
From the constructor the function which gets the information from WebAPI is triggered, and the remote call is successful, but the variable this._apps is an empty array if I try to call it from anywhere in the code. I could not determine the type of the parameter "apps" in the subscribe function, but for some reason it cannot be assigned and the best answer given is that it is a function (See my first update) in one of my tries. Currently it returns in the console "[object Object]", but apps[0] gives undefined, so it is an empty Array.
This is the console output, just starting the application:
Angular is running in the development mode. Call enableProdMode() to enable the production mode.
Refreshing apps cache calling http://development:4300/api/v1/atbc-apps
Apps subscriber
Apps subscriber Ends [object Object]
I was trying this solution among many others that I forget (to use the more modern HttpClient instead the Http I used before), so what I'm doing wrong?
Update 1
I changed the constructor to this:
constructor(private _http: HttpClient) {
this.getAllApplications().subscribe(apps => {
console.log('apps length ' + apps.length);
this._apps = apps; // Remember private _apps: Application[] = [];
console.log('Apps subscriber Ends ' + apps.toString);
},
err => {
console.log(err.status); // 401
console.log(err.error.error); // undefined
console.log(JSON.parse(err.error).error); // unauthorized
});
}
and the declaration of the function called into this:
private getAllApplications(): Observable<Application[]> {
// the exactly the same as before
}
And now I got from the console this:
apps length undefined
Apps subscriber Ends
function () {
if (this instanceof Promise) {
return PROMISE_OBJECT_TO_STRING;
}
return originalObjectToString.apply(this, arguments);
}
That is the function I was talking about. Any ideas about why even though there is no errors (nor at compile time, neither at runtime), the returning object is not a real Application array?
Change this line:
private _apps: Application[] = [];
to:
_apps: Application[] = [];
Which will default to making it public. Then this line will see it:
this._apps = apps;
At the end I suppose is a mindset to work with Observables, and I tried to build a kind of cache, so the only way I could do it (let me know if there is a better way) was using the view to fill-out the cache. I could not do it from the service itself because the calling the function from the view is synchronous and to fill out the array is async. So I had to create a public setApplicationCache procedure which is filled out after calling the service from the view, it call the setApplicationCache( Application[] ) function and the rest works because it takes just the cache to do filtering and other operations or use it from other pages w/o calling the database again and again.
This is the code from the first view called (main page)
ngOnInit() {
this._myService.getAllApplications().subscribe(arrObjApps => {
this._myService.setApplicationsCache(arrObjApps)
this.listApps = this._myService.getApplications(true);
});
And the service has this functions:
private _apps: Application[] = [];
getAllApplications(): Observable<Application[]> {
return this._http.get('http://development:4300/api/v1/atbc-apps').pipe(
map( (response: Response) => {
let results = response.json().data.map( app => {
return new Application(app.name, app.baseUrl, app.deprecated);
});
return results;
})
);
}
getApplication(appName: string): Application {
return this._apps.find(app => app.name == appName);
}
getApplications(onlyActives: boolean): Application[] {
if (onlyActives) {
return this._apps.filter(app => app.deprecated == false);
} else {
return this._apps;
}
}
And as I stated the solution should be obvious. Just again the async mindset required to work with observables.
So I'm getting the following JSON structure from my asp.net core api:
{
"contentType": null,
"serializerSettings": null,
"statusCode": null,
"value": {
"productName": "Test",
"shortDescription": "Test 123",
"imageUri": "https://bla.com/bla",
"productCode": null,
"continuationToken": null
}
}
I have the following typescript function that invokes the API to get the above response:
public externalProduct: ProductVM;
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map((data: ProductVM) => {
this.externalProduct = data; //not working...
console.log("DATA: " + data);
console.log("DATA: " + data['value']);
return data;
});
}
ProductVM:
export interface ProductVM {
productName: string;
shortDescription: string;
imageUri: string;
productCode: string;
continuationToken: string;
}
My problem is that I can't deserialize it to ProductVM. The console logs just produce [object Object]
How can I actually map the contents of the value in my json response to a ProductVM object?
Is it wrong to say that data is a ProductVM in the map function? I have tried lots of different combinations but I cannot get it to work!
I'm unsure whether I can somehow automatically tell angular to map the value array in the json response to a ProductVM object or if I should provide a constructor to the ProductVM class (it's an interface right now), and extract the specific values in the json manually?
The data object in the map method chained to http is considered a Object typed object. This type does not have the value member that you need to access and therefore, the type checker is not happy with it.
Objects that are typed (that are not any) can only be assigned to untyped objects or objects of the exact same type. Here, your data is of type Object and cannot be assigned to another object of type ProductVM.
One solution to bypass type checking is to cast your data object to a any untyped object. This will allow access to any method or member just like plain old Javascript.
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map((data: any) => this.externalProduct = data.value);
}
Another solution is to change your API so that data can deliver its content with data.json(). That way, you won't have to bypass type checking since the json() method returns an untyped value.
Be carefull though as your any object wil not have methods of the ProductVM if you ever add them in the future. You will need to manually create an instance with new ProductVM() and Object.assign on it to gain access to the methods.
From angular documentation: Typechecking http response
You have to set the type of returned data when using new httpClient ( since angular 4.3 ) => this.http.get<ProductVM>(...
public externalProduct: ProductVM;
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get<ProductVM>("api/product?productCode=" + code)
.map((data: ProductVM) => {
this.externalProduct = data; // should be allowed by typescript now
return data;
});
}
thus typescript should leave you in peace
Have you tried to replace
this.externalProduct = data;
with
this.externalProduct = data.json();
Hope it helps
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map(data => {
this.externalProduct = <ProductVM>data;
console.log("DATA: " + this.externalProduct);
return data;
});
}
So, first we convert the response into a JSON.
I store it into response just to make it cleaner. Then, we have to navigate to value, because in your data value is the object that corresponds to ProductVM.
I would do it like this though:
Service
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get(`api/product?productCode=${code}`)
.map(data => <ProductVM>data)
.catch((error: any) => Observable.throw(error.json().error || 'Server error'));
}
Component
this.subscription = this.myService.getProductExternal(code).subscribe(
product => this.externalProduct = product,
error => console.warn(error)
);
I used this approach in a client which uses the method
HttpClient.get<GENERIC>(...).
Now it is working. Anyway, I do not understand, why I do not receive a type of T back from the http client, if I don't use the solution provided in the answer above.
Here is the client:
// get
get<T>(url: string, params?: [{key: string, value: string}]): Observable<T> {
var requestParams = new HttpParams()
if (params != undefined) {
for (var kvp of params) {
params.push(kvp);
}
}
return this.httpClient.get<T>(url, {
observe: 'body',
headers: this.authHeaders,
params: requestParams
}).pipe(
map(
res => <T>res
)
);
}