用Angular实现Markdown渲染的管道
哈喽!你好,很高兴见到你在奥斯汀!
简而言之
今天我想介绍一种使用Angular将Markdown渲染为HTML的方法!
在中国, 我们只需要一种选择。
以前,我介绍了如何使用Lit的Web Components来渲染Markdown的方法。
在Angular中,您也可以使用这个Web Component来渲染Markdown。而且,我认为这也是一个非常好的在框架中利用Web Component的例子。
然而,Angular为我们提供了强大的工具来转换数据。
这个工具叫做”Pipes”(管道)。
本篇文章介绍了如何制作自定义管道并将Markdown转换为HTML的方法。
Angular的管道是什么?
Angular的管道是Angular的HTML模板中用于转换字符串数据显示的便捷功能。
有许多现有的管道。
让我们在某个组件的 HTML 模板中尝试使用 UpperCasePipe。
content_copy
import { Component } from '@angular/core';
@Component({
selector: 'app-hero-birthday',
templateUrl: './my-component.component.html',
})
export class HeroBirthdayComponent {
public name = "austin"
}
<h1>{{ this.name | uppercase }}</h1>
然后会显示如下内容:
AUSTIN
有很多这样的管道,它们是非常强大的工具。通过管道来转换显示内容是非常典型的 Angular 特色。
因此,我想要将Markdown字符串转换为HTML字符串,所以得出了使用管道的结论。
安装 marked.js
由于从零开始制作能够渲染Markdown的软件非常困难,因此我们会使用开源的优秀软件。
请使用以下CLI命令将其添加到Angular项目中。
npm install marked
添加自定义管道
在现有的Angular项目中,执行以下CLI命令将markdown.pipe.ts添加到项目中。
ng generate pipe pipes/markdown
※ 不需要管道,但筆者喜歡將管道放入特定的文件夾,因此這樣指定。
于是,在src/app/pipes目录下会生成两个文件:markdown.pipe.spec.ts和markdown.pipe.ts。但是,这次我们只关注markdown.pipe.ts这个文件。
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'markdown'
})
export class MarkdownPipe implements PipeTransform {
transform(value: unknown, ...args: unknown[]): unknown {
return null;
}
}
这个transform类的函数能够执行一些神奇的操作。它会返回一个在HTML中渲染的结果。
使用类似于Markdown的逻辑输入,如下所示:Lit的Markdown
import { Pipe, PipeTransform } from "@angular/core";
import { marked } from "marked";
@Pipe({
name: "markdown",
})
export class MarkdownPipe implements PipeTransform {
transform(rawMarkdown: string) {
return new Promise<string>((resolve, reject) => {
marked.parse(rawMarkdown, (error, result) => {
if (error) return reject(error);
resolve(result);
});
});
}
}
在这里出现了两个令人担忧的问题。
-
- transform函数能否返回一个Promise?
- 如果返回HTML字符串,Angular会进行转义吗?
我们一起解决这两个问题吧。
用管道将Promise转换
首先,让我们尝试一下上述的管道。
import { Component } from "@angular/core";
import { startAppCheck } from "@firebase/app-check-module";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class AppComponent {
public raw = `# Hello World
I am a paragraph`;
}
<div>{{ this.raw | markdown }}</div>
结果 (jié guǒ)
嗯,大概就是这样了吧。
只是显示了Promise.prototype.toString()的结果,而不是Promise的结果。
在这里可以帮助你的是现有的AsyncPipe。
在上述HTML模板的管道位置添加一个管道符号。
<div>{{ this.raw | markdown | async }}</div>
保存后,界面会发生变化!
然而,问题还没有完全解决。
如何让生HTML在Angular中可靠地运行
由于安全原因,Angular默认会对未经处理的HTML进行正确的转义。
尽管这个功能原本很有用,但在需要显示Markdown渲染结果的HTML时却会变得妨碍。
在这种情况下,您可以将管道的结果绑定到innerHTML。
<div [innerHTML]="this.raw | markdown | async"></div>
结果 (jié guǒ)
结果很理想,是吧!
使用管道将 DomSanitizer 用于中文
在markdown.pipe.ts中,您还可以注入名为DomSanitizer的库,并指定它是一个应显式避免转义的值。
import { Pipe, PipeTransform } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { marked } from "marked";
@Pipe({
name: "markdown1",
})
export class Markdown1Pipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(rawMarkdown: string) {
return new Promise<string>((resolve, reject) => {
marked.parse(rawMarkdown, (error, result) => {
if (error) return reject(error);
resolve(result);
});
}).then((rawHTML) => {
const bypassedHTML = this.sanitizer.bypassSecurityTrustHtml(rawHTML);
return bypassedHTML;
});
}
}
然后,会得到与上述相同的结果,但这是非常危险的行为。
将app.component.ts中的raw值更改如下:
import { Component } from "@angular/core";
import { startAppCheck } from "@firebase/app-check-module";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class AppComponent {
public raw = `# Hello World
<script>alert("Hacked!");</script>
I am a paragraph`; // XSS攻撃!アキサミヨー!
ngAfterViewInit(): void {
startAppCheck();
}
}
然后, 页面会呈现以下的HTML。
正如上述的DOM树所示,从Markdown中插入