Это приложение сделано с помощью угловой (версия 12.2.8). Это приложение рендеринга на стороне сервера, которое использует node.js
и express
, и он ищет названия фильмов. Это приложение для одной страницы размещено бесплатно на Heroku (платформа облачных приложений). Вам понадобится создать бесплатную учетную запись с themoviedb.org для участия в этом уроке. Инструкции о том, как закупить ключ API TMDB, приведены ниже.
Автоматически установлен локально с помощью Angular CLI
ng new movie-search --skip-git
Would you like to add Angular routing? Yes
Which stylesheet format would you like to use? SCSS
Преимущество того, чтобы сделать это приложение на стороне сервера, состоит в том, что приложение может отображаться на экране (пользовательский интерфейс) быстрее, чем обычное приложение. Это дает пользователям возможность просмотреть макет приложения, прежде чем он станет полностью интерактивным. Это также может помочь с SEO.
ng add @nguniversal/express-engine
server.ts
для обслуживания и обработки данных для этого приложения."serve:ssr": "node dist/server/main.js",
package.json
line 12
angular.json
изменить line 20
на "outputPath": "dist/browser",
и line 129
на "outputPath": "dist/server",
server.ts
изменить line 14
на const distFolder = join(process.cwd(), 'dist/browser');
npm run build:ssr
npm run serve:ssr
ng gc modules/home --module=app.module.ts
homeComponent
по умолчанию. //app-routing.module.ts
import { HomeComponent } from './modules/home/home.component' ;
const routes : Routes = [
{
path : '' ,
component : HomeComponent
} ,
{
path : '**' ,
component : HomeComponent
}
] ;
app.component.html
Placeholder HTML -контент на это ниже. <!-- app.component.html -->
< div class =" container " >
< router-outlet > </ router-outlet >
</ div >
Для этого приложения нам нужно будет добавить HttpClinetModule
, ReactiveFormsModule
и FormsModule
в файл app.module.ts
и добавить его в imports
. Это сделает эти модули доступными через все приложение. Модуль HTTP позволит нам совершать вызовы на сервер. ReactiveFormsModule
поможет нам использовать FormControl
на HTML -входе, и когда значение изменения значения (текст при вводе поиска) запрос API будет отправлен на сервер.
//app.module.ts
import { HttpClientModule } from "@angular/common/http" ;
import { ReactiveFormsModule , FormsModule } from "@angular/forms" ;
FormControl
в home.component.html
.async Observable
и function
для стрельбы. <!-- home.component.html -->
< div class =" row " >
< input type =" search " class =" form-control " placeholder =" search " [formControl] =" searchField " >
< span class =" search-title " > {{ (results | async)?.length ? 'Results' : 'Search' }} </ span >
</ div >
//home.component.ts
import { Component , OnInit } from '@angular/core' ;
import { FormControl } from "@angular/forms" ;
import { Observable } from 'rxjs' ;
import {
debounceTime ,
distinctUntilChanged ,
tap ,
switchMap
} from 'rxjs/operators' ;
import { DataService } from '../../services/data.service' ;
@ Component ( {
selector : 'app-home' ,
templateUrl : './home.component.html' ,
styleUrls : [ './home.component.scss' ]
} )
export class HomeComponent implements OnInit {
public loading : boolean = false ;
public results : Observable < any > ;
public searchField : FormControl ;
constructor ( private dataService : DataService ) { }
ngOnInit ( ) : void {
this . searchField = new FormControl ( ) ;
this . results = this . searchField . valueChanges . pipe (
debounceTime ( 400 ) ,
distinctUntilChanged ( ) ,
tap ( _ => {
this . loading = true ;
} ) ,
switchMap ( term => this . dataService . search ( term ) ) ,
tap ( _ => ( this . loading = false ) )
) ;
}
}
Angular cli
в терминале: ng gs services/data
server.ts
. //data.service.ts
import { Injectable } from '@angular/core' ;
import { HttpClient , HttpHeaders } from '@angular/common/http' ;
import { Observable } from 'rxjs' ;
const headers = new HttpHeaders ( ) . set ( 'Content-Type' , 'application/X-www-form-urlencoded' ) ;
@ Injectable ( {
providedIn : 'root'
} )
export class DataService {
public result : any ;
constructor ( private http : HttpClient ) { }
search ( item : string ) : Observable < any > {
let searchterm = `query= ${ item } ` ;
try {
this . result = this . http . post ( '/search' , searchterm , { headers } ) ;
return this . result ;
} catch ( e ) {
console . log ( e , 'error' )
}
}
}
В server.ts
создайте функцию для обработки запроса от клиента. Затем отправьте запрос в конечную точку TMDB. https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&language=en-US&page=1&include_adult=false&query=${term}
//server.ts line 35-40
server . post ( '/search' , async ( req , res ) => {
let searchquery = req . body . query ;
let encsearchquery = encodeURIComponent ( searchquery ) ;
const data = await api . data . search ( encsearchquery , apiKey ) ;
res . status ( 200 ) . json ( data ) ;
} )
api
и файл api.ts
mkdir api
, затем cd api
затем touch api.ts
, чтобы настроить каталог API.api
в файл server.ts
. import { api } from './api/api'
. В будущем, если вы захотите добавить различные запросы в TMDB api
вы можете добавить их в api.ts
чтобы сохранить файл server.ts
менее загроможден.
//api.ts
let request = require ( 'request' ) ;
let methods : any = { } ;
let searchInfo = [ ] ;
methods . search = async ( term : string , apiKey : string ) => {
let searchQuery = `https://api.themoviedb.org/3/search/movie?api_key= ${ apiKey } &language=en-US&page=1&include_adult=false&query= ${ term } ` ;
let searchPromise = new Promise ( ( resolve , reject ) => {
request ( searchQuery , { } , function ( err , res , body ) {
let data = JSON . parse ( body ) ;
searchInfo = data [ 'results' ] ;
resolve ( ) ;
} ) ;
} ) ;
let result = await searchPromise ;
return searchInfo ;
}
export const api = { data : methods } ;
Я использую библиотеку request
для обработки запроса API и проанализировать ответ. В TypeScript я могу использовать обещания, чтобы дождаться ответа, чтобы быть готовым избежать ошибки.
settings
.API
и отправьте данные вашего приложения, чтобы получить ключ API.Application Name: Movie Search
Application URL: localhost:4000
Application Summary: an app that will search for movies that are related to the search term entered into the app input and display them in the ui.
server.ts
в вашем приложении.WARNING: Do not commit your api key to github. If you do it could be found and used by another party.
//server.ts
import 'zone.js/dist/zone-node' ;
import { ngExpressEngine } from '@nguniversal/express-engine' ;
import * as express from 'express' ;
import { join } from 'path' ;
import { enableProdMode } from '@angular/core' ;
import { AppServerModule } from './src/main.server' ;
import { APP_BASE_HREF } from '@angular/common' ;
import { existsSync } from 'fs' ;
import { api } from './api/api' ;
const bodyParser = require ( 'body-parser' ) ;
enableProdMode ( ) ;
// The Express app is exported so that it can be used by serverless Functions.
export function app ( ) : express . Express {
const server = express ( ) ;
const distFolder = join ( process . cwd ( ) , 'dist/browser' ) ;
const indexHtml = existsSync ( join ( distFolder , 'index.original.html' ) ) ? 'index.original.html' : 'index' ;
const apiKey = 'TMDB api key' ;
server . use ( bodyParser . urlencoded ( { extended : true } ) ) ;
...
Когда мы возвращаем ответ данных из конечной точки TMDB, он отправляется обратно клиенту (фронт). home.component.html
необходимо настроить для отображения async Observable
.
<!-- home.component.html -->
< div class =" center-header " >
< h1 > Movie Search </ h1 >
< i > * This product uses the TMDb API but is not endorsed or certified by TMDb. </ i >
</ div >
< div class =" row " >
< input type =" search " class =" form-control " placeholder =" search " [formControl] =" searchField " >
< span class =" search-title " > {{ (results | async)?.length ? 'Results' : 'Search' }} </ span >
</ div >
< div class =" row wrapper " >
< div class =" no-res " *ngIf =" (results | async)?.length === 0 " > Nothing matches this search. </ div >
< div [ngClass] =" {'dn': item?.poster_path == null} " class =" col " *ngFor =" let item of results | async " >
< span class =" item " >
< span class =" bg " [ngStyle] =" {'background': 'linear-gradient(-225deg, rgba(0,0,0,0.5) 50%, rgba(0,0,0,0.5) 80%), url(https://image.tmdb.org/t/p/w440_and_h660_face'+ item?.poster_path +')' } " >
</ span >
</ span >
</ div >
</ div >
В этом пользовательском интерфейсе есть несколько вещей. Я использую тройную проводимость внутри углового интерполяционного кронштейна, чтобы отобразить текст «Поиск» или «Результаты», раздаваясь, если есть данные.
{{ (results | async)?.length ? 'Results' : 'Search' }}
Я использую директиву [ngClass]
, которая находится в стороне от угловой структуры. Я добавляю класс dn
, если poster_path
Data null
, а затем в styles.scss
добавить .dn {display: none;}
, чтобы избежать пустых элементов фильма. Я также использую директиву [ngStyle]
, чтобы динамически добавить фоновое изображение изображения плаката каждого фильма фильма.
Я добавил несколько основных стилей CSS, чтобы показать результаты фильма в макете колонки Flex Row. Это также будет обрабатывать меньшие мобильные экраны. С помощью файла scss
вы можете написать вложенные CSS, как показано ниже. Полный файл SCSS для этого приложения поиска фильма
// styles.scss
html ,
body {
height : 100 % ;
font-family : Arial , sans-serif ;
margin : 0 ;
background-color : #303030 ;
}
.container {
color : #fff ;
min-height : 100 % ;
margin-bottom : -50 px ;
margin : 0 auto ;
max-width : 1380 px ;
.row {
display : flex ;
flex-direction : row ;
flex-wrap : wrap ;
width : 100 % ;
justify-content : center ;
.col {
display : flex ;
flex-direction : column ;
flex : 0 0 13 % ;
width : 100 % ;
color : #fff ;
margin-bottom : 5 px ;
position : relative ;
.item {
.bg {
background-size : cover !important ;
background-repeat : no-repeat !important ;
background-position : center !important ;
position : relative ;
height : 250 px ;
display : block ;
}
}
}
.col.dn {
display : none ;
}
}
.row.wrapper {
max-width : 1200 px ;
margin : 0 auto
}
}
@media ( max-width : 900 px ) {
.container .row .col {
flex : 0 0 20 % ;
}
}
@media ( max-width : 500 px ) {
.container .row .col {
flex : 0 0 33 % ;
}
}
Если вы хотите разместить это приложение бесплатно, посетите его в любое время, когда вы хотите, и поделитесь им с другими, затем выполните следующие шаги.
package.json
для Herokuline 6
Добавить "start:heroku": "node dist/server/main.js",
line 7
Добавьте "heroku-postbuild": "npm run build:ssr"
Procfile
в корень этого приложения.touch Procfile
Добавить эту строку web: npm run start:heroku
в файл.server.ts
process.env.TOKEN
.line 20
добавить const apiKey = process.env.TOKEN;
git commit -am "make a commit."
Затем git push
Heroku CLI
входит в Heroku от Terminal Run: heroku login
.heroku create angular-movie-search
и git push heroku master
.key: TOKEN
и value: TMDB api key
.Если название приложения Heroku, которое вы создали, воспринимается уникальное имя, которое доступно. Я добавлю часть 2 для этой паутины, чтобы мы могли показать еще несколько данных о фильмах и сделать страницу интерактивной, загрузив трейлеры фильмов. Спасибо за чтение. Полный исходный код
angular cli
.Давайте покажем некоторую информацию о фильме, когда мы будем зависать над изображением фильма. Поисковая полезная нагрузка содержит оценку рейтинга фильмов 0 - 10. Рейтинг (hoge_average) может быть преобразован в массив, чтобы показать рейтинг в виде звездных значков, равных длине массива.
payload
- это данные, отправленные обратно после того, как вы сделаете запрос на поиск в api
. Вы получите максимум 20 результатов за ответ. Чем меньше полезная нагрузка, тем быстрее данные отображаются в пользовательском интерфейсе. Это пример первого объекта Oserverable, отправленного обратно API.
{
"popularity" : 24.087 ,
"id" : 670466 ,
"video" : false ,
"vote_count" : 29 ,
"vote_average" : 6.8 ,
"title" : " My Valentine " ,
"release_date" : " 2020-02-07 " ,
"original_language" : " en " ,
"original_title" : " My Valentine " ,
"genre_ids" :[ 53 , 27 ],
"backdrop_path" : " /jNN5s79gjy4D3sJNxjQvymXPs9d.jpg " ,
"adult" : false ,
"overview" : " A pop singer's artistic identity is stolen by her ex-boyfriend/manager and shamelessly pasted onto his new girlfriend/protégé. Locked together late one night in a concert venue, the three reconcile emotional abuses of the past . . . until things turn violent. " , "poster_path" : " /mkRShxUNjeC8wzhUEJoFUUZ6gS8.jpg "
}
В первой части этого урока я использовал poster_path
чтобы показать изображение фильма, и теперь я могу использовать vote_average
, чтобы показать рейтинг фильма. Я создал функцию внутри контроллера компонента, чтобы преобразовать рейтинг в массив, который затем может представлять значение vote_average
, округленного до целого числа, и использовать золотые звезды, чтобы представлять рейтинг, когда я нахожусь на изображении фильма.
< span class =" star-rating " *ngFor =" let star of rating(item) " >
< span > ☆ </ span >
</ span >
//home.component.ts line 53
public rating ( movie ) {
let rating = Array ( Math . round ( movie . vote_average ) ) . fill ( 0 ) ;
movie . rating = rating ;
return rating ;
}
Затем приготовьте контент для возвращенного значения рейтинга, чтобы мы видели звезды только на парящих. Полный файл SCSS для диалогового компонента
// components/dialog/dialog.component.scss
.item .bg .info {
background-color : rgba ( 0 , 0 , 0 , 0.0 );
position : absolute ;
bottom : 0 ;
color : #fff ;
font-size : 9 px ;
text-transform : uppercase ;
width : 100 % ;
transition : linear .3 s ;
p {
opacity : 0 ;
transition : linear .3 s ;
}
.star-rating {
color : transparent ;
span :before {
content : " 2605 " ;
position : absolute ;
color : gold ;
}
}
}
.item .bg .item :hover {
.info {
background-color : rgba ( 0 , 0 , 0 , 0.5 );
p {
color : #fff ;
opacity : 1 ;
font-size : 14 px ;
}
}
}
Затем я собираюсь добавить ссылку, которая отправит еще один запрос API для трейлера фильма (предварительный просмотр), а затем откроет диалог (всплывающее окно), чтобы отобразить трейлер с обзором (описание) фильма.
Когда я получаю информацию о испытании фильма из API, я хочу встроить ссылку на медиа в HTML <iframe>
. Я хочу добавить ссылку «трейлер», которая откроет окно, чтобы показать трейлер при нажатии. Используя angular cli
я буду генерировать новый диалоговый компонент. В типе терминала ng gc components/dialog --module=app.module.ts
. Эта команда добавит компонент в app.module.ts
автоматически.
Чтобы создать всплывающее окно с нуля, мне нужно использовать небольшие CSS и несколько угловых трюков, чтобы помочь мне добавить специальный class
, когда нажимает ссылка «трейлер». dialog component
использует boolean
чтобы добавить active
класс в div
чтобы показать темный фон наложения с всплывающим окном, расположенным в центре наложения. Использование угловой директивы [ngClass]
если isOpen
boolean
является true
добавьте active
класс к id
наложения. <div id="overlay" [ngClass]="{'active': isOpen}">
это позволяет мне скрывать div
, пока он не будет active
, когда я щелкну по ссылке трейлера и сделаю isOpen
равным True. Все, что мне нужно сделать, это добавить несколько Inputs
в компонент app-dialog
.
<!-- home.component.html line 1 -->
< app-dialog
[isOpen] =" isOpen "
[selectedMovie] =" selectedMovie "
[trailerUrl] =" trailerUrl "
(close) =" isOpen = false " > </ app-dialog >
//dialog.component.ts
import { Component , Input , Output , EventEmitter } from '@angular/core' ;
@ Component ( {
selector : 'app-dialog' ,
templateUrl : './dialog.component.html' ,
styleUrls : [ './dialog.component.scss' ]
} )
export class DialogComponent {
@ Input ( 'selectedMovie' )
public selectedMovie : any ;
@ Input ( 'trailerUrl' )
public trailerUrl : any ;
@ Input ( 'isOpen' )
public isOpen : boolean ;
@ Output ( ) close = new EventEmitter ( ) ;
constructor ( ) { }
public closeModal ( ) {
this . isOpen = false ;
this . selectedMovie = null ;
this . trailerUrl = null ;
this . close . emit ( ) ;
}
}
Я использую Input
для ввода данных из home.component
на щелчке, вызывая function
, и я использую Output
для emit
function
, когда диалог закрыт. Диалог может быть закрыт, нажав на X в правом верхнем углу или нажав на наложение. Функция closeModal()
удалит active
класс, сбросит все значения и испускает функцию для сброса isOpen
в home.component
.
<!-- dialog.component.html -->
< div id =" overlay " [ngClass] =" {'active': isOpen} " (click) =" closeModal() " >
< div class =" modal " (click) =" $event.stopPropagation() " >
< div class =" modal-header " >
< span class =" header-title " *ngIf =" selectedMovie != null " >
{{selectedMovie?.title ? selectedMovie?.title : selectedMovie?.name}}
({{selectedMovie?.release_date ? (selectedMovie?.release_date | date: 'y') : selectedMovie?.first_air_date | date: 'y'}})
</ span >
< span class =" right " (click) =" closeModal() " > X </ span >
</ div >
< div class =" content " >
< div id =" top " class =" row " >
< div *ngIf =" trailerUrl != null " class =" col-trailer " >
< iframe [src] =" trailerUrl " width =" 560 " height =" 315 " frameborder =" 0 " allowfullscreen > </ iframe >
</ div >
< div class =" col-overview " >
< span class =" star-rating " *ngFor =" let star of selectedMovie?.rating " >
< span > ☆ </ span >
</ span >
< span >
{{selectedMovie?.rating?.length}}/10
</ span >
< br >
< hr >
< span > {{selectedMovie?.overview}} </ span > < br >
</ div >
</ div >
</ div >
</ div >
</ div >
trailerUrl
YouTube и отображайте в <iframe>
. selectedMovie
передается из home.component
, когда ссылка на фильм нажимается, но до открытия диалога мне нужно получить трейлер фильма из API. Я добавил новый вызов API в файл api.ts
//home.component.ts
//line 17
public isOpen: boolean = false ;
public selectedMovie: any ;
public trailerUrl: any ;
//line 37
public openTrailer ( movie ) {
this . selectedMovie = movie ;
this . dataService . trailer ( movie . id ) . subscribe ( res => {
if ( res [ 0 ] != null ) {
if ( res [ 0 ] . site === 'YouTube' ) {
this . trailerUrl = this . sanitizer . bypassSecurityTrustResourceUrl (
`https://www.youtube.com/embed/ ${ res [ 0 ] . key } `
) ;
this . isOpen = true ;
}
}
} )
}
Отсюда data.service
будет работать в качестве среднего менеджера для общения с server / api
и отправить ответ обратно клиенту (фронт). Первым индексом в ответе с USALLY является ссылка на YouTube, где проживают почти все трейлеры фильмов, поэтому я использую условие для конкретного использования только трейлеров YouTube и, если не открывать трейлер. Для развлечения вы можете добавить в это состояние, если вы хотите позволить трейлеру открыть из другого источника видео.
//data.service.ts line 24
trailer ( item ) {
let searchterm = `query= ${ item } ` ;
try {
this . result = this . http . post ( '/trailer' , searchterm , { headers } ) ;
return this . result ;
} catch ( e ) {
console . log ( e , 'error' )
}
}
Я использую try catch
, чтобы обработать ошибку, но есть много способов обработки ошибки в angular
. Это было только для простоты с моей стороны.
//server.ts line 42
server . post ( '/trailer' , async ( req , res ) => {
let searchquery = req . body . query ;
let encsearchquery = encodeURIComponent ( searchquery ) ;
const data = await api . data . trailer ( encsearchquery , apiKey ) ;
res . status ( 200 ) . json ( data ) ;
} )
Я использую async function
TypeScript, которая будет await
для API, чтобы дать нам полезную нагрузку (ответ) перед завершением post
, чтобы избежать ошибки сервера.
//api/api.ts
//line 4
let trailerInfo = [ ] ;
//line 19
methods . trailer = async ( id : string , apiKey : string ) => {
let apiUrl = `https://api.themoviedb.org/3/movie/ ${ id } /videos?api_key= ${ apiKey } &language=en-US` ;
let trailerPromise = new Promise ( ( resolve , reject ) => {
request ( apiUrl , { } , function ( err , res , body ) {
let data = JSON . parse ( body ) ;
trailerInfo = data [ 'results' ] ;
resolve ( ) ;
} ) ;
} ) ;
let result = await trailerPromise ;
return trailerInfo ;
} ;
Я использую эту конечную точку tmdb https://api.themoviedb.org/3/movie/${id}/videos?api_key=${apiKey}&language=en-US
api_key=,Pikey?&language=en-us, чтобы получить трейлер фильма от id
фильма. id
и apikey
передаются в конечную точку с использованием кронштейнов TypeScript и обратных и бэк -обработке, что является новым способом добавления динамических значений с JS, и он выглядит намного приятнее, чем использование +
для объединения значений.
Если данные соответствуют условию YouTube, открывается всплывающее окно Diaglog, и данные будут отображаться внутри HTML и angular
интерполированных строк {{selectedMovie.title}}
, двойные скобки обрабатывают данные в HTML динамически.
Что -то, о чем не всегда говорится с такими проектами, что не займет много времени, чтобы преобразовать это в совершенно другой проект. Вы можете легко изменить конечные точки в файле api.ts
, чтобы общаться с другим API и получить разные данные, чтобы показать в пользовательском интерфейсе. Конечно, вам нужно будет изменить некоторые из соглашений об именовании переменных, чтобы код имел смысл, но этот проект может быть переработан с чем -то еще, что вас может заинтересовать. Смотрите его как шаблон, уже настроенный с простым бэкэнд -сервером и Файл API для обработки любых данных, которые вы хотели бы получить, и отправить обратно на фронт для отображения. Измените заголовок заголовка в home.html
на что -то вроде Job Search
и подключитесь к API списка заданий, который может получить задания, например, по ключевым словам. Как только вы начнете, что -нибудь возможно. Спасибо за кодирование со мной. Удачи. Полный исходный код
Примечание: я только что обнаружил, что прямо в эту минуту есть tag
html5
<dialog open>This is an open dialog window</dialog>
но он не сработал для меня в Chrome. Это может быть слишком новым и не хватает поддержки браузеров, но, возможно, вы, творческие разработки, сможете найти способ использовать это вместо моего подхода «делать это с нуля».
API TMDB недавно добавил новую конечную точку к своему API, которая расскажет вам, где транслируется фильм, используя идентификатор фильма. Я также добавил еще одну конечную точку, чтобы получить актерский состав фильмов в поисковой полезной нагрузке. Я обновил файл api.ts
для обработки этих новых запросов.
После получения результатов поиска я использую метод rxjs forkJoin
и map
чтобы сделать несколько запросов для providers
для каждого фильма по id
. Данные поставщика pushed
в массив mvProvider
, а затем добавляются в объект searchInfo
, который затем отправляется обратно клиенту с массивом результатов фильмов, массивом поставщиков и массивам кредитов.
В зависимости от суммы результатов поиска, один запрос на поиск может генерировать до 30 запросов API до того, как данные будут разрешены и отправлены клиенту. Я создал функцию sleep
, чтобы await
всех запросов, чтобы закончить, прежде чем resolve
отправит данные imcomplete обратно на передний конец. Это не лучший способ справиться с этим, но это работает. Использование async
and await
важно, чтобы убедиться, что все полученные данные завершаются до того, как promise
будет resolved
.
//api.ts
let request = require ( 'request' )
let methods : any = { }
let searchInfo = [
{
results : [ ] ,
providers : [ ] ,
credits : [ ]
}
]
import { forkJoin } from 'rxjs'
methods . search = async ( term : string , apiKey : string ) => {
let type = 'movie'
let searchQuery = `https://api.themoviedb.org/3/search/ ${ type } ?api_key= ${ apiKey } &language=en-US&page=1&include_adult=false&query= ${ term } `
let searchPromise = new Promise ( ( resolve , reject ) => {
request ( searchQuery , { } , function ( err , res , body ) {
let mvProviders = [ ]
let mvCredits = [ ]
const sleep = ( ms ) => new Promise ( resolve => setTimeout ( resolve , ms ) )
let data = JSON . parse ( body )
searchInfo [ 0 ] [ 'results' ] = data [ 'results' ]
const providers = async ( ) => {
forkJoin (
data [ 'results' ] . map ( m =>
request (
`https://api.themoviedb.org/3/ ${ type } / ${ m . id } /watch/providers?api_key= ${ apiKey } ` ,
{ } ,
async function ( err , res , body ) {
let data = await JSON . parse ( body ) ;
mvProviders . push ( data ) ;
if ( mvProviders . length > 1 ) {
return searchInfo [ 0 ] [ 'providers' ] = mvProviders ;
}
}
)
)
)
}
const credits = async ( ) => {
await providers ( )
forkJoin (
data [ 'results' ] . map ( m =>
request (
`https://api.themoviedb.org/3/ ${ type } / ${ m . id } /credits?api_key= ${ apiKey } &language=en-US` ,
{ } ,
async function ( err , res , body ) {
let data = await JSON . parse ( body ) ;
mvCredits . push ( data ) ;
if ( mvCredits . length > 1 ) {
searchInfo [ 0 ] [ 'credits' ] = mvCredits ;
}
}
)
)
) ;
await sleep ( 1500 ) ;
resolve ( 'done' ) ;
}
credits ( )
} ) ;
} ) ;
let result = await searchPromise ;
return searchInfo ;
}
export const api = { data : methods } ;
Мне пришлось рефакторировать метод поиска из Pevious Luctireds, потому что он блокировал меня от добавления более связанных данных фильма в массив, прежде чем представить его в файле home.component.html
.
В файле html
я могу использовать привязку Angular
Event (input)
чтобы прослушать изменение события и вызвать функцию, проходящую в событии, которое содержит текст поиска.
Я могу передать текст поиска в файл api.ts
, добавляя его в api endpoint
и request
данные.
Если req
(запрос) успешен, полевая нагрузка (ответ) отправляется обратно клиенту (фронт), и я могу отформатировать данные с логикой внутри home.component.ts
(контроллер). Я создал array
называемый results
, чтобы определить отформатированные данные, чтобы актеры и потоковая служба показали в пользовательском интерфейсе.
<!-- home.component.html -->
< div class =" row " >
< input (input) =" doSearch($event) " type =" search " class =" form-control " placeholder =" search " >
< span class =" search-title " > {{ results?.length ? 'Results' : 'Search' }} </ span >
</ div >
// home.component.ts
...
ngOnInit ( ) : void {
}
public doSearch ( e ) {
if ( e . target . value . length > 2 ) {
this . dataService . search ( e . target . value ) . pipe (
debounceTime ( 400 ) ) . subscribe ( res => {
this . format ( res )
} )
}
if ( e . target . value . length == 0 ) {
this . results = [ ]
this . selectedMovie = null
}
}
public format ( data ) {
this . util . relatedInfo ( data [ 0 ] . results , data [ 0 ] . providers , 'providers' , 'movies' )
this . util . relatedInfo ( data [ 0 ] . results , data [ 0 ] . credits , 'credits' , 'movies' )
this . loading = false
this . results = data [ 0 ] . results
console . log ( this . results , 'res' )
}
...
Перейдите в Full home.component.ts
File.
Мне нужно отформатировать данные в соответствии с providers
и credits
с фильмами, к которым они принадлежат ID. Я создал коммунальный service
для обработки форматирования. Используя команду ng gs services/util
я могу сгенерировать файл сервиса с помощью Angular cli
. Функции в service
могут использоваться всеми components
углового приложения, импортируя service
в контроллер component
. Теперь я могу вызвать relatedInfo
функцию в home.component.ts
.
//util.service.ts
public relatedInfo ( items , extras , type : string , itemType : string ) {
for ( let item of items ) {
for ( let e of extras ) {
if ( item . id === e . id ) {
if ( type === 'credits' ) {
item . credits = e ;
item . type = itemType ;
if ( e . cast [ 0 ] != null ) {
item . credit1 = e . cast [ 0 ] [ 'name' ] ;
item . credit1Pic = e . cast [ 0 ] [ 'profile_path' ] ;
item . credit1Char = e . cast [ 0 ] [ 'character' ] ;
}
if ( e . cast [ 1 ] != null ) {
item . credit2 = e . cast [ 1 ] [ 'name' ] ;
item . credit2Pic = e . cast [ 1 ] [ 'profile_path' ] ;
item . credit2Char = e . cast [ 1 ] [ 'character' ] ;
}
}
if ( type === 'providers' ) {
item . provider = e [ 'results' ] . US != null ? e [ 'results' ] . US : 'unknown' ;
}
}
}
}
return items ;
}
Мне нужно показать титры в представлении поиска и представление трейлера. Поскольку HTML уже настроен на массив results
, есть несколько корректировок, которые мне нужно внести в home.component.html
и dialog.component.html
чтобы показать актерские кредиты фильма.
<!-- home.component.html line 21 -->
...
< span class =" info " >
< p >
< span *ngIf =" item?.credit1 " > Cast: {{item?.credit1}}, {{item?.credit2}} < br > </ span >
< span class =" star-rating " *ngFor =" let star of rating(item) " > < span > ☆ </ span > </ span > < br >
< span class =" trailer-link " (click) =" openTrailer(item) " > Trailer </ span >
</ p >
</ span >
...
<!-- dialog.component.html line 24 -->
...
< span > {{selectedMovie?.overview}} </ span > < br >
< span class =" row cast " *ngIf =" selectedMovie?.credits " >
< span class =" col-cast " *ngFor =" let actor of selectedMovie?.credits?.cast; let i = index " >
< span [ngClass] =" {'dn': i > 3 || actor?.profile_path == null, 'cast-item': i <= 3 && actor?.profile_path != null} " >
< img src =" https://image.tmdb.org/t/p/w276_and_h350_face{{actor?.profile_path}} " alt =" image of {{actor?.name}} " > < br >
{{actor?.name}} < br > ({{actor?.character}})
</ span >
</ span >
</ span >
...
Я добавил несколько новых стилей CSS для обработки этих новых данных.
/* dialog.component.scss line 111 */
...
.row.cast {
margin-top : 20 px ;
.col-cast {
flex : 0 0 50 % ;
text-align : left ;
font-size : 10 px ;
img {
width : 30 % ;
border : 1 px solid #fff ;
border-radius : 8 px ;
}
.cast-item {
padding-bottom : 5 px ;
display : block ;
}
.dn {
display : none ;
}
}
}
...
Полный dialog.component.scss
здесь.
Чтобы отобразить потоковую службу каждого фильма в возвращенном массиве фильмов, я использую function
под названием getProvider
. Эта функция возьмет один аргумент и для каждого цикла через данные поставщика и определит короткую string
, чтобы соответствовать дисплею. Некоторые названия поставщиков слишком велики для пользовательского интерфейса и должны быть изменены, используя if / else
Conditonals.
<!-- home.component.html line 28 -->
< span *ngIf =" item?.provider != null " class =" {{getProvider(item?.provider)}} probar " >
{{getProvider(item?.provider)}}
</ span >
// home.component.ts line 75
...
public getProvider ( pro ) {
try {
if ( pro === 'unknown' ) {
return ''
} else if ( pro [ 'flatrate' ] != null ) {
if ( pro . flatrate [ 0 ] . provider_name === 'Amazon Prime Video' ) {
return 'prime'
} else if ( pro . flatrate [ 0 ] . provider_name . toUpperCase ( ) === 'IMDB TV AMAZON CHANNEL' ) {
return 'imdb tv'
} else if ( pro . flatrate [ 0 ] . provider_name . toUpperCase ( ) === 'APPLE TV PLUS' ) {
return 'apple tv'
} else {
return pro . flatrate [ 0 ] . provider_name . toLowerCase ( )
}
} else if ( pro [ 'buy' ] != null ) {
return ''
} else if ( pro [ 'rent' ] != null ) {
return ''
} else {
return ''
}
} catch ( e ) {
console . log ( e , 'error' )
}
}
...
Теперь, используя class
Вероятного, я могу стилизовать поставщика потоковых услуг. Я также могу использовать имя поставщика в классе HTML, используя угловые вьющиеся кронштейны, чтобы добавить динамический цвет фона в стиль вероятного.
/* styles.scss line 58 */
...
.probar {
position : absolute ;
bottom : 0 ;
width : 100 % ;
text-align : center ;
font-size : 14 px ;
font-weight : 300 ;
letter-spacing : 2 px ;
opacity : 0.8 ;
color : #fff ;
text-transform : uppercase ;
background : #555 ;
}
.netflix {
background : #e00713 ;
}
.hbo {
background : #8440C1 ;
}
.prime {
background : #00A3DB ;
}
.hulu {
background : #1DE883 ;
}
.disney {
background : #111D4E ;
}
.apple {
background : #000 ;
}
...
Я решаю сделать трейлер модальной полной шириной экрана и добавить фоновое изображение выбранного фильма. Я обновил dialog.component.html
, используя директиву Angular [ngStyle]
для обработки фонового изображения фильма. Я добавил класс с full
в modal
классе, затем добавил новые стили в Active modal.full
Class.
<!-- dialog.component.html line 11 -->
...
< div id =" top " class =" row " *ngIf =" isOpen " [ngStyle] =" {'background': 'linear-gradient(-225deg, rgba(0,0,0,0.6) 50%, rgba(0,0,0,0.6) 80%), url(https://image.tmdb.org/t/p/w533_and_h300_bestv2/'+ selectedMovie?.backdrop_path +')' } " >
...
/* dialog.component.scss line 133 */
...
.modal.full {
height : 100 % ;
min-width : 100 % ;
#top {
height : 500 px ;
background-size : cover !important ;
background-repeat : no-repeat !important ;
}
.row .col {
flex : 0 0 11 % ;
}
.content {
.col-trailer {
align-self : center ;
text-align : center ;
}
.col-overview {
align-self : center ;
}
}
}
...
Это классная функция, которую я часто использую, чтобы найти, где транслируется фильм, и поиск реагирует быстро, быстрее, чем поиск в Google, я думаю.
Веселый вызов для продвинутых разработчиков, читающих это, было бы, если бы вы могли рефактировать код api.ts
Я использую вложенные Promises
, чтобы дождаться resolve
прежде чем отправлять данные фильма обратно клиенту. Однако мне не нравится, как мне пришлось использовать функцию sleep
await
Timer, чтобы данные ждали, пока данные credit
и provider
будут спасения до того, как данные фильма будут отправлены обратно client
. Мне не нужно было использовать function
sleep
, которая была просто патчкой, чтобы помочь мне. Проблема заключалась в том, что я создал слишком много прицелов, и рельсы не работали так, как я хотел. Если кто -то сможет получить решения для работы без использования function
Timer sleep
, я был бы очень впечатлен. Спасибо за чтение!