Angular 7通过Blob获取图片时重复请求服务器的问题及解决
解决Angular模板中调用方法导致重复HTTP请求的问题
首先咱们得揪出问题的根源:你在模板的[src]属性里直接调用了DownloadImage(9)方法,而Angular的变更检测机制会频繁触发这个方法执行。
Angular的变更检测会在很多场景下启动——比如用户点击操作、数据更新、定时器触发,甚至浏览器窗口resize,每次触发时它都会重新计算模板里的所有表达式,包括你写的这个方法调用。表格有4行数据,每次变更检测就会调用4次DownloadImage,再加上多次变更检测周期叠加,请求次数自然就爆炸了。更糟的是你现在硬编码了id=9,所有行都在请求同一张图片,重复请求量就更大了。
下面给你两种实用的解决办法,按需选择:
方法一:提前加载并缓存图片URL
这种方式适合已经拿到完整表格数据的场景,提前把需要的图片加载完成后存在组件的缓存对象里,模板直接绑定缓存好的URL即可。
步骤1:修改组件TypeScript代码
import { Component, OnInit, OnDestroy } from '@angular/core'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { PostService } from './your-post-service-path'; @Component({ // 组件元数据(比如selector、templateUrl等) }) export class YourComponent implements OnInit, OnDestroy { // 缓存每个图片ID对应的安全URL imageUrls: { [key: number]: SafeUrl } = {}; // 假设你的表格数据存储在这个数组里 tableData: any[] = []; constructor( private postService: PostService, private sanitizer: DomSanitizer ) {} ngOnInit(): void { // 先获取表格数据,再批量加载图片 this.fetchTableData().then(() => { this.tableData.forEach(item => this.loadImage(item.id)); }); } ngOnDestroy(): void { // 组件销毁时清理Blob URL,避免内存泄漏 Object.values(this.imageUrls).forEach(safeUrl => { const rawUrl = safeUrl.changingThisBreaksApplicationSecurity; window.URL.revokeObjectURL(rawUrl); }); } private loadImage(id: number): void { // 如果已经加载过该图片,直接返回,避免重复请求 if (this.imageUrls[id]) return; this.postService.downloadImage(id).subscribe(data => { const arrayBufferView = new Uint8Array(data); const blob = new Blob([arrayBufferView], { type: 'image/jpeg' }); const imageUrl = window.URL.createObjectURL(blob); this.imageUrls[id] = this.sanitizer.bypassSecurityTrustUrl(imageUrl); }); } private async fetchTableData(): Promise<void> { // 模拟获取表格数据的逻辑,替换成你实际的接口调用 // this.tableData = await yourDataService.getTableData(); } }
步骤2:修改模板代码
直接绑定缓存好的URL,还可以添加默认图片作为占位:
<ng-container matColumnDef="thumbnail"> <th mat-header-cell *matHeaderCellDef> {{ 'GENERAL.PHOTO' | translate }} </th> <td mat-cell *matCellDef="let element"> <img class="table-user-pic" [src]="imageUrls[element.id] || '/assets/default-avatar.jpg'" alt="user avatar" width="50" > </td> </ng-container>
方法二:使用Async管道+Observable缓存
这种方式更灵活,适合按需加载图片(比如滚动到某行才加载),而且Angular会自动管理订阅,避免内存泄漏。
步骤1:修改组件TypeScript代码
import { Component, OnDestroy } from '@angular/core'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { Observable, shareReplay, map } from 'rxjs'; import { PostService } from './your-post-service-path'; @Component({ // 组件元数据 }) export class YourComponent implements OnDestroy { // 缓存每个图片ID对应的Observable private imageObservables: { [key: number]: Observable<SafeUrl> } = {}; // 存储已创建的Blob URL,用于组件销毁时清理 private createdUrls: string[] = []; constructor( private postService: PostService, private sanitizer: DomSanitizer ) {} ngOnDestroy(): void { // 清理所有Blob URL this.createdUrls.forEach(url => window.URL.revokeObjectURL(url)); } getImageUrl(id: number): Observable<SafeUrl> { // 如果没有缓存过该ID的Observable,就创建并缓存 if (!this.imageObservables[id]) { this.imageObservables[id] = this.postService.downloadImage(id).pipe( map(data => { const arrayBufferView = new Uint8Array(data); const blob = new Blob([arrayBufferView], { type: 'image/jpeg' }); const imageUrl = window.URL.createObjectURL(blob); this.createdUrls.push(imageUrl); return this.sanitizer.bypassSecurityTrustUrl(imageUrl); }), shareReplay(1) // 缓存请求结果,同一个ID只会发起一次请求 ); } return this.imageObservables[id]; } }
步骤2:修改模板代码
用async管道自动订阅Observable:
<ng-container matColumnDef="thumbnail"> <th mat-header-cell *matHeaderCellDef> {{ 'GENERAL.PHOTO' | translate }} </th> <td mat-cell *matCellDef="let element"> <img class="table-user-pic" [src]="getImageUrl(element.id) | async" alt="user avatar" width="50" > </td> </ng-container>
内容的提问来源于stack exchange,提问作者kianoush dortaj
相关产品推荐
相关产品推荐

