Angular 5中ngModel变更时如何重新渲染Prism.js
解决Angular中Prism.js在文章内容变更时的高亮问题
我来帮你搞定这个Prism.js高亮的问题~首先咱们先弄明白为什么onBodyChange无效,然后再一步步实现Prism.highlightElement的方案。
问题根源
你遇到的onBodyChange无效的原因很简单:当ngModelChange触发时,Angular还没来得及把新的article.body渲染到预览区域的DOM里。这时候调用highlightAll,Prism找不到刚要生成的代码块,自然没效果。而ngAfterViewChecked是在视图完全更新后触发的,所以能找到代码块,但这个钩子会在每次视图变更时都触发(比如组件其他变量变化),有点浪费性能。
用Prism.highlightElement精准高亮的实现步骤
1. 给预览区域加模板引用
假设你的预览区域是用[innerHTML]渲染文章内容的,先给它加个模板引用变量,方便后续获取DOM:
<!-- 你的预览模板 --> <div #previewContainer class="article-preview" [innerHTML]="article.body"></div>
2. 扩展HighlightService(可选但推荐)
在你的HighlightService里新增一个针对单个元素的高亮方法,这样组件里不用直接操作Prism,更符合Angular的依赖注入思想:
// highlight.service.ts import { Injectable, Inject } from '@angular/core'; import { PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import 'clipboard'; import 'prismjs'; import 'prismjs/plugins/toolbar/prism-toolbar'; import 'prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard'; import 'prismjs/components/prism-css'; import 'prismjs/components/prism-javascript'; import 'prismjs/components/prism-markup'; import 'prismjs/components/prism-typescript'; import 'prismjs/components/prism-scss'; declare var Prism: any; @Injectable() export class HighlightService { constructor(@Inject(PLATFORM_ID) private platformId: Object) { } highlightAll() { if (isPlatformBrowser(this.platformId)) { Prism.highlightAll(); } } // 新增:高亮单个元素 highlightElement(element: HTMLElement) { if (isPlatformBrowser(this.platformId)) { Prism.highlightElement(element); } } }
3. 修改组件逻辑,在DOM更新后触发高亮
在组件里通过ViewChild获取预览容器,然后在onBodyChange里等待DOM更新完成后,找到所有代码块并逐个高亮:
// 你的组件.ts import { Component, ViewChild, ElementRef } from '@angular/core'; import { PLATFORM_ID, Inject } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { HighlightService } from './highlight.service'; @Component({ // 组件元数据 }) export class YourArticleComponent { article = { body: '' }; highlighted = false; @ViewChild('previewContainer') previewContainer!: ElementRef; constructor( private highlightService: HighlightService, @Inject(PLATFORM_ID) private platformId: Object ) {} onBodyChange() { // 用setTimeout等待Angular完成DOM更新(0秒延迟足够) setTimeout(() => { if (this.previewContainer && isPlatformBrowser(this.platformId)) { // 获取预览区域内所有的<pre><code>元素 const codeElements = this.previewContainer.nativeElement.querySelectorAll('pre code'); // 逐个调用高亮方法 codeElements.forEach((el: HTMLElement) => { this.highlightService.highlightElement(el); }); this.highlighted = true; } }, 0); } // 可以把ngAfterViewChecked的逻辑去掉了,用上面的方法更高效 // ngAfterViewChecked() { ... } }
优化建议:添加防抖提升性能
如果用户连续输入内容,每次都触发高亮会有点浪费性能。可以用RxJS的防抖功能,等用户停止输入300ms后再触发高亮:
// 先引入RxJS相关工具 import { Subject } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; // 组件里新增一个Subject private bodyChange$ = new Subject<void>(); ngOnInit() { // 订阅防抖后的事件 this.bodyChange$.pipe(debounceTime(300)).subscribe(() => { this.updateHighlight(); }); } onBodyChange() { // 只发送事件,不直接执行高亮 this.bodyChange$.next(); } // 把高亮逻辑抽成单独方法 private updateHighlight() { setTimeout(() => { if (this.previewContainer && isPlatformBrowser(this.platformId)) { const codeElements = this.previewContainer.nativeElement.querySelectorAll('pre code'); codeElements.forEach((el: HTMLElement) => { this.highlightService.highlightElement(el); }); this.highlighted = true; } }, 0); }
这样用户快速输入时不会频繁触发DOM操作,体验会更流畅~
内容的提问来源于stack exchange,提问作者user6030397
相关产品推荐
相关产品推荐

