Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 2 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,8 @@ This package is a thin wrapper around [TinyMCE](https://github.com/tinymce/tinym
|<= 8 |3.x |
|< 5 | Not supported |

### Not yet Zoneless ( >=Angular v21 )
* This wrapper still requires `zone.js` to ensure backward compatibility to older Angular versions. Therefore, if your application uses Angular v21 or higher, it needs to include `provideZoneDetection()` in its providers.

```jsx
import { NgModule, provideZoneChangeDetection } from '@angular/core';

@NgModule({
declarations: [
// ...
],
imports: [
// ...
],
providers: [ provideZoneChangeDetection() ],
bootstrap: [ AppComponent ]
})
```
### Zoneless Support ( >=Angular v19 )
This wrapper supports Angular's zoneless change detection. No additional configuration is needed — the component works with both zone-based and zoneless applications.

### Issues

Expand Down
4 changes: 3 additions & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"configDir": ".storybook",
"browserTarget": "angular:build",
"compodoc": false,
"experimentalZoneless": true,
"port": 9001
}
},
Expand All @@ -30,7 +31,8 @@
"configDir": ".storybook",
"browserTarget": "angular:build",
"compodoc": false,
"outputDir": "storybook-static"
"outputDir": "storybook-static",
"experimentalZoneless": true
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@
"to-string-loader": "^1.1.5",
"tslib": "^2.6.2",
"typescript": "^5.9.3",
"webpack": "^5.95.0",
"zone.js": "~0.16.0"
"webpack": "^5.95.0"
},
"version": "9.1.2-rc",
"name": "@tinymce/tinymce-angular",
Expand Down
2 changes: 1 addition & 1 deletion stories/Editor.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const IframeStory: StoryObj<EditorComponent> = {
initialValue: sampleContent,
init: {
height: 300,
plugins: 'help',
plugins: 'help code',
},
}
};
Expand Down
39 changes: 15 additions & 24 deletions tinymce-angular-component/src/main/ts/editor/editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@
import { isPlatformBrowser, CommonModule } from '@angular/common';
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
forwardRef,
Inject,
InjectionToken,
Input,
NgZone,
OnDestroy,
PLATFORM_ID,
InjectionToken,
Optional,
ChangeDetectorRef,
ChangeDetectionStrategy
PLATFORM_ID,
} from '@angular/core';
import { FormsModule, ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subject, takeUntil } from 'rxjs';
Expand All @@ -34,7 +33,7 @@ const EDITOR_COMPONENT_VALUE_ACCESSOR = {
multi: true
};

export type Version = `${'4' | '5' | '6' | '7' | '8'}${'' | '-dev' | '-testing' | `.${number}` | `.${number}.${number}`}`;
export type Version = `${'5' | '6' | '7' | '8'}${'' | '-dev' | '-testing' | `.${number}` | `.${number}.${number}`}`;

@Component({
selector: 'editor',
Expand Down Expand Up @@ -97,8 +96,6 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
return this._editor;
}

public ngZone: NgZone;

private _elementRef: ElementRef;
private _element?: HTMLElement;
private _disabled?: boolean;
Expand All @@ -112,14 +109,12 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal

public constructor(
elementRef: ElementRef,
ngZone: NgZone,
private cdRef: ChangeDetectorRef,
@Inject(PLATFORM_ID) private platformId: object,
@Optional() @Inject(TINYMCE_SCRIPT_SRC) private tinymceScriptSrc?: string
) {
super();
this._elementRef = elementRef;
this.ngZone = ngZone;
}

public writeValue(value: string | null): void {
Expand Down Expand Up @@ -222,9 +217,7 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
this._element.style.visibility = '';
}

this.ngZone.runOutsideAngular(() => {
getTinymce().init(finalInit);
});
getTinymce().init(finalInit);
};

