Skip to content

Instantly share code, notes, and snippets.

@KangHidro
Last active July 23, 2025 07:43
Show Gist options
  • Save KangHidro/4db037bbdcb93904a253c4dac9c8e7ff to your computer and use it in GitHub Desktop.
Save KangHidro/4db037bbdcb93904a253c4dac9c8e7ff to your computer and use it in GitHub Desktop.
Angular CK-Editor Standalone component
/* @import 'ckeditor5/ckeditor5.css'; */
@media print {
body {
margin: 0 !important;
}
}
.ck-content {
font-family: 'Lato';
line-height: 1.6;
word-break: break-word;
}
.ck-content h3.category {
font-family: 'Oswald';
font-size: 20px;
font-weight: bold;
color: #555;
letter-spacing: 10px;
margin: 0;
padding: 0;
}
.ck-content h2.document-title {
font-family: 'Oswald';
font-size: 50px;
font-weight: bold;
margin: 0;
padding: 0;
border: 0;
}
.ck-content h3.document-subtitle {
font-family: 'Oswald';
font-size: 20px;
color: #555;
margin: 0 0 1em;
font-weight: bold;
padding: 0;
}
.ck-content p.info-box {
--background-size: 30px;
--background-color: #e91e63;
padding: 1.2em 2em;
border: 1px solid var(--background-color);
background: linear-gradient(
135deg,
var(--background-color) 0%,
var(--background-color) var(--background-size),
transparent var(--background-size)
),
linear-gradient(
135deg,
transparent calc(100% - var(--background-size)),
var(--background-color) calc(100% - var(--background-size)),
var(--background-color)
);
border-radius: 10px;
margin: 1.5em 2em;
box-shadow: 5px 5px 0 #ffe6ef;
}
.ck-content blockquote.side-quote {
font-family: 'Oswald';
font-style: normal;
float: right;
width: 35%;
position: relative;
border: 0;
overflow: visible;
z-index: 1;
margin-left: 1em;
}
.ck-content blockquote.side-quote::before {
content: '“';
position: absolute;
top: -37px;
left: -10px;
display: block;
font-size: 200px;
color: #e7e7e7;
z-index: -1;
line-height: 1;
}
.ck-content blockquote.side-quote p {
font-size: 2em;
line-height: 1;
}
.ck-content blockquote.side-quote p:last-child:not(:first-child) {
font-size: 1.3em;
text-align: right;
color: #555;
}
.ck-content span.marker {
background: yellow;
}
.ck-content span.spoiler {
background: #000;
color: #000;
}
.ck-content span.spoiler:hover {
background: #000;
color: #fff;
}
.ck-content pre.fancy-code {
border: 0;
margin-left: 2em;
margin-right: 2em;
border-radius: 10px;
}
.ck-content pre.fancy-code::before {
content: '';
display: block;
height: 13px;
background: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NCAxMyI+CiAgPGNpcmNsZSBjeD0iNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGMzZCNUMiLz4KICA8Y2lyY2xlIGN4PSIyNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGOUJFNEQiLz4KICA8Y2lyY2xlIGN4PSI0Ny41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiM1NkM0NTMiLz4KPC9zdmc+Cg==);
margin-bottom: 8px;
background-repeat: no-repeat;
}
.ck-content pre.fancy-code-dark {
background: #272822;
color: #fff;
box-shadow: 5px 5px 0 #0000001f;
}
.ck-content pre.fancy-code-bright {
background: #dddfe0;
color: #000;
box-shadow: 5px 5px 0 #b3b3b3;
}
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
import { FormGroup, FormsModule } from '@angular/forms';
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
import {
AccessibilityHelp,
Alignment,
Autoformat,
AutoImage,
Autosave,
BalloonToolbar,
BlockQuote,
Bold,
ClassicEditor,
Code,
CodeBlock,
Essentials,
FindAndReplace,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
GeneralHtmlSupport,
Heading,
Highlight,
HorizontalLine,
HtmlEmbed,
ImageBlock,
ImageCaption,
ImageInline,
ImageInsert,
ImageInsertViaUrl,
ImageResize,
ImageStyle,
ImageTextAlternative,
ImageToolbar,
ImageUpload,
Indent,
IndentBlock,
Italic,
Link,
LinkImage,
List,
ListProperties,
MediaEmbed,
Paragraph,
PasteFromOffice,
RemoveFormat,
SelectAll,
SimpleUploadAdapter,
SourceEditing,
SpecialCharacters,
SpecialCharactersArrows,
SpecialCharactersCurrency,
SpecialCharactersEssentials,
SpecialCharactersLatin,
SpecialCharactersMathematical,
SpecialCharactersText,
Strikethrough,
Style,
Subscript,
Superscript,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TextTransformation,
TodoList,
Underline,
Undo,
type EditorConfig
} from 'ckeditor5';
import { timer } from 'rxjs';
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'cke-editor',
imports: [FormsModule, CKEditorModule],
template: `@if (ckeReady) { <ckeditor [editor]="editor" [config]="config" [ngModel]="ckeData" (ngModelChange)="onChange($event)" /> }
@else { <div class="w-full text-center italic">Loading editor...</div> }`,
styleUrls: [
'../../../../../node_modules/ckeditor5/dist/ckeditor5.css',
'./cke-editor.component.css',
],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
})
export class CkeEditorComponent implements AfterViewInit {
@Input() ckeData = '';
@Input() refFormGroup: FormGroup | null = null;
@Input() refFormControlName = '';
@Output() ckeDataChange = new EventEmitter<string>();
ckeReady = false;
editor = ClassicEditor;
config: EditorConfig = {
// Why licenseKey: https://github.com/ckeditor/ckeditor5/blob/master/docs/getting-started/licensing/license-key-and-activation.md#using-the-gpl-key
// Install version "^43.3.1" will not need this "licenseKey" field
licenseKey: 'GPL',
toolbar: {
items: [
'heading', '|', 'removeFormat', 'alignment', 'bold', 'italic', 'underline',
'strikethrough', 'link', 'insertImage', 'mediaEmbed', 'highlight', '|',
'outdent', 'indent', 'specialCharacters', '|', 'fontSize',
'fontBackgroundColor', 'fontColor', 'fontFamily', 'bulletedList',
'numberedList', 'insertTable', 'subscript', 'superscript', 'blockQuote',
'undo', 'redo', 'findAndReplace', 'codeBlock', 'code', 'htmlEmbed',
'horizontalLine', 'selectAll', '|', 'sourceEditing'
],
shouldNotGroupWhenFull: false
},
plugins: [
AccessibilityHelp,
Alignment,
Autoformat,
AutoImage,
Autosave,
BalloonToolbar,
BlockQuote,
Bold,
Code,
CodeBlock,
Essentials,
FindAndReplace,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
GeneralHtmlSupport,
Heading,
Highlight,
HorizontalLine,
HtmlEmbed,
ImageBlock,
ImageCaption,
ImageInline,
ImageInsert,
ImageInsertViaUrl,
ImageResize,
ImageStyle,
ImageTextAlternative,
ImageToolbar,
ImageUpload,
Indent,
IndentBlock,
Italic,
Link,
LinkImage,
List,
ListProperties,
MediaEmbed,
Paragraph,
PasteFromOffice,
RemoveFormat,
SelectAll,
SimpleUploadAdapter,
SourceEditing,
SpecialCharacters,
SpecialCharactersArrows,
SpecialCharactersCurrency,
SpecialCharactersEssentials,
SpecialCharactersLatin,
SpecialCharactersMathematical,
SpecialCharactersText,
Strikethrough,
Style,
Subscript,
Superscript,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TextTransformation,
TodoList,
Underline,
Undo
],
balloonToolbar: ['bold', 'italic', 'underline', 'strikethrough', '|', 'link', 'highlight'],
fontFamily: {
supportAllValues: true
},
fontSize: {
options: [10, 12, 14, 'default', 18, 20, 22],
supportAllValues: true
},
heading: {
options: [
{
model: 'paragraph',
title: 'Paragraph',
class: 'ck-heading_paragraph'
},
{
model: 'heading1',
view: 'h1',
title: 'Heading 1',
class: 'ck-heading_heading1'
},
{
model: 'heading2',
view: 'h2',
title: 'Heading 2',
class: 'ck-heading_heading2'
},
{
model: 'heading3',
view: 'h3',
title: 'Heading 3',
class: 'ck-heading_heading3'
},
{
model: 'heading4',
view: 'h4',
title: 'Heading 4',
class: 'ck-heading_heading4'
},
{
model: 'heading5',
view: 'h5',
title: 'Heading 5',
class: 'ck-heading_heading5'
},
{
model: 'heading6',
view: 'h6',
title: 'Heading 6',
class: 'ck-heading_heading6'
}
]
},
htmlSupport: {
allow: [
{
name: /^.*$/,
styles: true,
attributes: true,
classes: true
}
]
},
image: {
toolbar: [
'toggleImageCaption',
'imageTextAlternative',
'|',
'imageStyle:inline',
'imageStyle:wrapText',
'imageStyle:breakText',
'|',
'resizeImage'
]
},
initialData: '',
link: {
addTargetToExternalLinks: true,
defaultProtocol: 'https://',
decorators: {
toggleDownloadable: {
mode: 'manual',
label: 'Downloadable',
attributes: {
download: 'file'
}
}
}
},
list: {
properties: {
styles: true,
startIndex: true,
reversed: true
}
},
menuBar: {
isVisible: true
},
placeholder: '',
style: {
definitions: [
{
name: 'Article category',
element: 'h3',
classes: ['category']
},
{
name: 'Title',
element: 'h2',
classes: ['document-title']
},
{
name: 'Subtitle',
element: 'h3',
classes: ['document-subtitle']
},
{
name: 'Info box',
element: 'p',
classes: ['info-box']
},
{
name: 'Side quote',
element: 'blockquote',
classes: ['side-quote']
},
{
name: 'Marker',
element: 'span',
classes: ['marker']
},
{
name: 'Spoiler',
element: 'span',
classes: ['spoiler']
},
{
name: 'Code (dark)',
element: 'pre',
classes: ['fancy-code', 'fancy-code-dark']
},
{
name: 'Code (bright)',
element: 'pre',
classes: ['fancy-code', 'fancy-code-bright']
}
]
},
table: {
contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties']
},
simpleUpload: {
// uploadUrl: UrlConstant.API.CKE_IMG,
uploadUrl: 'http://aaa.aaa',
withCredentials: true,
headers: {
// Authorization: `Bearer ${JSON.parse(localStorage.getItem(SystemConstant.CURRENT_INFO) ?? '{}')?.token}`
}
},
mediaEmbed: {
previewsInData: true,
// Block mediaEmbed from domain;
// Docs: https://ckeditor.com/docs/ckeditor5/latest/api/module_media-embed_mediaembed-MediaEmbedConfig.html
// removeProviders: [],
},
extraPlugins: [],
removePlugins: [],
};
constructor(private cdr: ChangeDetectorRef) { }
ngAfterViewInit(): void {
if (this.refFormControlName && this.refFormGroup) {
this.refFormGroup?.get(this.refFormControlName)?.valueChanges.subscribe((newValue: string) => {
this.ckeData = newValue;
this.cdr.detectChanges();
});
this.ckeData = this.refFormGroup?.get(this.refFormControlName)?.value;
}
timer(100).subscribe(() => {
this.ckeReady = true;
this.cdr.detectChanges();
});
}
onChange(newValue: string) {
if (this.refFormControlName && this.refFormGroup) {
this.refFormGroup?.get(this.refFormControlName)?.setValue(newValue);
}
this.ckeDataChange.emit(newValue);
}
}

Install Package:

"ckeditor5": "^45.2.1",
"@ckeditor/ckeditor5-angular": "^9.1.0",

HTML:

Binding: <cke-editor [(ckeData)]="ckeData"></cke-editor>

Form: <cke-editor [refFormGroup]="exampleForm" [refFormControlName]="'formControlNameAbc'"></cke-editor>

CSS:

TailwindCSS (Optional, Check lines 80-81 in the cke-editor.component.ts if you do not use this library)

TS:

@Component({
...
imports: [CkeEditorComponent],
...
})

Demo

SS-Microsoft Edge -2025-06-26 at 11 02 32

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment