Angular Http Call map Api Response - javascript

I'd like to get my trips, which are my response from API in Angular.
From the backend I'm getting:
{
"trips": [
{
"id": 0,
"name": "string",
"startDate": "2019-06-30T06:05:48.006Z",
"endDate": "2019-06-30T06:05:48.006Z",
"description": "string",
"roomSharing": true,
"countries": [
{
"id": 0,
"name": "string",
"code": "string"
}
],
"languages": [
{
"id": 0,
"name": "string",
"code": "string"
}
]
}
]
}
which is fine, but I have a problem on the client side.
Here's my code for getting trips:
getTrips(): Observable<Trip[]> {
return this.http.get<Trip[]>(this.apiUrl + '/Trip/Get')
.pipe(
tap(_ => console.log('fetched trips')),
retry(1),
catchError(this.handleError),
map(data => {
return data;
})
);
}
and in my component I have:
loadTrips() {
return this.rest.getTrips()
.subscribe((data) => {
this.trips = data;
console.log(this.trips);
}
, err => console.log(err));
}
I'd like to get trips in a template like:
<div class="card mb-3 trip" *ngFor="let trip of trips">
but I have to like:
<div class="card mb-3 trip" *ngFor="let trip of trips.trips">
So, the question is how can I map my response to get Trip array instead of Array of Trips array?

Unless I'm misunderstanding something, this should work:
interface TripsResponse {
trips: Trips[],
}
getTrips(): Observable<Trip[]> {
// use your response interface instead
//return this.http.get<Trip[]>(this.apiUrl + '/Trip/Get')
return this.http.get<TripsResponse>(this.apiUrl + '/Trip/Get')
.pipe(
tap(_ => console.log('fetched trips')),
retry(1),
catchError(this.handleError),
map(data => {
return data.trips; // set it properly here
})
);
}

Change your return statement:
return this.http.get('/Trip/Get')
.pipe(
tap(_ => console.log('fetched trips')),
retry(1),
catchError(this.handleError),
map((data: TripsResponse) => { // change made here; make data of type TripsResponse
return data.trips;
})
);
where TripsResponse is
interface TripsResponse {
trips: Trips[],
... // other fields for future if required
}

Dont over complicate by doing .map, just do:
loadTrips() {
return this.rest.getTrips()
.subscribe((data) => {
this.trips = data.trips;
}
, err => console.log(err));
}
Also, correct the model Trip[] which you have created it should be
export interface ITripsResponse {
trips: Trips[],
}
return this.http.get<ITripsResponse>(this.apiUrl + '/Trip/Get')
or else, correct .map by
map((data) => {
return data.trips;
})
and then Observable<Trip[]> would be a valid return type

Related

Getting metadata information from an Observable while executing inside another Observable

I have some song information that I need to add metadata too. I am looking to combine this using one Observable execution (sorry if my terminology is wrong). But I can't get the metadata in the final map
First Attempt:
songsViewData = combineLatest([
this.songs,
this.genre,
]).pipe(
map(([songs, genre]) => {
let query = {
filename: songs[genre],
song_id_range: 50,
filenames_included: true
}
// This doesn't return the inner object. Just an observable
return this.getSongData(query).subscribe((metaData) => {
return (songs[genre]).map((song) => {
return {
id: song,
songTitle: metaData[song].songTitle,
artistName: metaData[song].artistName
}
})
})
})
)
Second Attempt:
songsViewData = combineLatest([
this.songs,
this.genre,
]).pipe(
switchMap(([songs, genre]) => {
let query = {
filename: songs[genre],
song_id_range: 50,
filenames_included: true
}
return this.getSongData(query) // This gets the metadata
}),
map(([songs, genre, metaData]) => { // I want to access the metadata here
return (songs[genre]).map((song) => {
return {
id: song,
songTitle: metaData[song].songTitle,
artistName: metaData[song].artistName
}
})
})
)
You can use a forkJoin in which you wrap both the switchMap and also the other values you need. This only works if this.getSongData completes. Otherwise, take combineLatest instead.
songsViewData = combineLatest([
this.songs,
this.genre,
]).pipe(
switchMap(([songs, genre]) => {
let query = {
filename: songs[genre],
song_id_range: 50,
filenames_included: true
}
return forkJoin([of([songs, genre]), this.getSongData(query)])
}),
map(([[songs, genre], metaData]) => {
return (songs[genre]).map((song) => {
return {
id: song,
songTitle: metaData[song].songTitle,
artistName: metaData[song].artistName
}
})
})
)

React component don't get the whole data

