Angular中用RxJS Subject服务传递Blob跨组件失败求助
问题根源
你的代码无法传递Blob的核心原因有两个:
- 跨标签页服务不共享:新打开的标签页是一个独立的Angular应用实例,原页面的
DocPreviewService和新标签页的服务实例完全隔离,RxJS Subject的消息无法跨实例传递。 - 时机不匹配:预览组件的
ngAfterViewInit执行时,异步请求大概率还未完成,此时doc_blob仍为undefined,不会触发渲染逻辑。
解决方案一:通过Object URL传递(推荐)
利用浏览器的URL.createObjectURL生成Blob的临时访问地址,将其作为路由参数传递给新标签页,预览组件直接通过该地址获取Blob并渲染。
1. 修改第一个组件的showReport方法
showReport(selected_id: string) { this.report_service.createReport(this.calc_items[selected_id]).subscribe({ next: (doc_blob: Blob) => { // 生成Blob的临时URL const blobUrl = URL.createObjectURL(doc_blob); // 将URL作为查询参数传入路由 const url = this.router.serializeUrl( this.router.createUrlTree([`tools/${selected_id}/preview`], { queryParams: { blobUrl } }) ); window.open(url, '_blank'); }, error: (error: any) => { console.error('获取报告失败:', error); }, }); }
2. 修改预览组件
import { ActivatedRoute } from '@angular/router'; export class DocPreviewComponent implements OnInit, AfterViewInit, OnDestroy { doc_blob?: Blob; @ViewChild('doc_preview') doc_preview!: ElementRef; private blobUrl?: string; constructor( private route: ActivatedRoute, private local_storage: LocalStorageService ) {} ngOnInit(): void { // 从路由参数获取Blob的临时URL this.route.queryParams.subscribe(params => { this.blobUrl = params['blobUrl']; if (this.blobUrl) { // 通过fetch获取Blob fetch(this.blobUrl) .then(res => res.blob()) .then(blob => { this.doc_blob = blob; this.renderDoc(); }); } }); } ngAfterViewInit(): void { // 若初始化时已获取到Blob,直接渲染 if (this.doc_blob) { this.renderDoc(); } } private renderDoc(): void { if (this.doc_blob && this.doc_preview) { doc.renderAsync(this.doc_blob, this.doc_preview.nativeElement) .then(() => { console.log("docx: finished"); // 渲染完成后释放临时URL,避免内存泄漏 if (this.blobUrl) { URL.revokeObjectURL(this.blobUrl); } }); } } ngOnDestroy(): void { // 组件销毁时释放临时URL if (this.blobUrl) { URL.revokeObjectURL(this.blobUrl); } } }
解决方案二:通过LocalStorage传递(适合大Blob场景)
将Blob转成Base64字符串存入LocalStorage,新标签页读取后转回Blob,渲染完成后清除存储内容。
1. 修改第一个组件的showReport方法
showReport(selected_id: string) { this.report_service.createReport(this.calc_items[selected_id]).subscribe({ next: (doc_blob: Blob) => { const reader = new FileReader(); reader.onload = () => { const base64Data = reader.result as string; // 用selected_id作为存储键,避免冲突 localStorage.setItem(`preview_blob_${selected_id}`, base64Data); const url = this.router.serializeUrl( this.router.createUrlTree([`tools/${selected_id}/preview`]) ); window.open(url, '_blank'); // 1分钟后自动清除存储,防止冗余数据 setTimeout(() => { localStorage.removeItem(`preview_blob_${selected_id}`); }, 60000); }; reader.readAsDataURL(doc_blob); }, error: (error: any) => { console.error('获取报告失败:', error); }, }); }
2. 修改预览组件
import { ActivatedRoute } from '@angular/router'; export class DocPreviewComponent implements OnInit, AfterViewInit, OnDestroy { doc_blob?: Blob; @ViewChild('doc_preview') doc_preview!: ElementRef; private selectedId?: string; constructor( private route: ActivatedRoute, private local_storage: LocalStorageService ) {} ngOnInit(): void { this.route.paramMap.subscribe(params => { this.selectedId = params.get('selected_id'); if (this.selectedId) { const base64Data = localStorage.getItem(`preview_blob_${this.selectedId}`); if (base64Data) { // 将Base64转成Blob const byteString = atob(base64Data.split(',')[1]); const mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0]; const ab = new ArrayBuffer(byteString.length); const ia = new Uint8Array(ab); for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } this.doc_blob = new Blob([ab], { type: mimeString }); this.renderDoc(); // 读取后立即清除存储 localStorage.removeItem(`preview_blob_${this.selectedId}`); } } }); } ngAfterViewInit(): void { if (this.doc_blob) { this.renderDoc(); } } private renderDoc(): void { if (this.doc_blob && this.doc_preview) { doc.renderAsync(this.doc_blob, this.doc_preview.nativeElement) .then(() => console.log("docx: finished")); } } ngOnDestroy(): void {} }
内容的提问来源于stack exchange,提问作者sakayevas
相关产品推荐
相关产品推荐