private getScriptSrc() {
Expand All @@ -236,24 +229,22 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
private initEditor(editor: TinyMCEEditor) {
listenTinyMCEEvent(editor, 'blur', this.destroy$).subscribe(() => {
this.cdRef.markForCheck();
this.ngZone.run(() => this.onTouchedCallback());
this.onTouchedCallback();
});

listenTinyMCEEvent(editor, this.modelEvents, this.destroy$).subscribe(() => {
this.cdRef.markForCheck();
this.ngZone.run(() => this.emitOnChange(editor));
this.emitOnChange(editor);
});

if (typeof this.initialValue === 'string') {
this.ngZone.run(() => {
editor.setContent(this.initialValue as string);
if (editor.getContent() !== this.initialValue) {
this.emitOnChange(editor);
}
if (this.onInitNgModel !== undefined) {
this.onInitNgModel.emit(editor as unknown as EventObj<any>);
}
});
editor.setContent(this.initialValue as string);
if (editor.getContent() !== this.initialValue) {
this.emitOnChange(editor);
}
if (this.onInitNgModel !== undefined) {
this.onInitNgModel.emit(editor as unknown as EventObj<any>);
}
}
}

Expand Down
9 changes: 4 additions & 5 deletions tinymce-angular-component/src/main/ts/utils/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,12 @@ const bindHandlers = (ctx: EditorComponent, editor: any, destroy$: Subject<void>
const eventEmitter: EventEmitter<any> = ctx[eventName];

listenTinyMCEEvent(editor, eventName.substring(2), destroy$).subscribe((event) => {
// Caretaker note: `ngZone.run()` runs change detection since it notifies the forked Angular zone that it's
// being re-entered. We don't want to run `ApplicationRef.tick()` if anyone listens to the specific event
// within the template. E.g. if the `onSelectionChange` is not listened within the template like:
// Caretaker note: We only emit if the event emitter is observed to avoid scheduling unnecessary change
// detection runs. E.g. if `onSelectionChange` is not bound in the template like:
// `<editor (onSelectionChange)="..."></editor>`
// then it won't be "observed", and we won't run "dead" change detection.
// then it won't be "observed" and we can skip emmitting the event
if (isObserved(eventEmitter)) {
ctx.ngZone.run(() => eventEmitter.emit({ event, editor }));
eventEmitter.emit({ event, editor });
}
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import 'core-js/features/reflect';
import 'zone.js';
import 'zone.js/plugins/fake-async-test';

import { TestBed } from '@angular/core/testing';
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
import { NgModule, provideZoneChangeDetection } from '@angular/core';
import { NgModule, provideZonelessChangeDetection } from '@angular/core';

@NgModule({
providers: [ provideZoneChangeDetection() ],
providers: [ provideZonelessChangeDetection() ],
})
class AppTestingModule {}

Expand Down
10 changes: 7 additions & 3 deletions tinymce-angular-component/src/test/ts/alien/TestHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const editorHook = <T = unknown>(component: Type<T>, moduleDef: TestModul
return firstValueFrom(

editorComponent.onInit.pipe(
throwTimeout(10000, `Timed out waiting for editor to load`),
throwTimeout(15000, `Timed out waiting for editor to load`),
switchMap(
({ editor }) =>
new Promise<Editor>((resolve) => {
Expand All @@ -85,10 +85,14 @@ export const editorHook = <T = unknown>(component: Type<T>, moduleDef: TestModul
// after global tinymce is removed in a clean up. Specifically, it happens when unloading/loading different versions of TinyMCE
if (editor.licenseKeyManager) {
editor.licenseKeyManager.validate({}).then(() => {
resolve(editor as Editor);
setTimeout(() => {
resolve(editor as Editor);
}, 500);
}).catch((reason) => console.warn(reason));
} else {
resolve(editor as Editor);
setTimeout(() => {
resolve(editor as Editor);
}, 500);
}
});
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,13 @@ import { describe, it } from '@ephox/bedrock-client';

import { EditorComponent } from '../../../main/ts/public_api';
import { eachVersionContext, editorHook } from '../alien/TestHooks';
import { map, merge, timer, first, buffer, Observable, tap, firstValueFrom } from 'rxjs';
import { NgZone } from '@angular/core';
import { map, merge, timer, first, buffer, firstValueFrom } from 'rxjs';
import { Assertions } from '@ephox/agar';
import { Fun } from '@ephox/katamari';
import { throwTimeout } from '../alien/TestHelpers';
import { supportedTinymceVersions, throwTimeout } from '../alien/TestHelpers';

describe('EventBlacklistingTest', () => {
const shouldRunInAngularZone = <T>(source: Observable<T>) =>
source.pipe(
tap(() => Assertions.assertEq('Subscribers to events should run within NgZone', true, NgZone.isInAngularZone()))
);

eachVersionContext([ '4', '5', '6', '7', '8' ], () => {
eachVersionContext(supportedTinymceVersions(), () => {
const createFixture = editorHook(EditorComponent);

it('Events should be bound when allowed', async () => {
Expand All @@ -27,9 +21,9 @@ describe('EventBlacklistingTest', () => {

const pEventsCompleted = firstValueFrom(
merge(
fixture.editorComponent.onKeyUp.pipe(map(Fun.constant('onKeyUp')), shouldRunInAngularZone),
fixture.editorComponent.onKeyDown.pipe(map(Fun.constant('onKeyDown')), shouldRunInAngularZone),
fixture.editorComponent.onClick.pipe(map(Fun.constant('onClick')), shouldRunInAngularZone)
fixture.editorComponent.onKeyUp.pipe(map(Fun.constant('onKeyUp'))),
fixture.editorComponent.onKeyDown.pipe(map(Fun.constant('onKeyDown'))),
fixture.editorComponent.onClick.pipe(map(Fun.constant('onClick')))
).pipe(throwTimeout(10000, 'Timed out waiting for some event to fire'), buffer(timer(100)), first())
);
fixture.editor.fire('keydown');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { eachVersionContext, editorHook, fixtureHook } from '../alien/TestHooks'
import { By } from '@angular/platform-browser';
import { first, firstValueFrom, switchMap } from 'rxjs';
import type { Editor } from 'tinymce';
import { fakeTypeInEditor } from '../alien/TestHelpers';
import { fakeTypeInEditor, supportedTinymceVersions } from '../alien/TestHelpers';

type FormControlProps = Partial<Record<'touched' | 'pristine' | 'dirty' | 'valid', boolean>>;

Expand All @@ -21,7 +21,7 @@ describe('FormControlTest', () => {
}
};

eachVersionContext([ '4', '5', '6', '7', '8' ], () => {
eachVersionContext(supportedTinymceVersions(), () => {
[ ChangeDetectionStrategy.Default, ChangeDetectionStrategy.OnPush ].forEach((changeDetection) => {
context(`[formControl] with change detection: ${changeDetection}`, () => {
@Component({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { describe, it } from '@ephox/bedrock-client';

import { EditorComponent } from '../../../main/ts/editor/editor.component';
import { eachVersionContext, editorHook } from '../alien/TestHooks';
import { fakeTypeInEditor } from '../alien/TestHelpers';
import { fakeTypeInEditor, supportedTinymceVersions } from '../alien/TestHelpers';

describe('NgModelTest', () => {
const assertNgModelState = (prop: 'valid' | 'pristine' | 'touched', expected: boolean, ngModel: NgModel) => {
Assertions.assertEq('assert ngModel ' + prop + ' state', expected, ngModel[prop]);
};

eachVersionContext([ '4', '5', '6', '7', '8' ], () => {
eachVersionContext(supportedTinymceVersions(), () => {
@Component({
standalone: true,
imports: [ EditorComponent, FormsModule ],
Expand Down
42 changes: 0 additions & 42 deletions tinymce-angular-component/src/test/ts/browser/NgZoneTest.ts

This file was deleted.

4 changes: 2 additions & 2 deletions tinymce-angular-component/src/test/ts/browser/PropTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { context, describe, it } from '@ephox/bedrock-client';

import { EditorComponent } from '../../../main/ts/public_api';
import { eachVersionContext, fixtureHook } from '../alien/TestHooks';
import { captureLogs, throwTimeout } from '../alien/TestHelpers';
import { captureLogs, supportedTinymceVersions, throwTimeout } from '../alien/TestHelpers';
import { concatMap, distinct, firstValueFrom, mergeMap, of, toArray } from 'rxjs';
import { ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
Expand Down Expand Up @@ -42,7 +42,7 @@ describe('PropTest', () => {
)
);

eachVersionContext([ '4', '5', '6', '7', '8' ], () => {
eachVersionContext(supportedTinymceVersions(), () => {
context('Single editor with ID', () => {
@Component({
standalone: true,
Expand Down
5 changes: 0 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13914,8 +13914,3 @@ zone.js@~0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.10.3.tgz#3e5e4da03c607c9dcd92e37dd35687a14a140c16"
integrity sha512-LXVLVEq0NNOqK/fLJo3d0kfzd4sxwn2/h67/02pjCjfKDxgx1i9QqpvtHD8CrBnSSwMw5+dy11O7FRX5mkO7Cg==

zone.js@~0.16.0:
version "0.16.0"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.16.0.tgz#955b9e28846d76ca2ef7091c8f9e96f35f79e828"
integrity sha512-LqLPpIQANebrlxY6jKcYKdgN5DTXyyHAKnnWWjE5pPfEQ4n7j5zn7mOEEpwNZVKGqx3kKKmvplEmoBrvpgROTA==
Loading