I'm trying to show a page in React that shows a grid of images. I get the data for the grid with a fetch to a file in a public subfolder.
export const GalleryGrid = () => {
const { galleries, loading } = useFetchGalleries()
return (
<div>
{loading && <div className="diving__loading flex-column">
<div><Loader
type="Puff"
color="#264653ff"
height={200}
width={200}
/></div>
<div>
<p>
Please Wait ...
</p>
</div>
</div>}
<div className="card-grid">
<div className="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 row-cols-xl-5">
{
galleries.map(gal => (
<GalleryGridItem
key={gal.url}
{...gal}
/>
))
}
</div>
</div>
</div>
)
}
I use the function "useFetchGAlleries()" to get the data I neew for the grid. This function launch a helper, called "getGalleries", and return de data stored with useState
export const useFetchGalleries = () => {
const [ state, setState] = useState({
galleries: [],
loading: true
});
useEffect(() => {
getGalleries()
.then( galleries => {
setState({
galleries,
loading: false
})
})
.catch( e => {
console.log( e );
});
}, [ ]);
return state;
}
The helper "getGalleries" uses a fetch to read the files that contain objects in json format, and transform them to an object:
export const getGalleries = async () => {
const galleries = await fetch(`./assets/data/galleries.txt`)
.then( res => {
return res.json();
})
.then( body => {
return body.galleries;
})
.catch( e => {
console.log( e );
});
// the .map check if the gallery have images or not
await galleries.map( gal => (
gal.interactive ? fetch(`./assets/data/${gal.url}.txt`)
.then( res => {
return res.json();
})
.then( body => {
gal.images = body.images;
})
.catch( e => {
console.log( e );
})
: ""
));
return galleries ;
}
In "GalleryGrid" I have all information about galleries I need. For example, the first gallery from the array is:
{
"name": "Nuestros Fondos 2022",
"url": "galeria_22_fishes",
"number": 8,
"interactive": true,
"images": [
{"index": '01', "url": 'galeria_03_cursos'},
{"index": '02', "url": 'galeria_03_cursos'},
{"index": '03', "url": 'galeria_03_cursos'},
{"index": '04', "url": 'galeria_03_cursos'},
{"index": '05', "url": 'galeria_03_cursos'},
{"index": '06', "url": 'galeria_03_cursos'},
{"index": '07', "url": 'galeria_03_cursos'},
]
}
But when I give the info to the component "GalleryGridItem" in the "GalleryGrid" function, the data doesn't contain the array of images. The data is as follow:
{
"name": "Nuestros Fondos 2022",
"url": "galeria_22_fishes",
"number": 8,
"interactive": true,
"images": []
}
Any suggestion about what is happening?
The JSON of images is invalid JSON. You need to add quotes to the keys.
You are awaiting an array of promises. You need to use Promise.all()
await Promise.all(galleries.map( gal => (...))
Your function is returning immediately after .map, before any images are loaded.

Using Rxjs map and filter together to select country and states from json

I have a json file that contains both countries and states (as a sample):
{
"countries": [
{
"id": 1,
"name": "United States"
},
{
"id": 2,
"name": "India"
}],
"states": [
{
"id": 1,
"countryId": 1,
"name": "Alabama"
},
{
"id": 2,
"countryId": 1,
"name": "Alaska"
} ] }
Now I have a Service to get the countries and states to later show in my dropdowns:
export class CountriesService {
constructor(private http: HttpClient) { }
public getCountries(): Observable<Country[]> {
return this.http.get<Country[]>("assets/data.json").pipe(map(obj => obj["countries"]));
}
public getStates(countryId: number): Observable<State[]> {
return this.http.get<State[]>("assets/data.json").pipe(
map(res => res["states"]),
map(res => { if (res.countryId === countryId) return res;}));
}
}
The getCountries() works as expected, returning only the Countries but I can't get the specific state based on the countryId from my getStates method.
It returns nothing.
What am I doing wrong ?
You need to filter it later once you get the data.
export class CountriesService {
constructor(private http: HttpClient) { }
public getCountries(): Observable<Country[]> {
return this.http.get<Country[]>("assets/data.json").pipe(
map(obj => obj["countries"]),
);
}
public getStates(countryId: number): Observable<State[]> {
return this.http.get<State[]>("assets/data.json").pipe(
map(res => res["states"]),
withLatestFrom(of(countryId)),
map((id, states) => states.filter(state.countryId === id)),
);
}
}
also you can combine all together to avoid requests for different states because it's still the same data.
export class CountriesService {
constructor(private http: HttpClient) { }
public getCountriesWithStates(): Observable<Country[]> {
return this.http.get<Country[]>("assets/data.json").pipe(
map(data => data.countries.map(country => ({
...country,
states: data.states.filter(state => state.countryId === country.id),
}))),
);
}
}
service.getCountriesWithStates().subscribe(countries => {
// countries[0].states[0];
});
While the result from the API call is an observable, it is "emitting"an observable if an array due to your map function, not an array of observables. So the second map function you have isn't doing what you would expect. Instead, inside your first map, when you get the states, you can use a method that is on the JavaScript array called filter to for the states down to the ones that have the desired country id.
public getStates(countryId: number): Observable<State[]> { return this.http.get<State[]>("assets/data.json").pipe( map(res => res["states"].filter(res => res.countryId === countryId)); }

Parsing JSON from ReactJS application

I am trying to parse a nested json request from my reactjs web app.
Below is the json that I received from a request.
response.data
{
"total": 2,
"offset": 1,
"limit": 987,
"staging": [
{
"id": 101,
"name": "Test Stage"
},
{
"id": 102,
"name": "Dev Stage"
},
{
"id": 103,
"name": "Prod Stage"
}
]
}
I need to parse “staging” and display the results on browser screen.
Below is the code that I am trying to parse. But, it is throwing error (SyntaxError: Unexpected token o in JSON at position 1).
export default class ItemLister extends React.Component {
state = {
persons: []
}
componentDidMount() {
axios
.get('https://xxx.yyy.zzz/xyz/zyx/', {
headers: {
'authorization':'Bearer XXXXXXXXX',
'X-Api-Key': 'XXXXXXXXXXXXXX',
},
withCredentials: true
})
.then(response => {
console.log(response.data) // it gets the correct response and printing in logs
const persons = response.data;
this.setState({ persons });
})
.catch (err => {
console.log("error")
});
}
render() {
return <ul>{this.state.persons.map(person => <li>{person.name}</li>)}</ul>
}
}
ReactDOM.render(<ItemLister />, document.getElementById('root'))
registerServiceWorker()
I couldn't find fix for it. Can someone guide me whether the parsing of such json is correct or not and how to get the parsed results and displayed on screen?
An error occurs because you're trying to parse an Object instead of a String. Simply skip JSON.parse and set result to response.data:
.then(response => {
console.log(response.data) // it gets the correct response and printing in logs
this.setState({ result: response.data });
})
And in you render:
render() {
return (
<ul>
{ this.state.result &&
this.state.result.staging &&
this.state.result.staging.map(person => <li>{person.name}</li>)
}
</ul>
);
}

How to write a recursive flat map in javascript?

I have an object of nested route.
Any route MAY contains a list of route childRoutes.
I want to get the list of all the route that contains the key menu.
const routes = [{
"name": "userManagement",
"childRoutes": [
{
"name": "blogManagement",
"childRoutes": [
{
"name": "blog", // <=== I want to have this route
"menu": {
"role": 1020
}
}
],
},
{
"name": "organizationList", // <=== and this one
"menu": {
"role": 1004
}
}
],
}, {
"name": "test",
"menu": { "role": 4667 }
}];
const deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));
// Should handle nesting of route
const links = deepFlatten(routes).filter((r) => !!r.menu);
console.log('it should have a length of 3:', links.length === 3);
console.log('it should be blog:', links[0].name === 'blog');
console.log('it should be organizationList:', links[1].name === 'organizationList');
console.log('it should be test:', links[2].name === 'test');
The above snippet does not work recursively yet.
How can I do it recursively without any third-party library ?
#yBrodsky's answer can be adapted to isolate and exhibit the generic flatMap operation – here, you'll see that the routes flattened with much of the reduce-map-concat plumbing out of the programmer's way.
// polyfill if you don't have it
Array.prototype.flatMap = function (f)
{
return this.reduce ((acc, x) =>
acc.concat (f (x)), [])
}
// your data
const routes =
[ { name : "userManagement"
, childRoutes :
[ { name : "blogManagement"
, childRoutes :
[ { name : "blog"
, menu : { role : 1020 }
}
]
}
, { name : "organizationList"
, menu : { role : 1004 }
}
]
}
, { name : "test"
, menu : { role : 4667 }
}
]
// flat-mapped routes
const allChildRoutes =
routes.flatMap (function loop (node) {
if (node.childRoutes)
return node.childRoutes.flatMap (loop)
else
return [node]
})
console.log (allChildRoutes)
how about this, seems to work.
const flatten = (routes) => {
return routes.reduce((acc, r) => {
if(r.childRoutes && r.childRoutes.length) {
acc = acc.concat(flatten(r.childRoutes));
} else {
acc.push(r);
}
return acc;
}, [])
}
https://jsfiddle.net/vv9odcxw/

Categories