diff --git a/src/app/app.component.html b/src/app/app.component.html
index 60b25a8..ab4868b 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -7,6 +7,7 @@
CKEditor 5 integration with Angular
Integration with reactive forms (formControlName
)
Integration with CKEditor Watchdog
Integration with CKEditor Context
+ Catching error when editor crashes during initialization
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 29bb669..20c19e6 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -11,6 +11,7 @@ import { DemoFormComponent } from './demo-form/demo-form.component';
import { DemoReactiveFormComponent } from './demo-reactive-form/demo-reactive-form.component';
import { ContextDemoComponent } from './context-demo/context-demo';
import { WatchdogDemoComponent } from './watchdog-demo/watchdog-demo';
+import { InitializationCrashComponent } from './initialization-crash/initialization-crash.component';
const appRoutes: Routes = [
{ path: '', redirectTo: '/simple-usage', pathMatch: 'full' },
@@ -18,7 +19,8 @@ const appRoutes: Routes = [
{ path: 'forms', component: DemoFormComponent },
{ path: 'reactive-forms', component: DemoReactiveFormComponent },
{ path: 'watchdog', component: WatchdogDemoComponent },
- { path: 'simple-usage', component: SimpleUsageComponent }
+ { path: 'simple-usage', component: SimpleUsageComponent },
+ { path: 'init-crash', component: InitializationCrashComponent }
];
@NgModule( {
@@ -35,7 +37,8 @@ const appRoutes: Routes = [
DemoFormComponent,
DemoReactiveFormComponent,
SimpleUsageComponent,
- WatchdogDemoComponent
+ WatchdogDemoComponent,
+ InitializationCrashComponent
],
providers: [],
bootstrap: [ AppComponent ]
diff --git a/src/app/initialization-crash/initialization-crash.component.css b/src/app/initialization-crash/initialization-crash.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/initialization-crash/initialization-crash.component.html b/src/app/initialization-crash/initialization-crash.component.html
new file mode 100644
index 0000000..b30ad53
--- /dev/null
+++ b/src/app/initialization-crash/initialization-crash.component.html
@@ -0,0 +1,30 @@
+Initialization crash demo
+Without watchdog
+
+
+
+
+
+
+
+
+
+
+ An error has occurred, please contact us at: support@company.com to resolve the situation.
+
+
+With watchdog
+
+
+
+
+
+
+
+
+
+
+ An error has occurred, please contact us at: support@company.com to resolve the situation.
+
diff --git a/src/app/initialization-crash/initialization-crash.component.ts b/src/app/initialization-crash/initialization-crash.component.ts
new file mode 100644
index 0000000..a63be5e
--- /dev/null
+++ b/src/app/initialization-crash/initialization-crash.component.ts
@@ -0,0 +1,64 @@
+import { Component, ViewChild } from '@angular/core';
+import { CKEditorComponent } from 'src/ckeditor';
+import AngularEditor from 'ckeditor/build/ckeditor';
+import type { ContextWatchdog } from '@ckeditor/ckeditor5-watchdog';
+
+@Component( {
+ selector: 'app-initialization-crash',
+ templateUrl: './initialization-crash.component.html',
+ styleUrls: [ './initialization-crash.component.css' ]
+} )
+export class InitializationCrashComponent {
+ public Editor = AngularEditor;
+ public EditorWatchdog = AngularEditor;
+
+ @ViewChild( CKEditorComponent ) public ckeditor?: CKEditorComponent;
+
+ public config: any;
+ public ready = false;
+
+ public errorOccurred = false;
+ public errorOccurredWatchdog = false;
+
+ public watchdog?: ContextWatchdog;
+
+ public ngOnInit(): void {
+ const contextConfig: any = {
+ foo: 'bar'
+ };
+
+ this.config = {
+ extraPlugins: [
+ function( editor: any ) {
+ editor.data.on( 'init', () => {
+ // Simulate an error.
+ // Create a non-existing position, then try to get its parent.
+ const position = editor.model.createPositionFromPath( editor.model.document.getRoot(), [ 1, 2, 3 ] );
+
+ return position.parent;
+ } );
+ }
+ ],
+ collaboration: {
+ channelId: 'foobar-baz'
+ }
+ };
+
+ this.watchdog = new AngularEditor.ContextWatchdog( AngularEditor.Context );
+
+ this.watchdog.create( contextConfig )
+ .then( () => {
+ this.ready = true;
+ } );
+ }
+
+ public onError( error: any ): void {
+ console.error( 'Editor without watchdog threw an error which was caught', error );
+ this.errorOccurred = true;
+ }
+
+ public onErrorWatchdog( error: any ): void {
+ console.error( 'Editor with watchdog threw an error which was caught', error );
+ this.errorOccurredWatchdog = true;
+ }
+}
diff --git a/src/app/watchdog-demo/watchdog-demo.html b/src/app/watchdog-demo/watchdog-demo.html
index 755c716..94d7970 100644
--- a/src/app/watchdog-demo/watchdog-demo.html
+++ b/src/app/watchdog-demo/watchdog-demo.html
@@ -1,7 +1,18 @@
Watchdog demo
-
+
-
-
+
+
+
+ Type '1' or '2' to crash the editor.
+
+
+
+
+
+
+
+ An error has occurred, please contast us at: support@company.com to resolve the situation.
+
diff --git a/src/app/watchdog-demo/watchdog-demo.ts b/src/app/watchdog-demo/watchdog-demo.ts
index 71a207f..0952d93 100644
--- a/src/app/watchdog-demo/watchdog-demo.ts
+++ b/src/app/watchdog-demo/watchdog-demo.ts
@@ -1,5 +1,4 @@
-import { Component, ElementRef, ViewChild } from '@angular/core';
-import { CKEditorComponent } from '../../ckeditor/ckeditor.component';
+import { Component } from '@angular/core';
import AngularEditor from '../../../ckeditor/build/ckeditor';
import type { ContextWatchdog } from '@ckeditor/ckeditor5-watchdog';
@@ -11,16 +10,31 @@ import type { ContextWatchdog } from '@ckeditor/ckeditor5-watchdog';
export class WatchdogDemoComponent {
public Editor = AngularEditor;
- @ViewChild( CKEditorComponent ) public ckeditor?: ElementRef;
-
public config: any;
public watchdog?: ContextWatchdog;
public ready = false;
public isDisabled = false;
+ public errorOccurred = false;
public onReady( editor: AngularEditor ): void {
console.log( editor );
+
+ const inputCommand = editor.commands.get( 'input' )!;
+
+ inputCommand.on( 'execute', ( evt, data ) => {
+ const commandArgs = data[ 0 ];
+
+ if ( commandArgs.text === '1' ) {
+ // Simulate an error.
+ throw new Error( 'a-custom-editor-error' );
+ }
+
+ if ( commandArgs.text === '2' ) {
+ // Simulate an error.
+ throw 'foobar';
+ }
+ } );
}
public ngOnInit(): void {
@@ -45,4 +59,8 @@ export class WatchdogDemoComponent {
public toggle(): void {
this.isDisabled = !this.isDisabled;
}
+
+ public onError(): void {
+ this.errorOccurred = true;
+ }
}
diff --git a/src/ckeditor/ckeditor.component.spec.ts b/src/ckeditor/ckeditor.component.spec.ts
index a4bbd22..ce5b8b3 100644
--- a/src/ckeditor/ckeditor.component.spec.ts
+++ b/src/ckeditor/ckeditor.component.spec.ts
@@ -491,6 +491,66 @@ describe( 'CKEditorComponent', () => {
} );
} );
} );
+
+ describe( 'initialization errors are catched', () => {
+ let config: any;
+
+ beforeEach( () => {
+ config = {
+ extraPlugins: [
+ function( editor: any ) {
+ editor.data.on( 'init', () => {
+ // Simulate an error.
+ // Create a non-existing position, then try to get its parent.
+ const position = editor.model.createPositionFromPath( editor.model.document.getRoot(), [ 1, 2, 3 ] );
+
+ return position.parent;
+ } );
+ }
+ ],
+ collaboration: {
+ channelId: 'foobar-baz'
+ }
+ };
+ } );
+
+ it( 'when internal watchdog is created', async () => {
+ fixture = TestBed.createComponent( CKEditorComponent );
+ const component = fixture.componentInstance;
+ const errorSpy = jasmine.createSpy( 'errorSpy' );
+ component.error.subscribe( errorSpy );
+ component.editor = AngularEditor;
+ component.config = config;
+
+ fixture.detectChanges();
+ await waitCycle();
+
+ expect( errorSpy ).toHaveBeenCalledTimes( 1 );
+
+ fixture.destroy();
+ } );
+
+ it( 'when external watchdog is provided', async () => {
+ fixture = TestBed.createComponent( CKEditorComponent );
+ const component = fixture.componentInstance;
+ const errorSpy = jasmine.createSpy( 'errorSpy' );
+ component.error.subscribe( errorSpy );
+ const contextWatchdog = new AngularEditor.ContextWatchdog( AngularEditor.Context );
+
+ await contextWatchdog.create();
+
+ component.watchdog = contextWatchdog;
+ component.editor = AngularEditor;
+ component.config = config;
+
+ fixture.detectChanges();
+ await waitCycle();
+
+ expect( errorSpy ).toHaveBeenCalledTimes( 1 );
+
+ fixture.destroy();
+ } );
+ } );
} );
describe( 'change detection', () => {
diff --git a/src/ckeditor/ckeditor.component.ts b/src/ckeditor/ckeditor.component.ts
index 9b8808b..4d80804 100644
--- a/src/ckeditor/ckeditor.component.ts
+++ b/src/ckeditor/ckeditor.component.ts
@@ -164,7 +164,7 @@ export class CKEditorComponent implements After
/**
* Fires when the editor component crashes.
*/
- @Output() public error = new EventEmitter();
+ @Output() public error = new EventEmitter();
/**
* The instance of the editor created by this component.
@@ -367,16 +367,15 @@ export class CKEditorComponent implements After
this.elementRef.nativeElement.removeChild( this.editorElement! );
};
- const emitError = () => {
+ const emitError = ( e?: unknown ) => {
// Do not run change detection by re-entering the Angular zone if the `error`
// emitter doesn't have any subscribers.
// Subscribers are pushed onto the list whenever `error` is listened inside the template:
// ``.
if ( hasObservers( this.error ) ) {
- this.ngZone.run( () => this.error.emit() );
+ this.ngZone.run( () => this.error.emit( e ) );
}
};
-
const element = document.createElement( this.tagName );
const config = this.getConfig();
@@ -392,6 +391,8 @@ export class CKEditorComponent implements After
destructor,
sourceElementOrData: element,
config
+ } ).catch( e => {
+ emitError( e );
} );
this.watchdog.on( 'itemError', ( _, { itemId } ) => {
@@ -410,11 +411,12 @@ export class CKEditorComponent implements After
editorWatchdog.on( 'error', emitError );
this.editorWatchdog = editorWatchdog;
-
this.ngZone.runOutsideAngular( () => {
// Note: must be called outside of the Angular zone too because `create` is calling
// `_startErrorHandling` within a microtask which sets up `error` listener on the window.
- editorWatchdog.create( element, config );
+ editorWatchdog.create( element, config ).catch( e => {
+ emitError( e );
+ } );
} );
}
}