Angular速成4 搭建一个markdown编辑器#

搭建一个markdown编辑器#

https://ace.c9.io 是一个挺好的文本编辑器 用 https://marked.js.org 编译markdown 用 https://highlightjs.org 做语法高亮


ViewChild#

https://angular.io/api/core/ViewChild

  • ViewChild修饰符可以让父组件获取子组件的值,或者说把子组件的值传给父组件。 是父子组件传数据的方法之一。是一种引用或者说关联。

  • 需要提供三个参数。 selector,read和static。默认只需传selector。

  • 可以用比如@ViewChild(“editor”)修饰一个组件类的成员a。 配和模板<div #editor>

这样如果a发生改变,模板也会改变。组件就可以对html元素进行控制。

  • html元素里用#是定义一个模板变量。比如<div #editor>

  • https://angular.io/guide/template-reference-variables

  • selector还可以直接传类等数据。这里不深入。

  • 如果我们用@ViewChild做了关联,并且要加一些初始化逻辑,那么应该在ngAfterViewInit()做。 如果在ngOnInit()里做可能会出问题,因为ngAfterViewInit()的时机更晚,可能并没有实际初始化完毕。


  • ace编辑器#

    ng new 起一个干净的项目

    ng add ace-builds 安装ace最新的1.4.13版本

    模板改为

    <div class="app-ace-editor" #editor style="width: 48%;height: 400px;"></div>
    

    定义一个模板变量editor

    组件改为

    import { Component, AfterViewInit, ElementRef, ViewChild } from '@angular/core';
    
    import * as ace from "ace-builds";
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.scss']
    })
    export class AppComponent implements AfterViewInit  {
      title = 'ng-test3';
    
      @ViewChild("editor") private editor!: ElementRef;
    
      private aceEditor: any;
    
      constructor() {
      }
    
      ngAfterViewInit(): void {
        ace.config.set("fontSize", "18px");
        ace.config.set('basePath', 'https://cdn.jsdelivr.net/npm/ace-builds@1.4.13/src-min-noconflict');
    
        this.aceEditor = ace.edit(this.editor.nativeElement);
        this.aceEditor.session.setValue("<p>wtf电池内部恶势力包括</p>");
        this.aceEditor.setTheme('ace/theme/github');
        this.aceEditor.session.setMode('ace/mode/markdown');
      }
    }
    

    引入了AfterViewInit,ElementRef,ViewChild,ace-builds。

    • ViewChild关联模板里的editor @ViewChild(“editor”) private editor!: ElementRef;

    • editor后面如果不加!,ts会报错,要求初始化。 加!告诉ts自己有把握,不用检查。

    • ngAfterViewInit里对editor初始化。 做font、mode、path、theme等的配置。

    • 此时ng serve后已经可以编辑文本了。


    marked#

    ng add marked 安装了4.0.4版本

    引入 import marked from "marked";

    会报错 error TS2307: Cannot find module 'marked' or its corresponding type declarations.

    因为缺少type的定义。对比项目下/node_modules/ace-builds和/node_modules/marked。 ace-builds里提供了ace-modules.d.ts和ace.d.ts,而marked没有。

    npm i @types/marked 可以安装对应的type,然后组件里import { marked } from "marked";可以编过了。

    暂时不太清楚这个type的来龙去脉和约定。 按 https://angular.io/guide/using-libraries 一般,库会包含.d.ts。 如果没有,就得找@types对应的库。 如果再没有,就得自己手动添加。

    模板里添加

    <div #finalHtml style="width: 48%;height: 600px; float:right; overflow:scroll"></div>
    

    组件

    import { Component, AfterViewInit, ElementRef, ViewChild } from '@angular/core';
    
    import * as ace from "ace-builds";
    import { marked } from "marked";
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.scss']
    })
    export class AppComponent implements AfterViewInit  {
      title = 'ng-test3';
    
      @ViewChild("editor") private editor!: ElementRef;
      @ViewChild('finalHtml') private finalHtml!: ElementRef;
    
      private aceEditor: any;
    
      constructor() {
      }
    
      ngAfterViewInit(): void {
        ace.config.set("fontSize", "18px");
        //ace.config.set('basePath', 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.13/');
        //ace.config.set('basePath', 'https://unpkg.com/ace-builds@1.4.12/src-noconflict');
        ace.config.set('basePath', 'https://cdn.jsdelivr.net/npm/ace-builds@1.4.13/src-min-noconflict');
    
        this.aceEditor = ace.edit(this.editor.nativeElement);
        this.aceEditor.session.setValue("* <p>wtf电池内部恶势力包括</p>");
        this.aceEditor.setTheme('ace/theme/github');
        this.aceEditor.session.setMode('ace/mode/markdown');
    
        this.aceEditor.on('change', () => this.onUpdateText());
      }
    
      onUpdateText(): void {
        this.finalHtml.nativeElement.innerHTML = marked.parse(this.aceEditor.session.getValue());
      }
    }
    

    finalHtml关联到模板。 监听ace的change事件。 当ace编辑器的文本发生改变时,用marked.parse()解析文本,并赋给finalHtml的innerHTML即可实时更新markdown显示。


    highlightjs#

    ng add highlight.js

    组件里引入

    import HighlightJS  from 'highlight.js';
    
    import c from 'highlight.js/lib/languages/c';
    import cpp from 'highlight.js/lib/languages/cpp';
    import python from 'highlight.js/lib/languages/python';
    import typescript from 'highlight.js/lib/languages/typescript';
    

    初始化时注册

    HighlightJS.registerLanguage('c', c);
    HighlightJS.registerLanguage('cpp', cpp);
    HighlightJS.registerLanguage('python', python);
    HighlightJS.registerLanguage('typescript', typescript);
    

    初始化marked时设置一下选项

    marked.setOptions({
      langPrefix:'hljs language-',
      highlight: function(code, lang) {
        const language = HighlightJS.getLanguage(lang) ? lang : 'plaintext';
        return HighlightJS.highlight(code, { language }).value;
      },
    });
    

    angular.json的styles里添加喜欢的风格 “./node_modules/highlight.js/scss/monokai-sublime.scss”


    参考 https://blog.shhdharmen.me/how-to-setup-ace-editor-in-angular https://blog.angular-university.io/angular-viewchild/ https://stackoverflow.com/questions/31548311/angular-html-binding https://pretagteam.com/question/cannot-find-module-or-its-corresponding-type-declarations-when-using-my-own-library