此應用程序是用角(版本12.2.8)製成的。這是一個使用node.js
和express
服務器端渲染應用程序,並且它搜索了電影標題。此單頁應用程序可在Heroku(Cloud Application Platform)上免費託管。您將需要使用themoviedb.org創建一個免費帳戶才能參與本教程。下面是如何採購TMDB API密鑰的說明。
由角CLI自動在本地安裝
ng new movie-search --skip-git
Would you like to add Angular routing? Yes
Which stylesheet format would you like to use? SCSS
使此應用程序服務器端渲染的優點是,該應用程序可以比常規應用程序快(UI)出現在屏幕(UI)上。這使用戶有機會在應用程序佈局完全交互之前查看應用程序佈局。它也可以幫助SEO。
ng add @nguniversal/express-engine
server.ts
文件,以服務和處理此應用程序的數據。package.json
中將line 12
更改為"serve:ssr": "node dist/server/main.js",
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
佔位符HTML內容。 <!-- app.component.html -->
< div class =" container " >
< router-outlet > </ router-outlet >
</ div >
對於此應用程序,我們需要將HttpClinetModule
, ReactiveFormsModule
和FormsModule
添加到app.module.ts
文件並將其添加到imports
。這將使該模塊在整個應用程序中可用。 HTTP模塊將允許我們撥打服務器。 ReactiveFormsModule
將幫助我們在HTML輸入上使用FormControl
,並且當值更改(搜索輸入中的文本)時,將發送API請求。
//app.module.ts
import { HttpClientModule } from "@angular/common/http" ;
import { ReactiveFormsModule , FormsModule } from "@angular/forms" ;
home.component.html
中使用FormControl
創建搜索輸入。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請求並解析響應。在打字稿中,我可以使用承諾等待響應準備避免丟失錯誤。
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 >
在此UI中有幾件事要拆箱。我正在使用角度插值支架內的三元條件來顯示文本“搜索”或“結果”,以顯示是否有數據可顯示。
{{ (results | async)?.length ? 'Results' : 'Search' }}
我使用的是[ngClass]
指令,該指令與角框架不同。如果數據poster_path
為null
,然後以styles.scss
為dn
.dn {display: none;}
我還使用[ngStyle]
指令來動態添加每個電影項目的海報圖像的背景圖像。
我添加了一些基本的CSS樣式,以在Flex行列佈局中顯示電影結果。這也將處理較小的移動屏幕。使用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
和Heroku之前,用process.env.TOKEN
替換API令牌。line 20
中添加const apiKey = process.env.TOKEN;
git commit -am "make a commit."
然後git push
Heroku CLI
登錄到Heroku,從終端運行: heroku login
。heroku create angular-movie-search
和git push heroku master
。key: TOKEN
和value: TMDB api key
。如果您創建的Heroku App名稱被構成可用的獨特名稱。我將為此綜合添加一個第2部分,以便我們可以顯示更多的電影數據,並通過加載電影預告片使頁面交互式。謝謝您的閱讀。完整的源代碼
angular cli
分量。當我們懸停在電影圖像上時,讓我們展示一些有關電影的信息。搜索有效載荷提供了電影評分分數0-10。可以將評分(投票率)轉換為數組,以將等級顯示為等於陣列長度的星圖標。
payload
是向api
提出搜索請求後發送回的數據。每個響應將獲得最大20個結果。有效載荷越小,在UI中渲染的數據越快。這是API發回的Oservorable的第一個對象的一個示例。
{
"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
相等時。我需要做的就是將一些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
來通過調用function
來注入home.component
中的數據,然後在關閉對話框時使用Output
來emit
function
。可以通過單擊右上角的X或單擊覆蓋層來關閉對話框。 closeModal()
函數將刪除active
類,重置所有值並發出一個函數以重置home.component
中的isOpen
。
<!-- 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
並在<iframe>
中顯示。單擊電影鏈接時, selectedMovie
將從home.component
傳遞,但是在打開對話框之前,我需要從API中獲取電影預告片。我在api.ts
文件中添加了一個新的API調用。
//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
交談,並將響應發送回客戶端(前端)。響應中使用Musly的第一個索引是指向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
,該功能將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
= en-us通過電影id
來獲取電影預告片。 id
和apikey
使用TypeScript括號和背景傳遞到端點,這是一種使用JS添加動態值的新方法,並且看起來比使用A +
來串聯值更好。
如果數據符合YouTube條件,則打開Diaglog彈出窗口,並且數據將顯示HTML內部和angular
插值字符串{{selectedMovie.title}}
,雙支架會動態地處理HTML中的數據。
與這樣的項目並非總是討論的事情是,將其轉換為完全不同的項目並不需要太多時間。您可以輕鬆地更改api.ts
文件中的端點,以與其他API通信,並在UI中顯示不同的數據。當然,您需要更改一些變量命名約定,以使代碼很有意義,但是該項目可以用您可能更感興趣的其他內容進行回收。將其視為已經使用簡單的後端服務器和API文件處理您想獲取的任何數據並將其發送回前端以進行顯示。將home.html
中的標題標題更改為諸如Job Search
類的東西,然後連接到可以通過關鍵字獲取作業的作業列表API。一旦開始,任何可能的事情就可以了。感謝您與我編碼。祝你好運。完整的源代碼
旁注:我剛剛發現這一刻有一個html5
對話框tag
<dialog open>This is an open dialog window</dialog>
但在Chrome中對我不起作用。它可能有點新,缺乏瀏覽器支持,但是也許您的創意開發人員可以找到一種方法來使用它,而不是我的“從頭開始”方法。
TMDB API最近在其API中添加了一個新的終點,它將告訴您使用電影ID在哪裡流式傳輸。我還添加了另一個端點,以在搜索有效載荷中獲取電影的演員。我更新了api.ts
文件以處理這些新請求。
獲得搜索結果後,我使用RXJS forkJoin
方法並map
為每個電影的id
providers
多個請求。將提供商數據pushed
mvProvider
數組,然後將其添加到searchInfo
對像中,然後將其發送給客戶端,並通過電影結果數組,提供商數組和Credits數組發送回客戶端。
根據搜索結果的數量,單個搜索請求可能會生成30個API請求,然後再解決數據並發送給客戶端。我創建了一個sleep
功能, await
所有請求完成,然後再resolve
將IMComplete數據發送回前端。這不是處理此問題的最佳方法。使用async
和await
,對於確保在resolved
promise
之前完成的所有數據完成至關重要。
//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教程中重構搜索方法,因為它阻止了我在home.component.html
文件中介紹該數組之前將更多相關的電影數據添加到數組中。
在html
文件中,我可以使用Angular
事件綁定(input)
來偵聽事件更改,並在包含搜索文本的事件中調用函數傳遞。
我可以將搜索文本傳遞到api.ts
文件將其附加到api endpoint
並request
數據。
如果req
(請求)成功,則有效負載RES(響應)將發送回客戶端(前端),我可以在home.component.ts
(Controller)內使用邏輯格式化數據。我創建了一個名為results
array
,以將格式化數據定義為,以便參與者和流媒體服務將顯示在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' )
}
...
轉到full home.component.ts
文件。
我需要將數據格式化以將providers
和credits
與他們屬於ID所屬的電影相匹配。我創建了一個實用service
來處理格式。通過使用命令ng gs services/util
我可以生成具有Angular cli
的服務文件。 Angular App的所有components
可以通過將service
導入到component
控制器中來使用service
中的功能。現在,我可以在home.component.ts
中調用relatedInfo
函數。
//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 ;
}
我需要在搜索視圖和預告片視圖中顯示積分。由於已經為results
數組設置了HTML,因此我需要在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
以適合顯示屏。一些提供商的標題太大,無法使用UI,需要使用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' )
}
}
...
現在使用Probar class
我可以樣式流媒體服務提供商。我還可以使用Angular插值捲曲支架為Probar樣式添加動態背景顏色,從而在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 ;
}
...
我決定將預告片模式化為屏幕的完整寬度,並添加所選電影的背景圖像。我通過使用Angular [ngStyle]
指令來處理電影背景圖像,更新了dialog.component.html
。我在modal
類中添加了一個名為full
類,然後在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 ;
}
}
}
...
這是我經常使用的一個很酷的功能,以找到電影流式傳輸的位置,並且搜索響應速度比我認為的Google搜索快,快速響應。
對於在那裡閱讀的高級開發人員來說,這是一個有趣的挑戰,如果您可以重構api.ts
代碼。我正在使用嵌套Promises
等待resolve
然後再將電影數據發送回客戶。但是,我不喜歡我必須使用sleep
await
計時器功能的方式,以便數據在將電影數據發送回client
之前等待credit
和provider
數據分析。我不必使用只是一個補丁的sleep
function
來幫助我。問題是我創建了太多的範圍,而決心沒有像我想要的那樣奏效。如果某人可以在不使用sleep
計時器function
情況下使解決方案工作,我會印象深刻。謝謝您的閱讀!