ASP.NET Core + Angular 4 教學 - CKEditor

-- Pageviews

ASP.NET Core + Angular 4 教學 - CKEditor 範例執行結果

本篇將介紹如何透過 TypeScript 把 CKeditor 包裝成 Angular 4 的 Directive,讓 Angular 4 能更方便使用 CKEditor。
並建立一個簡單的 ASP.NET Core Web API 跟 CKEditor 做存取資料的互動。

程式碼延續前兩篇的範例:
ASP.NET Core + Angular 4 教學 - 從無到有
ASP.NET Core + Angular 4 教學 - Webpack打包

安裝 npm 套件

安裝 ckeditor,指令如下:

1
npm install --save ckeditor

CKEditor

CKEditorDirective

因為 CKEditor 是透過 JavaScrupt 調用,為了方便在 Angular 中使用,我建立了一個 CKEditorDirective 包裝 CKEditor 成 Directive。
經由 CKEditorDirective 包裝後,使用存取 CKEditor 的值都是透過 ngModel,用起來就像 input text 一樣簡單。
shared\directives\ckeditor.directive.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { Directive, Input, Attribute, Output, EventEmitter, OnChanges } from "@angular/core";

declare var CKEDITOR: any;

@Directive({
selector: "[ckeditor][id][ngModel]"
})
export class CKEditorDirective implements OnChanges {
private ckeditor: any;
@Input() config: any;
@Input() ngModel: any;
@Output() ngModelChange = new EventEmitter();

constructor( @Attribute("id") private id: string) {
if (CKEDITOR === undefined || !id) {
throw new Error("The 'CKEDITOR' or the 'id' are not defined...");
}
}

ngAfterViewInit() {
if (this.ckeditor == null) {
this.ckeditor = CKEDITOR.replace(this.id, this.config);
}
this.ckeditor.on("change", () => this.ngModelChange.emit(this.ckeditor.getData()));
this.ckeditor.on("instanceReady", () => this.ckeditor.setData(this.ngModel));
}

ngOnChanges() {
if (this.ckeditor) {
this.ckeditor.setData(this.ngModel);
this.ngModelChange.emit(this.ngModel);
}
}
}

載入套件

在 NgModule 的 declarations 注入 CKEditorDirective。
app\main.ts

1
2
3
4
5
6
7
8
9
10
import { CKEditorDirective } from "./shared/directives/ckeditor.directive";

@NgModule({
// ...
declarations: [
// ...
CKEditorDirective
]
})
export class AppModule { }

因為是透過 npm 安裝 CKEditor,所以 CKEditor 相關的 plugins 跟 css 都在 node_modules/ckeditor 資料夾底下。
在 Startup.cs 的 Configure 加入靜態檔案路由,把 http://{domain}/ckeditor 指向 node_modules/ckeditor。
Startup.cs

1
2
3
4
5
6
7
8
9
10
public void Configure(IApplicationBuilder app)
{
// ...
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), @"node_modules/ckeditor")),
RequestPath = new PathString("/ckeditor")
});
}

CKEDITOR_BASEPATH 改為 Startup.cs 中設定的 http://{domain}/ckeditor 位置,並把 ckeditor 加入參考。
app\bundle-vendors.ts 加入以下程式碼:

1
2
3
4
// Define global variable
declare var CKEDITOR_BASEPATH: string;
eval(`CKEDITOR_BASEPATH = "/ckeditor/";`);
require("ckeditor");

CKEDITOR_BASEPATH 一定要在載入 ckeditor 前賦予設定值。

範例

app\components\editor.component.html

1
2
3
4
5
6
7
8
9
10
<h2>Editor</h2>

<div>{{message}}</div>
<br />

<textarea ckeditor id="editor" [(ngModel)]="content" [config]="ckeditorConfig"></textarea>
<div>
<input type="button" value="Send" (click)="send()" />
<input type="button" value="Clear" (click)="clear()" />
</div>

app\components\editor.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import { Component } from "@angular/core";
import { Http, Headers } from "@angular/http";
import { ResultModel } from "../shared/models/result.model";

@Component({
template: require("./editor.component.html")
})
export class EditorComponent {
private api: string = "/api/editor";
message: string;
content: string;
// config 請參考官網 https://docs.ckeditor.com/#!/api/CKEDITOR.config
ckeditorConfig: any = {
height: "300px"
};

constructor(private http: Http) {
}

ngAfterViewInit() {
this.http.get(this.api).subscribe(
(response) => {
let result: ResultModel = response.json();
if (!result.IsSuccess) {
this.showMessage(result.Message);
this.clear();
} else {
this.content = result.Data;
}
});
}

send(): void {
this.clearMessage();
let headers = new Headers({ "Content-Type": "application/json" });
this.http.put(this.api, JSON.stringify(this.content), { headers: headers }).subscribe(
(response) => {
let result: ResultModel = response.json();
if (!result.IsSuccess) {
this.showMessage(result.Message);
} else {
this.showMessage(`Saved successfully`);
}
});
}

clear(): void {
this.content = "";
}

clearMessage(): void {
this.message = "";
}

showMessage(message: string): void {
this.message = message;
}
}

Controllers\EditorController.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using Microsoft.AspNetCore.Mvc;
using MyWebsite.Models;

namespace MyWebsite.Controllers
{
[Route("api/[controller]")]
public class EditorController : Controller
{
private static string _content = string.Empty;

[HttpGet]
public ResultModel Get()
{
var result = new ResultModel();
result.Data = _content;
result.IsSuccess = result.Data != null;
return result;
}

[HttpPut]
public ResultModel Put([FromBody]string content)
{
var result = new ResultModel();
_content = content;
result.IsSuccess = true;
return result;
}
}
}

執行結果

ASP.NET Core + Angular 4 教學 - CKEditor 範例執行結果

範例程式碼

asp-net-core-angular-ckeditor