menu-title: Angular 2+ component category: builds-integration-frameworks
CKEditor 5 consists of {@link builds/guides/overview ready-to-use editor builds} and {@link framework/guides/overview CKEditor 5 Framework} upon which the builds are based.
Currently, the CKEditor 5 component for Angular supports integrating CKEditor 5 only via builds. Integrating {@link builds/guides/integration/advanced-setup#scenario-2-building-from-source CKEditor 5 built from source} is not possible yet due to the lack of ability to adjust webpack configuration in angular-cli.
While there is no support to integrate CKEditor 5 from source yet, you can still {@link builds/guides/development/custom-builds create a custom build of CKEditor 5} and include it in your Angular application.
In your existing Angular project, install the CKEditor 5 WYSIWYG editor component for Angular 2+:
npm install --save @ckeditor/ckeditor5-angular
Install one of the {@link builds/guides/overview#available-builds official editor builds} or {@link builds/guides/development/custom-builds create a custom one} (e.g. if you want to install more plugins or customize something that cannot be controlled with the {@link builds/guides/integration/configuration editor configuration}).
Assuming that you picked @ckeditor/ckeditor5-build-classic:
npm install --save @ckeditor/ckeditor5-build-classic
Now, add CKEditorModule to your application module imports:
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
@NgModule( {
imports: [
CKEditorModule,
// ...
],
// ...
} )
Import the editor build in your Angular component and assign it to a public property so it becomes accessible in the template:
import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
@Component( {
// ...
} )
export class MyComponent {
public Editor = ClassicEditor;
// ...
}
Finally, use the <ckeditor> tag in the template to run the rich text editor:
<ckeditor [editor]="Editor" data="<p>Hello, world!</p>"></ckeditor>
Rebuild your application and CKEditor 5 should greet you with "Hello, world!".
If you want to use the {@link framework/guides/document-editor Document editor build}, you need to {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create add the toolbar to the DOM manually}.
import * as DecoupledEditor from '@ckeditor/ckeditor5-build-decoupled-document';
@Component( {
// ...
export class MyComponent {
public Editor = DecoupledEditor;
public onReady( editor ) {
editor.ui.getEditableElement().parentElement.insertBefore(
editor.ui.view.toolbar.element,
editor.ui.getEditableElement()
);
}
}
And then, in the template:
<ckeditor [editor]="Editor" data="<p>Hello, world!</p>" (ready)="onReady($event)"></ckeditor>
ngModelThe component implements the ControlValueAccessor interface and works with the ngModel. Here is how to use it:
Create some model in your component to share with the editor:
@Component( {
// ...
} )
export class MyComponent {
public model = {
editorData: '<p>Hello, world!</p>'
};
// ...
}
Use the model in the template to enable a two–way data binding:
<ckeditor [(ngModel)]="model.editorData" [editor]="Editor"></ckeditor>
@Input propertiesThe following @Input properties are supported by the CKEditor 5 component for Angular 2+:
editor (required)The {@link builds/guides/integration/basic-api Editor} which provides the static {@link module:core/editor/editor~Editor.create create()} method to create an instance of the editor:
<ckeditor [editor]="Editor"></ckeditor>
configThe {@link module:core/editor/editorconfig~EditorConfig configuration} of the editor:
<ckeditor [config]="{ toolbar: [ 'heading', '|', 'bold', 'italic' ] }" ...></ckeditor>
dataThe initial data of the editor. It can be a static value:
<ckeditor data="<p>Hello, world!</p>" ...></ckeditor>
or a shared parent component's property
@Component( {
// ...
} )
export class MyComponent {
public editorData = '<p>Hello, world!</p>';
// ...
}
<ckeditor [data]="editorData" ...></ckeditor>
tagNameSpecifies the tag name of the HTML element on which the editor will be created.
The default tag is <div>.
<ckeditor tagName="textarea" ...></ckeditor>
disabledControls the editor's {@link module:core/editor/editor~Editor#isReadOnly read–only} state:
@Component( {
// ...
} )
export class MyComponent {
public isDisabled = false;
// ...
toggleDisabled() {
this.isDisabled = !this.isDisabled
}
}
<ckeditor [disabled]="isDisabled" ...></ckeditor>
<button (click)="toggleDisabled()">
{{ isDisabled ? 'Enable editor' : 'Disable editor' }}
</button>
@Output propertiesThe following @Output properties are supported by the CKEditor 5 component for Angular 2+:
readyFired when the editor is ready. It corresponds with the editor#ready event. Fired with the editor instance.
changeFired when the content of the editor has changed. It corresponds with the {@link module:engine/model/document~Document#event:change:data editor.model.document#change:data} event.
Fired with an object containing the editor and the CKEditor 5 change:data event object.
<ckeditor [editor]="Editor" (change)="onChange($event)"></ckeditor>
import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import { ChangeEvent } from '@ckeditor/ckeditor5-angular/ckeditor.component';
@Component( {
// ...
} )
export class MyComponent {
public Editor = ClassicEditor;
public onChange( { editor }: ChangeEvent ) {
const data = editor.getData();
console.log( data );
}
...
}
blurFired when the editing view of the editor is blurred. It corresponds with the {@link module:engine/view/document~Document#event:blur editor.editing.view.document#blur} event.
Fired with an object containing the editor and the CKEditor 5 blur event data.
focusFired when the editing view of the editor is focused. It corresponds with the {@link module:engine/view/document~Document#event:focus editor.editing.view.document#focus} event.
Fired with an object containing the editor and the CKEditor 5 focus event data.
The CKEditor 5 component for Angular can be styled using the component stylesheet or using a global stylesheet. Let's see how to set the CKEditor 5 component's height using these two approaches.
First, create a (S)CSS file in the parent component's directory and style the given editor's part preceded by the :host and ::ng-deep pseudo selectors.
/* src/app/app.component.css */
:host ::ng-deep .ck-editor__editable_inline {
min-height: 500px;
}
Then in the parent component add the relative path to the above stylesheet.
/* src/app/app.component.ts */
@Component( {
// ...
styleUrls: [ './app.component.css' ]
} )
To style the component using a global stylesheet, first, create it:
/* src/styles.css */
.ck-editor__editable_inline {
min-height: 500px;
}
Then, add it in the angular.json configuration file.
"architect": {
"build": {
"options": {
"styles": [
{ "input": "src/styles.css" }
]
}
}
}
To display {@link features/editor-placeholder the placeholder} in the main editable element simply set the placeholder field in the CKEditor 5 component configuration:
@Component( {
// ...
} )
export class MyComponent {
public config = {
placeholder: 'Type the content here!'
}
}
The <ckeditor> component will provide all the functionality needed for most use cases. In cases where access to the full CKEditor 5 API is needed it is easy to access the editor with an additional step.
To do this create a template reference variable #editor pointing the <ckeditor> component:
<ckeditor #editor [editor]="Editor" ...></ckeditor>
Then get the <ckeditor> component using a property decorated by @ViewChild( 'editor' ) and access the editor instance when needed:
@Component()
export class MyComponent {
@ViewChild( 'editor' ) editorComponent: CKEditorComponent;
public getEditor() {
// Warning: this may return undefined if the editor is hidden behind the `*ngIf` directive or
// if the editor is not fully initialised yet.
return this.editorComponent.editorInstance;
}
}
If you want to make changes on just created editor then the better option would be getting instance of CKEditor 5 editor on the ready event.
An Upload Adapter can be used with CKEditor 5 so that when a user adds an image to a document it is encoded as a link to the image. The image itself is stored separately from the document. Read more in the {@link framework/guides/deep-dive/upload-adapter Upload Adapter guide}.
If you are familiar with Angular you have probably already used the HTTPClient service which wraps the JavaScript XMLHttpRequest and provides a more concise API around it. Let's make a use of it to give Angular the control over the image upload process (e.g. for easier mocking and testing later):
import { Subscription, Observable } from 'rxjs';
import { HttpClient, HttpEventType, HttpRequest, HttpEvent } from '@angular/common/http';
// ...
@Component()
export class MyComponent {
constructor( private httpClient: HttpClient ){}
public editorConfig = {
extraPlugins: [ UploadAdapterPlugin ],
httpClient: this.httpClient
};
}
class UploadAdapterPlugin {
constructor( editor: any ) {
const httpClient = editor.config.get( 'httpClient' );
editor.plugins.get( 'FileRepository' ).createUploadAdapter = loader => {
// Modify your endpoint URL.
return new UploadAdapter( loader, httpClient, 'http://example.com/image/upload/path' );
};
}
}
class UploadAdapter {
private sub: Subscription;
constructor(
private loader: UploadLoader,
private httpClient: HttpClient,
private url: string
) { }
public async upload(): Promise<{ default: string }> {
const file = await this.loader.file;
return new Promise( ( resolve, reject ) => {
const formData = new FormData();
formData.append( 'file', file );
this.sub = this.uploadImage( formData ).subscribe( event => {
if ( event.type === HttpEventType.Response ) {
const response = event.body;
if ( response.error ) {
return reject( response.error.message );
}
resolve( { default: response.url } );
} else if ( event.type === HttpEventType.UploadProgress ) {
this.loader.uploaded = event.loaded;
this.loader.uploadTotal = event.total;
}
}, err => {
reject( err.body.message );
} );
} );
}
public abort() {
if ( this.sub ) {
this.sub.unsubscribe();
}
}
private uploadImage( formData: FormData ): Observable<HttpEvent<ImageEndpointResponse>> {
const request = new HttpRequest(
'POST',
this.url,
formData,
{
withCredentials: true,
reportProgress: true
}
);
return this.httpClient.request( request );
}
}
// Modify to align your endpoint response.
interface ImageEndpointResponse {
url: number[];
error?: { message: string };
}
interface UploadLoader {
uploaded: number;
uploadTotal: number;
readonly file: string;
}
The UploadAdapter class will be instantiated each time when CKEditor 5 needs it. Note that the functions in this class will run outside the NgZone, which means that if it makes changes to variables bound to HTML elements, the shadow-DOM logic of Angular won't detect those changes.
The CKEditor 5 component can be localized in two steps.
First, you need to add translation files to the bundle. This step can be achieved in two ways:
By importing translations for given languages directly in your component file:
import '@ckeditor/ckeditor5-build-classic/build/translations/de';
import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
...
By adding paths to translation files to the "scripts" array in angular.json:
"architect": {
"build": {
"options": {
"scripts": [ "node_modules/@ckeditor/ckeditor5-build-classic/build/translations/de.js" ]
}
}
}
Then, you need to configure the editor to use the given language:
@Component( {
// ...
} )
export class MyComponent {
public Editor = ClassicEditor;
public config = {
language: 'de'
};
}
For advanced usage see the {@link features/ui-language Setting UI language} guide.
The source code of the rich text editor component for Angular 2+ is available on GitHub in https://github.com/ckeditor/ckeditor5-angular.