此应用程序是用角(版本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
情况下使解决方案工作,我会印象深刻。谢谢您的阅读!