我一直在使用 Angular 16、TypeScript 和电影数据库 (TMDB) 开发 SPA。
我在 Bootstrap 5 轮播中显示电影预告片时遇到问题。
在
app\services\movie-service.service.ts
我有:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { HttpClient } from '@angular/common/http';
import { MovieResponse, Movie } from '../models/Movie';
import { Trailer, TrailerResponse } from '../models/Trailer';
@Injectable({
providedIn: 'root'
})
export class MovieService {
constructor(private http: HttpClient) { }
public getMovieTrailers(id: Number): Observable<TrailerResponse>{
return this.http.get<TrailerResponse>(`${environment.apiUrl}/movie/${id}/videos?api_key=${environment.apiKey}`);
}
public getMovieDetails(id: Number): Observable<Movie>{
return this.http.get<Movie>(`${environment.apiUrl}/movie/${id}?api_key=${environment.apiKey}`);
}
}
在
app\models\Trailer.ts
我有:
export interface Trailer {
id?: string;
key?: string;
name?: string;
site?: string;
}
export interface TrailerResponse {
results?: Trailer[];
}
我使用以下 ts 文件创建了一个 TrailerCarouselComponent 组件:
import { Component, Input } from '@angular/core';
import { Trailer } from '../../models/Trailer';
@Component({
selector: 'app-trailer-carousel',
templateUrl: './trailer-carousel.component.html',
styleUrls: ['./trailer-carousel.component.scss']
})
export class TrailerCarouselComponent {
@Input() trailers!: Trailer[];
}
这个模板:
<div id="trailersCarousel" class="carousel slide" data-bs-interval="false">
<ol *ngIf="trailers.length > 1" class="carousel-indicators">
<li
*ngFor="let video of trailers; let i = index"
data-bs-target="#trailersCarousel"
attr.data-slide-to="{{ i }}"
class="{{ i === 0 ? 'active' : '' }}"
>
{{ i + 1 }}
</li>
</ol>
<div class="carousel-inner">
<div
*ngFor="let video of trailers; let i = index"
class="carousel-item"
[ngClass]="{ 'active': i === 0 }"
>
<iframe
class="embed-responsive-item"
src="https://www.youtube.com/embed/{{ video.key }}"
></iframe>
</div>
</div>
</div>
我在 MovieDetailsComponent 组件中使用上述组件:
import { Component, Input } from '@angular/core';
import { Movie } from '../../models/Movie';
import { Trailer, TrailerResponse } from '../../models/Trailer';
import { MovieService } from '../../services/movie-service.service';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-movie-details',
templateUrl: './movie-details.component.html',
styleUrls: ['./movie-details.component.scss']
})
export class MovieDetailsComponent {
constructor(private movieService: MovieService, private activatedRoute: ActivatedRoute) {}
public movie!: Movie;
public trailerResponse!: TrailerResponse;
public trailers: Trailer[] | undefined = [];
public getMovieTrailers() {
const movie_id = Number(this.activatedRoute.snapshot.paramMap.get('id'));
this.movieService.getMovieTrailers(movie_id).subscribe((response) => {
this.trailerResponse = response;
this.trailers = this.trailerResponse.results;
if (this.trailers && this.trailers?.length) {
this.trailers = this.trailers.slice(0, 5);
}
});
}
public getMovieDetails() {
const movie_id = Number(this.activatedRoute.snapshot.paramMap.get('id'));
this.movieService.getMovieDetails(movie_id).subscribe((response) => {
this.movie = response;
// get movie trailers
this.getMovieTrailers();
});
}
ngOnInit() {
this.getMovieDetails();
}
}
模板:
<div *ngIf="trailers && trailers.length" class="mb-3">
<h2 class="section-title">Trailers</h2>
<app-trailer-carousel [trailers]="trailers"></app-trailer-carousel>
</div>
没有编译错误,但是,有一个令人惊讶的(对我来说)问题。
问题
NG0904: unsafe value used in a resource URL context
...
at TrailerCarouselComponent_div_3_Template (trailer-carousel.component.html:22:9)
有问题的行是 src="https://www.youtube.com/embed/{{ video.key }}
来自:
<iframe
class="embed-responsive-item"
src="https://www.youtube.com/embed/{{ video.key }}"
></iframe>
这对我来说更令人惊讶,因为我已经使用 Vue3 和 Typescript 问题src
,我们可以使用 Angular 的
bypassSecurityTrustResourceUrl
中的
DomSanitizer
来确保 URL 得到清理!html
<iframe
class="embed-responsive-item"
[src]="trustUrl(video.key)"
></iframe>
ts
constructor(private sanitizer: DomSanitizer) {}
...
...
trustUrl(key: string): string {
return this.sanitizer.bypassSecurityTrustResourceUrl(`https://www.youtube.com/embed/${key}`) as string;
}
...
ng g p bypass-sanitize
import { Pipe, PipeTransform, inject } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
@Pipe({
name: 'bypassSanitize',
standalone: true,
})
export class BypassSanitizePipe implements PipeTransform {
private readonly domSanitizer = inject(DomSanitizer);
// constructor(private domSanitizier: DomSanitizer) {} old way but still working
transform(value: string) {
return this.domSanitizer.bypassSecurityTrustResourceUrl(value);
}
}
由于您的组件没有独立属性,我假设您的应用程序是基于模块的。您可以在内部导入此管道NgModule
。将其添加到 NgModules 后,让我们使用这个管道:
<iframe
class="embed-responsive-item"
src="{{ 'https://www.youtube.com/embed/' + video.key | bypassSanitize }}"
></iframe>
警告:使用不受信任的用户数据调用此方法会使您的应用程序面临 XSS 安全风险!