本文來自作者 孫亖 在 GitChat 上分享 「使用 Angular2+ 開發 Markdown 編輯器」
編輯 | 弗利薩
前言
一直想寫一個 Angular2+ 的分享,但是沒有一個好的切入點。前段時間我向分享 Chat 的白宦成老師請教 Markdown 的問題,他向我推薦 Typora 編輯器,我覺著這就是我一直想要的 Markdown 編輯器,於是我就想到了這個主題。
當然,我的水平一時是難於寫出 Typora 那樣的編輯器的,但人生已經如此艱難,大家就不要拆穿了,我主要是想通過一個實際應用分享一下 Angular(文中提到的 Angular 指的都是Angular2+,實際版本是 Angular5)的開發過程,主要內容包括:
Angular 項目建立
Angular 中的服務
Angular 中使用第三方傳統庫
打包桌面版本
製作一個安裝程序
Angular 項目的默認語言是 TypeScript,如果你不太熟悉請看我的另外一個Chat:TypeScript 快速入門
項目初始化說了一大堆,我們是要使用 Angular 來開發一個新的項目,如果你以前沒安裝過,那麼你需要先安裝 Node 和 npm。然後使用下面的命令來安裝 Angular/Cli,我們是通過 Angular/Cli 進行項目的管理。
npm install -g @angular/cli
如果你已經安裝過老版本的 Angular/Cli,建議你使用下面的命令,升級到最新的版本。
npm uninstall -g @angular/clinpm cache clean# if npm version is > 5 then use `npm cache verify` to avoid errors (or to avoid using --force)npm install -g @angular/cli@latest
升級過程中可能會報錯誤,類似於:
npm WARN ajv-keywords@3.1.0 requires a peer of ajv@^6.0.0 but none is installed. You must install peer dependencies your
self.
npm ERR! code EINTEGRITY
npm ERR! sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= integrity checksum failed when using sha1: wanted sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= but got sha1-D6HriBx1DgZRGWjwAGT4GuPfJE4=. (988 bytes)
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\sunjipeng\AppData\Roaming\npm-cache_logs\2018-02-21T06_26_16_216Z-debug.log
我們可以手動刪除解決:
刪除用戶目錄下./npmrc。
刪除 Roaming 目錄下 npm 和 npm_cache。
接下來我們要用下面的命令,新建一個項目:
ng new ngMDEditor
新建項目後,有可能因為大區域網的原因,部分依賴未成功安裝,可以通過 npm i 完成安裝,安裝之後,可以通過如下命令,運行預覽調試:
ng serve
先添加個依賴首先,我是一個前端渣渣;其次,開源世界有很多現成的東西供我們使用,不需要我們再花精力在我們不熟悉的領域。
現在我們依賴別人,以後依賴人工智慧,最後會不會替代?
要做一個 Markdown 編輯器,首先需要解析 Markdown,根據 Github 上 most stars 排序,JavaScript 類的 marked 排第一,網上用的人也比較多,但是我喜歡另外一個 markdown-it,主觀感覺特性豐富,而且插件比較多,有 MathJax 的插件,挺好用的。
我們先安裝 markdown-it,使用 npm 命令如下:
npm install markdown-it --save
因為 Angular 默認使用 Typescript,通常我們需要編寫聲明文件告訴 TypeScript 我們的 JavaScript 庫的存在,很多庫已經有現成的聲明文件,所以,使用 npm 安裝一下:
npm install --save @types/markdown-it
另外,我需要界面布局,前面說了,我是前端渣渣,所以我常常使用其他工具來布局和寫界面,我用得最多的就是 Bootstrap。
雖然這很不 Angular,還非常的 jQuery,但不得不承認如今這幾種框架混用的現象是非常普遍的。我們先安裝 Bootstrap,同樣使用 npm 命令:
npm install bootstrap
由於 Bootstrap 還依賴 jQuery 和 popper.js,所以我們用同樣的方法安裝這兩個:
npm i jquery popper.js
我們在根目錄下,打開 .angular-cli.json,編輯 scripts 項如下:
"scripts": [ "../node_modules/markdown-it/dist/markdown-it.min.js", "../node_modules/jquery/dist/jquery.min.js", "../node_modules/popper.js/dist/umd/popper.min.js", "../node_modules/bootstrap/dist/js/bootstrap.min.js" ],
這樣就算把這些第三方庫加入到框架中了,注意那個 poper 使用的是 umd 庫,用錯了不行,我們也可以在 index.html 中使用傳統方式引入,但這樣更專業一些,Bootstrap 還包含了樣式,我們修改上述文件的 styles 項如下:
"styles": [ "styles.css", "../node_modules/bootstrap/dist/css/bootstrap.min.css"],
類似的,我們可以在 styles.css 中引入項目文件,例如:
@import "~bootstrap/dist/css/bootstrap.min.css";
主界面開發接下來,我們就正式開始編寫一個 Markdown 語法的編輯器,借用流行的左右兩欄模式,左邊是 Markdown 語法,右邊是解析渲染後的樣子。我們用 Angular/Cli 命令行工具生成一個 component,也就是這個編輯器:
ng g c page/Editor
上面 ng 就是 Angular/Cli 的命令行工具,參數 g 是 generator 的簡寫,c 是 component 的簡寫,合起來意思就是生成一個組件,最後的參數就是組件的名稱和位置(相對於源碼)。好了,這是自動生成四個文件:
editor.component.css
editor.component.html
editor.component.spec.ts
editor.component.ts
看後綴我們知道 css 是樣式,html 是頁面,ts 是業務邏輯。我們先來把頁面寫出來:
<div class="container d-flex h-100 p-3 mx-auto flex-column"> <div class="row h-100"> <div class="col-6"> <textarea class="h-100 w-100">這是左邊</textarea> </div> <div class="col-6"> <p> 這是右邊 </p> </div> </div></div>
我不想詳細講太多 HTML 頁面知識,總之就是左邊一個文本輸入框,右邊在 p 元素內將頁面渲染出來。
雙向綁定我們要把文本區域的內容獲取,傳統的做法就是通過 getElementById 等方法獲取元素,然後根據不同元素的屬性獲取值,大多數時候使用 getElmentById(id).getValue()。
但是,我們現在不是傳統做法,用的是 Angular,可以使用雙向綁定。
雙向綁定說白了就是元素值和業務代碼裡的某個變量或方法是雙向流通的,詳細的內容可以參考官方文檔,但常用的方法我要說一下,方括號 [] 就是代碼值傳到頁面,圓括號 () 就是頁面值傳到代碼,事件就是頁面傳到代碼。
當只有一種括號時是單向綁定,兩種括號一起用時就是雙向綁定。例如:
<input type="text" [(value)]="srcTxt"/>
對於 form 控制項,更常用的是 ngModel,使用 ngModel 前我們需要在 module 中引入 FormsModule:
imports: [ BrowserModule, FormsModule ],
基於我們前面的需求,現在代碼中新增一個屬性 srcTxt 保存文本區域輸入的 Markdown 代碼:
srcTxt:string = "源文件"
然後,在頁面上通過 ngModel 綁定此屬性:
<textarea class="h-100 w-100" [(ngModel)]="srcTxt">這是左邊</textarea>
解析 Markdown 並顯示我們現在通過 srcTxt 獲取了 Markdown 源碼,接下來我們通過 Markdown-it 解析出結果並顯示出來。
前面我們已經添加了 Markdown-it 的類型庫,現在我們引入它就可以使用:
import * as MarkdownIt from 'markdown-it'
當 Markdown 源碼改變時,我們需要重新解析源碼,因此在頁面添加監聽 ngModel 的改變:
<textarea class="h-100 w-100" [(ngModel)]="srcTxt" (ngModelChange)="srcChanged($event)">這是左邊</textarea>
在邏輯代碼中實現這個監聽,解析源碼為 HTML 格式:
srcChanged($event) { var md = new MarkdownIt(); this.outTxt = md.render($event) }
這裡的 outTxt 是我們定義的一個結果屬性,通過頁面將這個結果展現出去,頁面就完成了。
<div [innerHTML]="outTxt"></div>
另外一種方式是使用 pipe,Angular 裡面 pipe 的目的就是將輸入的值轉換為目標值,比如時間、貨幣、數字的格式化等。我們先寫一個 Markdown 解析轉換的 pipe,先生成一個:
ng g pipe pipe/Md
Angular 中內置了一些 pipe,也可以自定義 pipe,我們修改生成的 pipe,這裡的代碼很簡單:
transform(value: any, args?: any): any { var md = new MarkdownIt(); return md.render(value); }
相應的,界面上需要根據 pipe 的用法改變:
<div class="col-6"> <span> <div [innerHTML]="srcTxt | md"></div> </span></div>
支持數學公式首先,我們從這裡下載一個 MathJax 的庫,本來 Markdown-it 有 MathJax 的擴展,但是使用起來也有各種問題,就只好直接使用 MathJax 官網庫,但編譯後提示有文件找不到,最後就找了這麼一個單文件庫,總算可以了。直接加載文件是這樣配置 .angular-cli.json 的:
"scripts": [ ... "./assets/MathJax.min.js" ],
導入庫後需要配置,由於我們不是用 npm 導入的,也沒有類型庫,所以我們先定義以下,然後再配置:
declare var MathJax:any ngOnInit() { MathJax.Hub.Config({tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]}}); }
這次我們編寫一個 directive 來實現這個功能,本來我打算通過 pipe,或者監聽文本變化用 MathJax 解析公式,但是由於 MathJax 解析方法不太好實現,所以我們用 directive:
ng g directive directive/MathJax
directive 代碼如下:
import { Directive, Input, ElementRef } from '@angular/core';declare var MathJax:any@Directive({ selector: '[MathJax]'})export class MathJaxDirective { @Input('MathJax') fractionString: string; constructor(private el: ElementRef) { } ngOnChanges() { this.el.nativeElement.innerHTML = this.fractionString; MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.el.nativeElement]); }}
最後,我們在標籤上使用 directive 來處理公式:
<div [MathJax]="outTxt"></div>
這是我們輸出公式就可以正常顯示了。
保存編輯到資料庫這一節我們主要是想講 Angular 使用 HttpClient 與遠程伺服器通訊的功能,這是技術上的,從功能上我們還可以將各種功能進行設計,在 Angular 中可以將它們抽象成服務,新建一個服務如下:
ng g s service/Md
通過上面的命令,我們在 Service 目錄下新建了一個 Md 服務,我們要在這個服務中,添加一個將 Markdown 文檔保存到伺服器的功能。
Angular 通過 HttpClient 來和遠程服務通訊,HttpClient 也是一個服務,要是用這個服務我們首先要在 app.module.ts 中導入 HttpClientModule,app.module.ts 變成了如下內容:
@NgModule({ declarations: [ ... ], imports: [ ... HttpClientModule, ... ], providers: [], bootstrap: [AppComponent]})export class AppModule { }
然後,我們將 HttpClient 注入到要使用的地方,這裡具體就是 Md 服務:
import { Injectable } from '@angular/core';import { HttpClient } from '@angular/common/http';@Injectable()export class MdService { constructor(private http:HttpClient) { }}
現在,我們就可以通過 HttpClient 來保存數據了,新建一個服務方法:
save(id:number, title:string, content:string) { return this.http.post("http://localhost:8080/post", {id: id, title:title, content:content})}
有了服務之後,我們需要在界面中調用它,首先,需要在 app.module.ts 的 providers 中定義:
@NgModule({ declarations: [ AppComponent, EditorComponent, MdPipe ], imports: [ BrowserModule, FormsModule, HttpClientModule ], providers: [MdService], bootstrap: [AppComponent]}) export class AppModule { }
同樣和 HttpClient 一樣,我們需要在使用的地方注入它,我們在 Editor 頁面新建一個按鈕事件:
<div class="row"> <button type="button" class="btn btn-primary" (click)="doSave()">保存</button> </div> <div class="row"> <input type="text" [(ngModel)]="title"> </div>
按鈕點擊通過圓括號綁定到 doSave 方法,doSave 方法通過注入的 Md 服務保存內容到伺服器:
constructor(private mdSvc:MdService) { } doSave() { this.mdSvc.save(null, this.title, this.srcTxt).subscribe(data=> { alert("保存成功") }, err => { alert("保存失敗") }) }
我這裡僅演示一下,服務使用的是我前面的 Chat:Kotlin開發SpringBoot之Data JPA,有請興趣的朋友可以閱讀該 Chat,另外一個編輯器的功能不單是保存,還有打開等功能,就留作大家的作業吧。
Electron 打包成桌面應用現在前端的發展其實是很快的,網頁、桌面、手機一網打盡了,Angular 的主頁寫的就是一種框架,適於多個平臺。
把 JavaScript 項目打包成桌面,現在主流有兩種方案 NW.js 和 Electron,我查找學習的資料大多是 Electron,所以這裡我們來看看如何用 Electron 把 Angular 項目打包成桌面應用。
Electron 打包桌面應用說白了就是網頁套個殼,高級點可以提供 API 訪問本地原生功能,基於此,我們甚至可以給一個網站做個桌面應用,我們先初始化一個 npm 的空項目:
掃描下方二維碼
閱讀完整原文