问题
I'm sure a lot of you have used the TMDb (The Movie Database) api for movies. But I'm having issues with showing the genre names for each movie displayed. I'm trying to replace each number in genre_ids
, from movies api, with their corresponding name
from genres api, as showing numbers to users doesn't say much! But I don't get the desired result. I'm not sure what the correct way is...
Movie adapter
import DS from 'ember-data';
const apiKey = 'SOME_API_KEY_HERE';
export default DS.RESTAdapter.extend({
host: `https://api.themoviedb.org/`,
namespace: '3',
pathForType() {
return `discover/movie?sort_by=popularity.desc&api_key=${apiKey}`;
},
});
Genre adapter
import DS from 'ember-data';
const apiKey = 'SOME_API_KEY_HERE';
export default DS.RESTAdapter.extend({
host: `https://api.themoviedb.org/`,
namespace: '3',
pathForType() {
return `genre/movie/list?api_key=${apiKey}`;
},
});
Movie serializer
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
payload = { movies: payload.results };
return this._super(store, primaryModelClass, payload, id, requestType);
}
});
Genre serializer
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
payload = { genres: payload.genres };
return this._super(...arguments);
}
});
Movie model
import DS from 'ember-data';
const { attr, hasMany } = DS;
export default DS.Model.extend({
vote_count: attr('number'),
video: attr('boolean'),
vote_average: attr('number'),
title: attr('string'),
popularity: attr('number'),
poster_path: attr('string'),
original_language: attr('string'),
original_title: attr('string'),
genre_ids: attr(),
backdrop_path: attr('string'),
adult: attr('boolean'),
overview: attr('string'),
release_date: attr('date'),
});
Genre model
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
});
Route
import Route from '@ember/routing/route';
import RSVP from 'rsvp'
export default Route.extend({
model() {
return RSVP.hash({
movies: this.store.findAll('movie'),
genres: this.store.findAll('genre'),
});
},
});
Movie-listing Component
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
movieGenreIds: computed('movies.@each.genre_ids', function() {
return this.movies.map(movie => movie.genre_ids).reduce((a, b) => [...a, ...b]);
}),
genresNames: computed('movieGenreIds', 'genres', 'movies', function() {
let names = [];
this.genres.map((genre) => {
this.movieGenreIds.forEach(movieGenreId => {
if (parseInt(genre.id) === movieGenreId) {
names.push(genre.name);
}
})
})
return names;
}),
});
Movies API (each movie from the results
array has this structure):
{
"vote_count": 1092,
"id":335983,
"video": false,
"vote_average": 6.7,
"title": "Venom",
"popularity": 505.173,
"poster_path": "\/2uNW4WbgBXL25BAbXGLnLqX71Sw.jpg",
"original_language": "en",
"original_title": "Venom",
"genre_ids": [27,878,28,53,35], // <-- I'm interested in this property
"backdrop_path": "\/VuukZLgaCrho2Ar8Scl9HtV3yD.jpg",
"adult": false,
"overview": "When Eddie Brock acquires the powers of a symbiote, he will have to release his alter-ego “Venom” to save his life.",
"release_date": "2018-10-03"
}
Genres API
"genres":[
{"id":28,"name":"Action"},
{"id":12,"name":"Adventure"},
{"id":16,"name":"Animation"},
{"id":35,"name":"Comedy"},
...
]
Hbs Template (the expected result)
<ul class="movie">
{{#each movies as |movie|}}
<li>
<h2 class="movie__title">{{movie.title}}</h2>
<p class="movie__genre">
genres:
{{#each genresNames as |genre|}}
{{genre}} <!-- a list of genre names for this particular movie -->
{{/each}}
</p>
<img src="https://image.tmdb.org/t/p/w500/{{movie.poster_path}}" alt="" class="movie__image">
</li>
{{/each}}
回答1:
I think your primary problem is that you're trying to fix something on the component layer that is better handled on the model layer.
While you can do that, what you actually want is a relationship from the movie
model to the genre
model:
genres: hasMany('genre'),
I'm not sure what your API provides 1:1 because you've not pasted the exact response. At some point you've mentioned a results
array, and the genres seems to be wrapped inside a genres
array. So if thats not 100% correct you maybe need to tweak this solution a bit.
For the start I would recommend the newer JSONSerializer
instead of the RESTSerializer
.
Now you need to tell ember that for the genres
relationship it should use the ids provided in the genre_ids
array. This can be done by keyForRelationship:
import DS from 'ember-data';
import {singularize} from 'ember-inflector';
export default DS.JSONSerializer.extend({
...
keyForRelationship(key, typeClass, method) {
return `${singularize(key)}_ids`;
},
});
Here I use the ember-inflector to get the singular of the relationship name (so genres -> genre
) and then just add _ids
. This is enough for ember to recognize the ids and then use them to provide the right model instances.
Next you can basically just loop over genres
on your movie
model:
{{#each movie.genres as |genre|}}
{{genre.name}}
{{/each}}
Now you don't even need to pass the list of all genres to the controller/template. However you still need to load them so ember-data
can use them. Otherwise ember-data
would try to fetch them individually when you use them.
So your model
hook could look like this:
model() {
return RSVP.hash({
genres: this.store.findAll('genre'),
movies: this.store.findAll('movie'),
}).then(x => x.movies);
}
Here is a twiddle implementing this. However because I don't wanted to live-fetch the data I've created dummy adapters that return static data.
来源:https://stackoverflow.com/questions/52832701/matching-genre-ids-with-genre-names-in-tmdb-with-javascript-ember-js