Angular管道返回空值但控制台输出正确数据问题排查
问题
初始化时从API拉取数据填充benefit对象,之后调用另一个API获取图片Blob数据并准备转成base64。此时DOM已经渲染,执行returnBase64FromBlob管道时,控制台能打印出正确的base64,但<img>的src始终是空的。管道会执行两次:第一次因为value为空返回空值,第二次控制台输出正确数据,但DOM里的src就是不更新,这是怎么回事?
相关代码
Benefits.component.html
<div class="col-12 col-sm-12 col-md-6 bg-white" *ngFor="let benefit of benefits"> <div class="img-container"> <img [src]="benefit.logofile_base64 | returnBase64FromBlob" > </div> </div>
Benefits.pipe.ts
@Pipe({ name: 'returnBase64FromBlob' }) export class ReturnBase64FromBlob implements PipeTransform { constructor() { } transform(value: any ): string { if (!value) return ""; var reader = new FileReader(); reader.readAsDataURL(value); reader.onloadend = function () { var base64data = reader.result; console.log("base64data - crmpipe"); console.log(base64data); return base64data; } return ""; } }
最终DOM结果
<img _ngcontent-ple-c114="" src="">
原因
核心问题出在异步操作和管道的同步特性不兼容:
- Angular的管道
transform方法是同步执行的,而FileReader.readAsDataURL是异步任务。你在onloadend回调里返回的base64data根本不会被管道接收——等回调触发时,transform方法早已经执行完毕,返回了初始的空字符串。 - 第二次管道执行时,虽然有了Blob值,但
transform还是直接返回空字符串,异步回调的结果无法反馈到管道的返回值里,Angular的变更检测看不到有效的更新,自然不会修改img的src。
解决办法
有两种靠谱的处理方式:
方式一:在组件里提前转成Base64
放弃用管道处理异步操作,直接在组件获取到Blob数据后完成转换,再赋值给benefit对象。这样值更新时,Angular会自动触发变更检测更新DOM:
// 组件中获取到Blob数据后的处理逻辑 const reader = new FileReader(); reader.onloadend = () => { // 直接把base64字符串赋值给benefit的属性 benefit.logofile_base64 = reader.result as string; // 如果组件用了OnPush变更检测策略,可能需要手动触发检测 // this.cdr.markForCheck(); }; reader.readAsDataURL(yourBlobData);
模板里直接绑定即可,不用管道:
<img [src]="benefit.logofile_base64">
方式二:改成异步管道
把自定义管道改成返回Observable,配合Angular的async管道处理异步结果:
import { Pipe, PipeTransform } from '@angular/core'; import { Observable, from } from 'rxjs'; @Pipe({ name: 'returnBase64FromBlob' }) export class ReturnBase64FromBlob implements PipeTransform { transform(value: Blob): Observable<string> { if (!value) { return new Observable(observer => observer.next('')); } // 把FileReader的异步操作包装成Promise,再转成Observable return from(new Promise<string>((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result as string); reader.readAsDataURL(value); })); } }
模板里用async管道订阅这个Observable:
<img [src]="benefit.logofile_base64 | returnBase64FromBlob | async">
内容的提问来源于stack exchange,提问作者MeC
相关产品推荐
相关产品推荐

