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" name="editor1"></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 Javascript-level {@link api CKEditor API} is needed
it is easy to access with an additional step.
To do this, assign the Angular <ckeditor> component to a locally scoped variable as
follows:
<ckeditor #editor1 [editor]="Editor" [config]="ckconfig"
[(ngModel)]="text" name="editcontent"></ckeditor>
You can then refer to that variable as needed in any method call within the component such as:
<button type="button" (click)="doPasteSomething(editor1)">Do Someting</button>
In the component the target function will look like this:
doPasteSomething(editorComponent: CKEditorComponent) {
const editor = editorComponent.editorInstance;
editor.model.insertContent(' >> This just got pasted! << ');
}
The operations performed may be anything that is defined and allowable by the API.
Note that the Angular editor1 template variable may also be accessed with an
@ViewChild-decorated variable declaration. The method in the example, however,
will be preferable in the case where there is more than one <ckeditor> element on
the page. Also, if the <ckeditor> element is inside an *ngIf structure the @ViewChild
declared variable may be inadvertently undefined.
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. When images are large or numerous this results in noticeable improvement in performance over the default behavior. Read about custom UploadAdapters {@link TODO framework/guides/deep-dive/upload-adapter here}.
If you're familiar with Angular you have probably already used the HTTPClient
service which wraps the JavaScript XMLHttpRequest and provides a more concise API around it.
import { Subscription, Observable } from 'rxjs';
import { HttpClient, HttpEventType, HttpRequest, HttpEvent } from '@angular/common/http';
// ...
@Component()
export class MyComponent {
public editorConfig = {
extraPlugins: [ UploadAdapterPlugin ],
httpClient: this.http
};
}
class UploadAdapterPlugin {
constructor( editor ) {
const httpClient = editor.config.get( 'httpClient' );
editor.plugins.get( 'FileRepository' ).createUploadAdapter = loader => {
// Modify your endpoint URL.
return new UploadAdapter( loader, httpClient, '/image' );
};
}
}
class UploadAdapter {
private sub: Subscription;
constructor(
private loader: UploadLoader,
private httpClient: HttpClient,
private url: string
) { }
public upload(): Promise<{ default: string }> {
return new Promise( ( resolve, reject ) => {
const formData = new FormData();
formData.append( 'file', this.loader.file );
this.sub = this.httpPostMediaReq( formData ).subscribe( event => {
if ( event.type === HttpEventType.Response ) {
// Modify for your endpoint, in this scenario the endpoint should return:
// { error: string } in case of an error.
// { list: number[] } (an array of image ids) in case of success.
const response = event.body;
if ( response.error ) {
return reject( response.error );
}
const imageUrl = this.getImageUrl( response.list[ 0 ] );
resolve( { default: imageUrl } );
} 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 getImageUrl( id: number ) {
// Return the URL where the successfully upload image
// can be retrieved with an HTTP GET request.
return `/images/${ id }`;
}
private httpPostMediaReq( formData: FormData ): Observable<HttpEvent<ImageEndpointResponse>> {
const request = new HttpRequest(
'POST',
this.url,
formData,
{
withCredentials: true,
reportProgress: true
}
);
return this.httpClient.request( request );
}
}
interface ImageEndpointResponse {
list: number[];
error?: string;
}
interface UploadLoader {
uploaded: number;
uploadTotal: number;
readonly file: string;
}
This class will be instantiated each time that CKEditor needs it. Note that
the functions in this class will run outside an NgZone which means that
if it makes changes to variables bound to HTML elements, the shadow-DOM logic
in the Angular framework won't detect those changes.
The instance property loader is the primary interface between the UploadAdapter
and the CKEditor instance.
The second parameter is the URL where the image will be sent. This will need
to have everything the imageURL function in the UploadAdpater needs to
generate a complete URL.
Next, we need to tell CKEditor where the Plugin can be found. This is done
using the extraPlugins element in the configuration object, which can
be used without rebuilding the standard build you are using. The following
instance variable can be defined:
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.