Aplikasi ini dibuat dengan Angular (versi 12.2.8). Ini adalah aplikasi rendering sisi server yang menggunakan node.js
dan express
dan mencari judul film. Aplikasi satu halaman ini di -host secara gratis di Heroku (Cloud Application Platform). Anda perlu membuat akun gratis dengan themoviedb.org untuk berpartisipasi dengan tutorial ini. Instruksi cara mendapatkan kunci API TMDB di bawah ini.
Secara otomatis dipasang secara lokal dengan sudut cli
ng new movie-search --skip-git
Would you like to add Angular routing? Yes
Which stylesheet format would you like to use? SCSS
Keuntungan membuat aplikasi ini di sisi server ini adalah bahwa aplikasi dapat muncul di layar (UI) lebih cepat dari aplikasi biasa. Ini memberi pengguna kesempatan untuk melihat tata letak aplikasi sebelum menjadi sepenuhnya interaktif. Ini juga dapat membantu dengan SEO juga.
ng add @nguniversal/express-engine
server.ts
untuk melayani dan menangani data untuk aplikasi ini.package.json
mengubah line 12
ke "serve:ssr": "node dist/server/main.js",
angular.json
mengubah line 20
ke "outputPath": "dist/browser",
dan line 129
ke "outputPath": "dist/server",
server.ts
mengubah line 14
ke const distFolder = join(process.cwd(), 'dist/browser');
npm run build:ssr
npm run serve:ssr
ng gc modules/home --module=app.module.ts
homeComponent
secara default. //app-routing.module.ts
import { HomeComponent } from './modules/home/home.component' ;
const routes : Routes = [
{
path : '' ,
component : HomeComponent
} ,
{
path : '**' ,
component : HomeComponent
}
] ;
app.component.html
placeholder konten html dengan ini di bawah ini. <!-- app.component.html -->
< div class =" container " >
< router-outlet > </ router-outlet >
</ div >
Untuk aplikasi ini kita perlu menambahkan HttpClinetModule
, ReactiveFormsModule
dan FormsModule
ke file app.module.ts
dan menambahkannya ke imports
. Ini akan membuat modul ini tersedia di seluruh seluruh aplikasi. Modul HTTP akan memungkinkan kami melakukan panggilan ke server. ReactiveFormsModule
akan membantu kita menggunakan FormControl
pada input HTML dan ketika nilai berubah (teks dalam input pencarian) permintaan API akan dikirim ke server.
//app.module.ts
import { HttpClientModule } from "@angular/common/http" ;
import { ReactiveFormsModule , FormsModule } from "@angular/forms" ;
FormControl
di home.component.html
.async Observable
dan function
untuk menembak onInit. <!-- 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
ini di terminal: 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' )
}
}
}
Di server.ts
Buat fungsi untuk menangani permintaan dari klien. Kemudian kirim permintaan ke titik akhir 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
dan file api.ts
mkdir api
kemudian cd api
kemudian touch api.ts
untuk mengatur direktori API.api
ke file server.ts
. import { api } from './api/api'
. Di masa depan jika Anda ingin menambahkan permintaan yang berbeda ke TMDB api
Anda dapat menambahkannya ke api.ts
untuk menjaga file server.ts
kurang berantakan.
//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 } ;
Saya menggunakan pustaka request
untuk menangani permintaan API dan menguraikan tanggapan. Di TypeScript saya dapat menggunakan janji untuk menunggu respons siap untuk menghindari melakukan kesalahan.
settings
.API
dan kirimkan detail aplikasi Anda untuk menerima kunci 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
di aplikasi Anda.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 } ) ) ;
...
Ketika kami mendapatkan respons data kembali dari titik akhir TMDB, ia dikirim kembali ke klien (front-end). home.component.html
perlu diatur untuk menampilkan 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 >
Ada beberapa hal untuk dibongkar di UI ini. Saya menggunakan kondtion ternary di dalam braket interpolasi sudut untuk menampilkan teks "pencarian" atau "hasil" tergantung pada apakah ada data yang harus ditampilkan.
{{ (results | async)?.length ? 'Results' : 'Search' }}
Saya menggunakan arahan [ngClass]
yang terpisah dari kerangka sudut. Saya menambahkan kelas dn
jika data poster_path
null
dan kemudian di styles.scss
menambahkan .dn {display: none;}
untuk menghindari item film kosong. Saya juga menggunakan arahan [ngStyle]
untuk menambahkan gambar latar belakang gambar poster setiap item film secara dinamis.
Saya telah menambahkan beberapa gaya CSS dasar untuk menampilkan hasil film dalam tata letak kolom Flex Row. Ini akan menangani layar seluler yang lebih kecil juga. Dengan file scss
Anda dapat menulis CSS bersarang seperti yang ditunjukkan di bawah ini. File SCSS lengkap untuk aplikasi pencarian film ini
// 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 % ;
}
}
Jika Anda ingin meng -host aplikasi ini secara gratis, kunjungi kapan saja Anda ingin dan membaginya dengan orang lain, ikuti langkah -langkah di bawah ini.
package.json
untuk Herokuline 6
Tambahkan "start:heroku": "node dist/server/main.js",
line 7
Tambahkan "heroku-postbuild": "npm run build:ssr"
Procfile
ke akar aplikasi ini.touch Procfile
Tambahkan baris ini web: npm run start:heroku
ke file.process.env.TOKEN
ke server.ts
sebelum mendorong ke GitHub dan Heroku.line 20
tambahkan const apiKey = process.env.TOKEN;
git commit -am "make a commit."
Lalu git push
Heroku CLI
Login ke Heroku dari Terminal Run: heroku login
.heroku create angular-movie-search
dan git push heroku master
.key: TOKEN
dan value: TMDB api key
.Jika nama aplikasi Heroku yang Anda buat diambil, buatlah nama unik yang tersedia. Saya akan menambahkan bagian 2 untuk tutrial ini sehingga kami dapat menampilkan beberapa data film lagi dan membuat halaman interaktif dengan memuat trailer film. Terima kasih telah membaca. Kode Sumber Lengkap
angular cli
.Mari kita tunjukkan beberapa info tentang film ketika kita melayang di atas gambar film. Payload pencarian memberikan skor peringkat film 0 - 10. Peringkat (VOTO_AVEREGE) dapat dikonversi ke array untuk menunjukkan peringkat sebagai ikon bintang yang sama dengan panjang array.
payload
adalah data yang dikirim kembali setelah Anda membuat permintaan pencarian ke api
. Anda akan mendapatkan maks 20 hasil per respons. Semakin kecil muatan semakin cepat data diberikan di UI. Ini adalah contoh objek pertama dari oserverable yang dikirim kembali oleh 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 "
}
Di bagian pertama tutorial ini saya menggunakan poster_path
untuk menampilkan gambar film dan sekarang saya dapat menggunakan vote_average
untuk menunjukkan peringkat film. Saya membuat fungsi di dalam pengontrol komponen untuk mengonversi peringkat ke array yang kemudian dapat mewakili nilai suara vote_average
yang dibulatkan ke seluruh bilangan bulat dan menggunakan bintang emas untuk mewakili peringkat ketika saya melayang di atas gambar film.
< 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 ;
}
Kemudian gaya konten untuk nilai peringkat yang dikembalikan sehingga kita hanya melihat bintang -bintang di hover. File SCSS lengkap untuk komponen dialog
// 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 ;
}
}
}
Selanjutnya saya akan menambahkan tautan yang akan mengirim permintaan API lain untuk trailer film (pratinjau) dan kemudian membuka dialog (jendela pop-up) untuk menampilkan trailer dengan ikhtisar (deskripsi) film.
Ketika saya mengambil info film Trialer dari API, saya ingin menyematkan tautan media di dalam html <iframe>
. Saya ingin menambahkan tautan "trailer" yang akan membuka jendela untuk menampilkan trailer saat diklik. Menggunakan angular cli
Saya akan menghasilkan komponen dialog baru. Dalam tipe terminal ng gc components/dialog --module=app.module.ts
. Perintah ini akan menambahkan komponen ke app.module.ts
secara otomatis.
Untuk membuat jendela pop-up dari awal, saya perlu menggunakan CSS kecil dan beberapa trik sudut untuk membantu saya menambahkan class
khusus ketika tautan "trailer" diklik. dialog component
menggunakan boolean
untuk menambahkan kelas active
ke div
untuk menunjukkan latar belakang overlay gelap dengan jendela pop-up yang diposisikan di tengah overlay. Menggunakan arahan sudut [ngClass]
Jika isOpen
boolean
true
Tambahkan kelas active
ke id
overlay. <div id="overlay" [ngClass]="{'active': isOpen}">
Ini memungkinkan saya untuk menyembunyikan overlay div
sampai active
, ketika saya mengklik tautan trailer dan membuat isOpen
sama benar. Yang perlu saya lakukan adalah menambahkan beberapa Inputs
ke komponen 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 ( ) ;
}
}
Saya function
Input
emit
menyuntikkan Output
dari home.component
function
Dialog dapat ditutup dengan mengklik X di kanan atas atau dengan mengklik overlay. Fungsi closeModal()
akan menghapus kelas active
, mengatur ulang semua nilai dan memancarkan fungsi untuk mereset isOpen
di 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 dan tampilkan di <iframe>
. selectedMovie
diteruskan dari home.component
Saya menambahkan panggilan API baru ke file 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 ;
}
}
} )
}
Dari server / api
data.service
. Indeks pertama dalam respons dengan biasanya menjadi tautan ke YouTube di mana hampir semua trailer film berada, jadi saya menggunakan kondisi untuk secara khusus hanya menggunakan trailer YouTube dan jika tidak membuka trailer. Untuk bersenang -senang, Anda dapat menambahkan kondisi ini jika Anda ingin membiarkan trailer terbuka dari sumber video lain.
//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' )
}
}
Saya menggunakan try catch
untuk menangani kesalahan tetapi ada banyak cara untuk menangani kesalahan di angular
. Ini hanya untuk kesederhanaan di pihak saya.
//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 ) ;
} )
Saya menggunakan async function
yang akan await
API untuk memberi kami muatan (respons) sebelum menyelesaikan post
untuk menghindari kesalahan server.
//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 ;
} ;
Saya menggunakan titik akhir TMDB ini https://api.themoviedb.org/3/movie/${id}/videos?api_key=${apiKey}&language=en-US
id}/videos?api_key=$ {apikeY}&language=en-us untuk mendapatkan trailer film dengan id
film. id
dan apikey
diteruskan ke titik akhir menggunakan braket TypeScript dan backticks yang merupakan cara baru untuk menambahkan nilai dinamis dengan JS dan terlihat jauh lebih bagus daripada menggunakan nilai +
untuk menggabungkan nilai.
Jika data memenuhi kondisi YouTube, pop-up diaglog dibuka dan data akan ditampilkan di dalam html dan string interpolasi angular
{{selectedMovie.title}}
, braket ganda memproses data dalam HTML secara dinamis.
Sesuatu yang tidak selalu dibicarakan dengan proyek -proyek seperti ini adalah tidak perlu banyak waktu untuk mengubah ini menjadi proyek yang sama sekali berbeda. Anda dapat dengan mudah mengubah titik akhir dalam file api.ts
untuk berkomunikasi dengan API yang berbeda dan mendapatkan data yang berbeda untuk ditampilkan di UI. Tentu saja Anda perlu mengubah beberapa variabel konvensi penamaan sehingga kode tersebut masuk akal tetapi proyek ini dapat didaur ulang dengan hal lain yang mungkin lebih Anda minati. Lihat itu sebagai templat yang sudah diatur dengan server backend sederhana dan server sederhana dan server sederhana dan server sederhana dan server backend sederhana dan File API untuk menangani data apa pun yang ingin Anda ambil dan kirim kembali ke front-end untuk tampilan. Ubah judul header di home.html
ke sesuatu seperti Job Search
dan terhubung ke API daftar pekerjaan yang dapat mengambil pekerjaan dengan kata kunci misalnya. Setelah Anda memulai, segala sesuatu mungkin terjadi. Terima kasih telah mengkode saya. Semoga beruntung. Kode Sumber Lengkap
Catatan: Saya baru tahu tepat saat ini ada tag
dialog html5
<dialog open>This is an open dialog window</dialog>
tetapi tidak berhasil untuk saya di Chrome. Mungkin sedikit terlalu baru dan kurang dukungan browser tetapi mungkin Anda para pengembang kreatif di luar sana dapat menemukan cara untuk menggunakannya alih -alih pendekatan "lakukan dari awal" saya.
API TMDB baru -baru ini menambahkan titik akhir baru ke API mereka yang akan memberi tahu Anda di mana film streaming dengan menggunakan ID film. Saya juga menambahkan titik akhir lain untuk mengambil pemeran film dalam muatan pencarian. Saya memperbarui file api.ts
untuk menangani permintaan baru ini.
Setelah mendapatkan hasil pencarian, saya menggunakan metode dan map
RXJS forkJoin
untuk membuat banyak permintaan providers
untuk setiap film dengan id
. Data penyedia pushed
ke array mvProvider
dan kemudian ditambahkan ke objek searchInfo
yang kemudian dikirim kembali ke klien dengan array hasil film, array penyedia dan array kredit.
Bergantung pada jumlah hasil pencarian, satu permintaan pencarian dapat menghasilkan hingga 30 permintaan API sebelum data diselesaikan dan dikirim ke klien. Saya membuat fungsi sleep
untuk await
semua permintaan untuk menyelesaikan sebelum resolve
mengirimkan data yang tidak lengkap kembali ke front-end. Ini bukan cara terbaik untuk menangani ini tetapi berhasil. Menggunakan async
dan await
adalah penting untuk memastikan semua data diambil selesai sebelum 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 } ;
Saya harus refactor metode pencarian dari tutorial Pevious karena memblokir saya dari menambahkan lebih banyak data film terkait ke array sebelum mempresentasikannya di file home.component.html
.
Dalam file html
saya dapat menggunakan event event Angular
(input)
untuk mendengarkan perubahan peristiwa dan memanggil fungsi yang lewat dalam acara yang berisi teks pencarian.
Saya dapat meneruskan teks pencarian ke file api.ts
Tambahkan ke api endpoint
dan request
data.
Jika req
(permintaan) berhasil, restload res (respons) dikirim kembali ke klien (front-end) dan saya dapat memformat data dengan logika di dalam home.component.ts
(controller). Saya membuat array
yang disebut results
untuk menentukan data yang diformat sehingga aktor dan layanan streaming akan ditampilkan di UI.
<!-- 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' )
}
...
Pergi ke file lengkap home.component.ts
.
Saya perlu memformat data untuk mencocokkan providers
dan credits
dengan film yang dimiliki oleh ID. Saya membuat service
utilitas untuk menangani pemformatan. Dengan menggunakan perintah ng gs services/util
saya dapat menghasilkan file layanan dengan Angular cli
. Fungsi dalam service
dapat digunakan oleh semua components
aplikasi sudut dengan mengimpor service
ke pengontrol component
. Sekarang saya dapat menghubungi fungsi relatedInfo
di 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 ;
}
Saya perlu menunjukkan kredit di tampilan pencarian dan tampilan trailer. Karena HTML sudah diatur untuk array results
, ada beberapa penyesuaian yang perlu saya lakukan di home.component.html
dan dialog.component.html
untuk menunjukkan kredit akting film.
<!-- 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 >
...
Saya menambahkan beberapa gaya CSS baru untuk menangani data baru ini.
/* 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
lengkap.component.scss di sini.
Untuk menampilkan layanan streaming dari setiap film dalam array film yang dikembalikan, saya menggunakan function
yang disebut getProvider
. Fungsi ini akan mengambil satu argumen dan untuk setiap loop melalui data penyedia dan mendefinisikan string
pendek agar sesuai dengan tampilan. Beberapa judul penyedia terlalu besar untuk UI dan perlu diubah menggunakan if / else
conditonal.
<!-- 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' )
}
}
...
Sekarang menggunakan class
probar saya dapat menata penyedia layanan streaming. Saya juga dapat menggunakan nama penyedia di kelas HTML dengan menggunakan braket interpolasi angular untuk menambahkan warna latar belakang dinamis ke gaya probar.
/* 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 ;
}
...
Saya memutuskan untuk membuat modal trailer lebar layar penuh dan menambahkan gambar latar belakang film yang dipilih. Saya [ngStyle]
dialog.component.html
. Saya menambahkan kelas bernama full
ke kelas modal
lalu saya menambahkan gaya baru ke kelas Active modal.full
.
<!-- 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 ;
}
}
}
...
Ini adalah fitur keren yang sering saya gunakan untuk menemukan di mana film streaming dan pencarian merespons dengan cepat, lebih cepat dari pencarian Google saya pikir.
Tantangan yang menyenangkan bagi pengembang lanjutan di luar sana membaca ini adalah jika Anda dapat refactor kode api.ts
Saya menggunakan Promises
bersarang untuk menunggu resolve
sebelum mengirim data film kembali ke klien. Namun saya tidak suka cara saya harus menggunakan fungsi pengatur waktu await
sleep
sehingga data akan menunggu credit
dan data provider
untuk disesuaikan sebelum data film dikirim kembali ke client
. Saya tidak perlu menggunakan function
sleep
yang hanya merupakan tambalan untuk membantu saya. Masalahnya adalah saya menciptakan terlalu banyak lingkup dan penyelesaian tidak bekerja seperti yang saya inginkan. Jika seseorang dapat menyelesaikan penyelesaian untuk bekerja tanpa menggunakan function
timer sleep
, saya akan sangat terkesan. Terima kasih telah membaca!