首页 > 解决方案 > 使用 JavaScript (Ember.js) 将类型 ID 与 TMDb 中的类型名称匹配

问题描述

我相信你们中的很多人都使用过 TMDb(电影数据库)api 来制作电影。但是我在显示每部电影的流派名称时遇到问题。我正在尝试将电影genre_idsapi 中的每个数字替换为它们对应的流派api,因为向用户显示数字并没有说太多!但我没有得到想要的结果。我不确定正确的方法是什么...name

电影适配器

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}`;
  },
});

流派适配器

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}`;
  },
});

电影连载器

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);
  }
});

流派序列化器

import DS from 'ember-data';

export default DS.RESTSerializer.extend({
  normalizeResponse(store, primaryModelClass, payload, id, requestType) {
    payload = { genres: payload.genres };
    return this._super(...arguments);
  }
});

电影模型

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'),
});

流派模型

import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
});

路线

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'),
    });
  },
});

电影列表组件

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;
  }),
});

电影 APIresults数组中的每部电影都有这个结构):

{
  "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"
}

流派 API

"genres":[
  {"id":28,"name":"Action"},
  {"id":12,"name":"Adventure"},
  {"id":16,"name":"Animation"},
  {"id":35,"name":"Comedy"},
  ...
]

Hbs 模板(预期结果)

<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}}

标签: javascriptarraysember.jsthemoviedb-api

解决方案


我认为您的主要问题是您试图在组件层上修复一些在模型层上处理得更好的东西。虽然您可以这样做,但您真正想要的是movie模型与genre模型之间的关系:

genres: hasMany('genre'),

我不确定您的 API 1:1 提供了什么,因为您没有粘贴确切的响应。在某些时候,您提到了一个results数组,而流派似乎被包装在一个genres数组中。因此,如果那不是 100% 正确,您可能需要稍微调整一下这个解决方案。

首先,我会推荐更新的JSONSerializer而不是RESTSerializer.

现在您需要告诉 ember 对于关系,它应该使用数组genres中提供的 id 。genre_ids这可以通过以下方式完成keyForRelationship

import DS from 'ember-data';
import {singularize} from 'ember-inflector';

export default DS.JSONSerializer.extend({
  ...
  keyForRelationship(key, typeClass, method) {
    return `${singularize(key)}_ids`;
  },
});

在这里,我使用ember-inflector来获取关系名称的单数(so genres -> genre),然后添加_ids. 这足以让 ember 识别 id,然后使用它们来提供正确的模型实例。

接下来你基本上可以genres在你的movie模型上循环:

{{#each movie.genres as |genre|}}
  {{genre.name}}
{{/each}}

现在您甚至不需要将所有类型的列表传递给控制器​​/模板。但是,您仍然需要加载它们以便ember-data使用它们。否则ember-data会在您使用它们时尝试单独获取它们。

所以你的model钩子可能看起来像这样:

model() {
  return RSVP.hash({
    genres: this.store.findAll('genre'),
    movies: this.store.findAll('movie'),
  }).then(x => x.movies);
}

这是一个实现 this 的 twiddle。但是,因为我不想实时获取数据,所以我创建了返回静态数据的虚拟适配器。


推荐阅读