KillerCodeMonkey / ngx-quill

Angular (>=2) components for the Quill Rich Text Editor
MIT License
1.79k stars 262 forks source link

ngx-quill with Angular Elements #1481

Closed abf7d closed 2 years ago

abf7d commented 2 years ago

I'm building an Angular 13 custom element / web component that uses ngx-quill. Here are the versions for the packages in my package.json:

 "ngx-quill": "^16.1.1",
"@angular/elements": "~13.0.2",
"@angular/core": "~13.0.2",

I build the web component app using the command: ng build plots-elements --output-hashing none. When I navigate to my component in the consuming app I get a blank area where the component is supposed to render and I get the error:

..\..\dist\plots-elements\runtime.js:formatted:194 GET http://localhost:4202/263.js net::ERR_ABORTED 404 (Not Found)
r.l @ ..\..\dist\plots-elements\runtime.js:formatted:194
r.f.j @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
r.e @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
Dm @ ..\..\dist\plots-elements\runtime.js:formatted:194
h @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
ZoneAwarePromise @ zone.js:1387
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
_trySubscribe @ ..\..\dist\plots-elements\runtime.js:formatted:194
subscribe @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
subscribe @ ..\..\dist\plots-elements\runtime.js:formatted:194
ngAfterViewInit @ ..\..\dist\plots-elements\runtime.js:formatted:194
Ig @ ..\..\dist\plots-elements\runtime.js:formatted:194
pg @ ..\..\dist\plots-elements\runtime.js:formatted:194
xa @ ..\..\dist\plots-elements\runtime.js:formatted:194
zo @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
zo @ ..\..\dist\plots-elements\runtime.js:formatted:194
gd @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
zo @ ..\..\dist\plots-elements\runtime.js:formatted:194
gd @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
zo @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
zo @ ..\..\dist\plots-elements\runtime.js:formatted:194
gd @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
zo @ ..\..\dist\plots-elements\runtime.js:formatted:194
gd @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
zo @ ..\..\dist\plots-elements\runtime.js:formatted:194
Mv @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
hd @ ..\..\dist\plots-elements\runtime.js:formatted:194
detectChanges @ ..\..\dist\plots-elements\runtime.js:formatted:194
tick @ ..\..\dist\plots-elements\runtime.js:formatted:194
(anonymous) @ ..\..\dist\plots-elements\runtime.js:formatted:194
invoke @ zone.js:372
onInvoke @ ..\..\dist\plots-elements\runtime.js:formatted:194
invoke @ zone.js:371
run @ zone.js:134
run @ ..\..\dist\plots-elements\runtime.js:formatted:194
next @ ..\..\dist\plots-elements\runtime.js:formatted:194
__tryOrUnsub @ ..\..\dist\plots-elements\runtime.js:formatted:194
next @ ..\..\dist\plots-elements\runtime.js:formatted:194
_next @ ..\..\dist\plots-elements\runtime.js:formatted:194
next @ ..\..\dist\plots-elements\runtime.js:formatted:194
next @ ..\..\dist\plots-elements\runtime.js:formatted:194
emit @ ..\..\dist\plots-elements\runtime.js:formatted:194
dp @ ..\..\dist\plots-elements\runtime.js:formatted:194
ev @ ..\..\dist\plots-elements\runtime.js:formatted:194
onInvokeTask @ ..\..\dist\plots-elements\runtime.js:formatted:194
invokeTask @ zone.js:405
runTask @ zone.js:178
invokeTask @ zone.js:487
invokeTask @ zone.js:1600
globalZoneAwareCallback @ zone.js:1626
Show 32 more frames
2..\..\dist\plots-elements\runtime.js:formatted:194 ERROR ChunkLoadError: Loading chunk 263 failed.
(error: http://localhost:4202/263.js)
    at Object.r.f.j (..\..\dist\plots-elements\runtime.js:formatted:194:NaN)

Everything else in my project works. The component works in an Angular app / imported as an Angular library. When it is compiled as an Angular Element and imported into a vanilla js page, it doesn't work. Is there a way to fix this or is there a work around?

KillerCodeMonkey commented 2 years ago

I would guess that it tries to load quilljs, because quilljs is loaded asynchronous and it is not part of your bundled component.

So you have to make sure, that quilljs is available as module.

Aaron Friedman @.***> schrieb am Do., 10. Feb. 2022, 15:57:

I'm building an Angular 13 custom element / web component that uses ngx-quill. Here are the versions for the packages in my package.json:

"ngx-quill": "^16.1.1", @./elements": "~13.0.2", @./core": "~13.0.2",

I build the web component app using the command: ng build plots-elements --output-hashing none . When I navigate to my component in the consuming app I get a blank area where the component is supposed to render and I get the error:

....\dist\plots-elements\runtime.js:formatted:194 GET http://localhost:4202/263.js net::ERR_ABORTED 404 (Not Found) r.l @ ....\dist\plots-elements\runtime.js:formatted:194 r.f.j @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 r.e @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 Dm @ ....\dist\plots-elements\runtime.js:formatted:194 h @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 ZoneAwarePromise @ zone.js:1387 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 _trySubscribe @ ....\dist\plots-elements\runtime.js:formatted:194 subscribe @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 subscribe @ ....\dist\plots-elements\runtime.js:formatted:194 ngAfterViewInit @ ....\dist\plots-elements\runtime.js:formatted:194 Ig @ ....\dist\plots-elements\runtime.js:formatted:194 pg @ ....\dist\plots-elements\runtime.js:formatted:194 xa @ ....\dist\plots-elements\runtime.js:formatted:194 zo @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 zo @ ....\dist\plots-elements\runtime.js:formatted:194 gd @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 zo @ ....\dist\plots-elements\runtime.js:formatted:194 gd @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 zo @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 zo @ ....\dist\plots-elements\runtime.js:formatted:194 gd @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 zo @ ....\dist\plots-elements\runtime.js:formatted:194 gd @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 zo @ ....\dist\plots-elements\runtime.js:formatted:194 Mv @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 hd @ ....\dist\plots-elements\runtime.js:formatted:194 detectChanges @ ....\dist\plots-elements\runtime.js:formatted:194 tick @ ....\dist\plots-elements\runtime.js:formatted:194 (anonymous) @ ....\dist\plots-elements\runtime.js:formatted:194 invoke @ zone.js:372 onInvoke @ ....\dist\plots-elements\runtime.js:formatted:194 invoke @ zone.js:371 run @ zone.js:134 run @ ....\dist\plots-elements\runtime.js:formatted:194 next @ ....\dist\plots-elements\runtime.js:formatted:194 __tryOrUnsub @ ....\dist\plots-elements\runtime.js:formatted:194 next @ ....\dist\plots-elements\runtime.js:formatted:194 _next @ ....\dist\plots-elements\runtime.js:formatted:194 next @ ....\dist\plots-elements\runtime.js:formatted:194 next @ ....\dist\plots-elements\runtime.js:formatted:194 emit @ ....\dist\plots-elements\runtime.js:formatted:194 dp @ ....\dist\plots-elements\runtime.js:formatted:194 ev @ ....\dist\plots-elements\runtime.js:formatted:194 onInvokeTask @ ....\dist\plots-elements\runtime.js:formatted:194 invokeTask @ zone.js:405 runTask @ zone.js:178 invokeTask @ zone.js:487 invokeTask @ zone.js:1600 globalZoneAwareCallback @ zone.js:1626 Show 32 more frames 2....\dist\plots-elements\runtime.js:formatted:194 ERROR ChunkLoadError: Loading chunk 263 failed. (error: http://localhost:4202/263.js) at Object.r.f.j (....\dist\plots-elements\runtime.js:formatted:194:NaN)

Everything else in my project works. The component works in an Angular app / imported as an Angular library. When it is compiled as an Angular Element and imported into a vanilla js page, it doesn't work. Is there a way to fix this or is there a work around?

— Reply to this email directly, view it on GitHub https://github.com/KillerCodeMonkey/ngx-quill/issues/1481, or unsubscribe https://github.com/notifications/unsubscribe-auth/AARI4YDOADWSDSJ3HDRUXK3U2PG5VANCNFSM5OA7SYMA . You are receiving this because you are subscribed to this thread.Message ID: @.***>

abf7d commented 2 years ago

Thanks for the quick reply. I tried to include the quill.min.js in my Angular elements project. After I build it, I'm still getting the error. I also verified that quill is included in the main.js file that elements generates.

abf7d commented 2 years ago

I tried importing the quill.min.js file in the angular element project and importing it into the consuming app. I created a very simple component that has only a <quill-editor></quill-editor> and nothing renders the editor.

KillerCodeMonkey commented 2 years ago

I general I do not know that much about custom elements with angular and how the build process works if you depend on other modules.

Maybe you can check if you need to handle async module imports there.

Maybe you need to add an importmap to your index html where you can map the import of "quill" to a qualified path to the quilljs file

Aaron Friedman @.***> schrieb am Do., 10. Feb. 2022, 17:53:

I tried importing the quill.min.js file in the angular element project and importing it into the consuming app. I created a very simple component that has only a and nothing renders the editor.

— Reply to this email directly, view it on GitHub https://github.com/KillerCodeMonkey/ngx-quill/issues/1481#issuecomment-1035158383, or unsubscribe https://github.com/notifications/unsubscribe-auth/AARI4YGKPVIYEV2UQ2XMYXTU2PUO7ANCNFSM5OA7SYMA . You are receiving this because you commented.Message ID: @.***>

KillerCodeMonkey commented 2 years ago

Or another idea: override the QuillService in your module to not async import quill. So it would be bundled into your component.

Bengt Weiße @.***> schrieb am Do., 10. Feb. 2022, 19:34:

I general I do not know that much about custom elements with angular and how the build process works if you depend on other modules.

Maybe you can check if you need to handle async module imports there.

Maybe you need to add an importmap to your index html where you can map the import of "quill" to a qualified path to the quilljs file

Aaron Friedman @.***> schrieb am Do., 10. Feb. 2022, 17:53:

I tried importing the quill.min.js file in the angular element project and importing it into the consuming app. I created a very simple component that has only a and nothing renders the editor.

— Reply to this email directly, view it on GitHub https://github.com/KillerCodeMonkey/ngx-quill/issues/1481#issuecomment-1035158383, or unsubscribe https://github.com/notifications/unsubscribe-auth/AARI4YGKPVIYEV2UQ2XMYXTU2PUO7ANCNFSM5OA7SYMA . You are receiving this because you commented.Message ID: @.***>

abf7d commented 2 years ago

Would you happen to have a quick example or resources on how to do this? I have never turned off async import. Is this whats known as lazy loading? Is there an injection token to use?

KillerCodeMonkey commented 2 years ago

Nope check the source code of ngx-quill. There is an quill-service. It handles the loading of quilljs. Since quilljs is a large library it should be loaded only once and only if needed and should work with server side rendering. This all leads down that the service is loading / importing quill with async await and makes sure it is done only once. Rxjs and observables are used to be able to cancel the loading as well.

You could do something unit tests to if you want to mock own or custom services.

@NgModule({ providers:[{ provide: QuillService, useClass: MyCustomQuillService }] })

And your custom service:

class MyQuillService implements Quillservice {

}

Just copy the content of the service but directly import quill at the top of your file and set the private Quill property to the imported quill class. Remove the part in the "defer" function where quill is imported and set to this.Quill

Aaron Friedman @.***> schrieb am Do., 10. Feb. 2022, 21:00:

Would you happen to have a quick example or resources on how to do this? I have never turned off async import. Is this whats known as lazy loading? Is there an injection token to use?

— Reply to this email directly, view it on GitHub https://github.com/KillerCodeMonkey/ngx-quill/issues/1481#issuecomment-1035432995, or unsubscribe https://github.com/notifications/unsubscribe-auth/AARI4YHQ6JC3GN2F6OVZFKLU2QKMPANCNFSM5OA7SYMA . You are receiving this because you commented.Message ID: @.***>

abf7d commented 2 years ago

This looks like a good start. I'll try it out, thanks!

abf7d commented 2 years ago

So I created a service and imported as you said.

I imported directly from quill: import * as quill from 'quill';

I set the Quill property private Quill: any = quill;

I injected the service providers: [{provide: QuillService, useClass: SyncQuillService}]

I added the script file to the angular json to see if that works

 "scripts": [
              "node_modules/quill/dist/quill.min.js"
    ]

While debugging, I verified that the ngx-quill was indeed using my service. I verified that the quill was working in my Angular app that cosumed the library. Everything was working. I then built the Angular Elements and ran it, but it did not work. However it was failing at the place where an instance of the Quill class is declared. The error is on this line: https://github.com/KillerCodeMonkey/ngx-quill/blob/06cb86a2fe64a142be02d9eae04fcc3f21ab9b55/projects/ngx-quill/src/lib/quill-editor.component.ts#L293

It look as though it is not finding the script that I imported. Do you have any idea why that would be happening?

KillerCodeMonkey commented 2 years ago

what error do you get?

maybe you do not provide the quill config in your service? In that line this.service.config.theme is accessed, maybe it is not defined?

abf7d commented 2 years ago

I get:

ERROR TypeError: e is not a constructor
    at ..\..\dist\plots-elements\main.js:1:592662
    at ZoneDelegate.invoke (zone.js:372:1)
    at Zone.run (zone.js:134:1)
    at Bi.runOutsideAngular (..\..\dist\plots-elements\main.js:1:1031558)
    at w._next (..\..\dist\plots-elements\main.js:1:592482)
    at w.__tryOrUnsub (..\..\dist\plots-elements\main.js:1:428923)
    at w.next (..\..\dist\plots-elements\main.js:1:428028)
    at M._next (..\..\dist\plots-elements\main.js:1:427320)
    at M.next (..\..\dist\plots-elements\main.js:1:427090)
    at M.next (..\..\dist\plots-elements\main.js:1:424851)

and when I look at the pretty printed line in main.js, I see:

this.zone.runOutsideAngular(()=>{
                            var ne, ce, Ee;
                            if (this.quillEditor = new e(this.editorElem,{
                                bounds: f,
                                debug: _,
                                formats: te,
                                modules: a,
                                placeholder: u,
                                readOnly: U,
                                defaultEmptyValue: P,
                                scrollingContainer: H,
                                strict: this.strict,
                                theme: this.theme || (this.service.config.theme ? this.service.config.theme : "snow")
                            }),
                            this.linkPlaceholder) {
                                const He = null == (ce = null == (ne = this.quillEditor) ? void 0 : ne.theme) ? void 0 : ce.tooltip
                                  , Me = null == (Ee = null == He ? void 0 : He.root) ? void 0 : Ee.querySelector("input[data-link]");
                                (null == Me ? void 0 : Me.dataset) && (Me.dataset.link = this.linkPlaceholder)
                            }
                        }
                        ),

When I was playing around with how to import quill as a normal package inside my non-Elements app, I got the same error (only the code wasn't minified). When I debugged I found that the Quill constructor (here minified to e) was undefined. I take it this meant that quill wasn't importing correctly. Also I forgot to mention above that this is how I commented out the async import of quill in my custom service:


 this.document.addEventListener =
      (this.document as any)['__zone_symbol__addEventListener'] || this.document.addEventListener;
    // const quillImport = await import('quill');
    this.document.addEventListener = maybePatchedAddEventListener;

    // this.Quill = (quillImport.default ? quillImport.default : quillImport) as any;
111
KillerCodeMonkey commented 2 years ago

Could you share your custom service code?

Aaron Friedman @.***> schrieb am Fr., 11. Feb. 2022, 15:55:

I get:

at ..\..\dist\plots-elements\main.js:1:592662
at ZoneDelegate.invoke (zone.js:372:1)
at Zone.run (zone.js:134:1)
at Bi.runOutsideAngular (..\..\dist\plots-elements\main.js:1:1031558)
at w._next (..\..\dist\plots-elements\main.js:1:592482)
at w.__tryOrUnsub (..\..\dist\plots-elements\main.js:1:428923)
at w.next (..\..\dist\plots-elements\main.js:1:428028)
at M._next (..\..\dist\plots-elements\main.js:1:427320)
at M.next (..\..\dist\plots-elements\main.js:1:427090)
at M.next (..\..\dist\plots-elements\main.js:1:424851)

and when I look at the pretty printed line in main.js, I see:

this.zone.runOutsideAngular(()=>{ var ne, ce, Ee; if (this.quillEditor = new e(this.editorElem,{ bounds: f, debug: _, formats: te, modules: a, placeholder: u, readOnly: U, defaultEmptyValue: P, scrollingContainer: H, strict: this.strict, theme: this.theme || (this.service.config.theme ? this.service.config.theme : "snow") }), this.linkPlaceholder) { const He = null == (ce = null == (ne = this.quillEditor) ? void 0 : ne.theme) ? void 0 : ce.tooltip , Me = null == (Ee = null == He ? void 0 : He.root) ? void 0 : Ee.querySelector("input[data-link]"); (null == Me ? void 0 : Me.dataset) && (Me.dataset.link = this.linkPlaceholder) } } ),

When I was playing around with how to import quill, I got the same error (only the code wasn't minified). When I debugged I found that the Quill constructor (here minified to e) was undefined. I take it this meant that quill wasn't importing correctly. Also I forgot to mention above that this is how I commented out the async import of quill in my custom service:

this.document.addEventListener = (this.document as any)['__zone_symbol__addEventListener'] || this.document.addEventListener; // const quillImport = await import('quill'); this.document.addEventListener = maybePatchedAddEventListener;

// this.Quill = (quillImport.default ? quillImport.default : quillImport) as any;

111

— Reply to this email directly, view it on GitHub https://github.com/KillerCodeMonkey/ngx-quill/issues/1481#issuecomment-1036295795, or unsubscribe https://github.com/notifications/unsubscribe-auth/AARI4YERPU5TFQJ3KYKO7GDU2UPOLANCNFSM5OA7SYMA . You are receiving this because you commented.Message ID: @.***>

abf7d commented 2 years ago
import {QuillService, defaultModules, QUILL_CONFIG_TOKEN, QuillConfig} from 'ngx-quill';
import {DOCUMENT} from '@angular/common';
import {Injectable, Inject, Injector, Optional} from '@angular/core';
import {defer, Observable} from 'rxjs';
import {shareReplay} from 'rxjs/operators';
import * as quill  from 'quill';

@Injectable({
  providedIn: 'root'
})
export class SyncQuillService /*implements QuillService*/ {
  // eslint-disable-next-line @typescript-eslint/naming-convention
  private Quill: any = quill;
  private document: Document;
  private quill$: Observable<any> = defer(async () => {
    // Quill adds events listeners on import https://github.com/quilljs/quill/blob/develop/core/emitter.js#L8
    // We'd want to use the unpatched `addEventListener` method to have all event callbacks to be run outside of zone.
    // We don't know yet if the `zone.js` is used or not, just save the value to restore it back further.
    const maybePatchedAddEventListener = this.document.addEventListener;
    // There're 2 types of Angular applications:
    // 1) zone-full (by default)
    // 2) zone-less
    // The developer can avoid importing the `zone.js` package and tells Angular that he/she is responsible for running
    // the change detection by himself. This is done by "nooping" the zone through `CompilerOptions` when bootstrapping
    // the root module. We fallback to `document.addEventListener` if `__zone_symbol__addEventListener` is not defined,
    // this means the `zone.js` is not imported.
    // The `__zone_symbol__addEventListener` is basically a native DOM API, which is not patched by zone.js, thus not even going
    // through the `zone.js` task lifecycle. You can also access the native DOM API as follows `target[Zone.__symbol__('methodName')]`.
    // eslint-disable-next-line @typescript-eslint/dot-notation
    this.document.addEventListener =
      (this.document as any)['__zone_symbol__addEventListener'] || this.document.addEventListener;
    // const quillImport = await import('quill');
    this.document.addEventListener = maybePatchedAddEventListener;

    // this.Quill = (quillImport.default ? quillImport.default : quillImport) as any;

    // Only register custom options and modules once
    this.config.customOptions?.forEach((customOption) => {
      const newCustomOption = this.Quill.import(customOption.import);
      newCustomOption.whitelist = customOption.whitelist;
      this.Quill.register(newCustomOption, true, this.config.suppressGlobalRegisterWarning);
    });

    this.config.customModules?.forEach(({implementation, path}) => {
      this.Quill.register(path, implementation, this.config.suppressGlobalRegisterWarning);
    });

    return this.Quill;
  }).pipe(shareReplay({bufferSize: 1, refCount: true}));

  constructor(injector: Injector, @Optional() @Inject(QUILL_CONFIG_TOKEN) public config: QuillConfig) {
    this.document = injector.get(DOCUMENT);

    if (!this.config) {
      this.config = {modules: defaultModules};
    }
  }

  getQuill() {
    return this.quill$;
  }
}
abf7d commented 2 years ago

Again, this service works when the component is used in a plain Angular library imported into an Angular app. When it is compiled into a plain js file through Angular Elements, it breaks.

KillerCodeMonkey commented 2 years ago

I must say. It looks like how I would do it

Aaron Friedman @.***> schrieb am Fr., 11. Feb. 2022, 16:09:

import {QuillService, defaultModules, QUILL_CONFIG_TOKEN, QuillConfig} from 'ngx-quill'; import {DOCUMENT} from @./common'; import {Injectable, Inject, Injector, Optional} from @./core'; import {defer, Observable} from 'rxjs'; import {shareReplay} from 'rxjs/operators'; import * as quill from 'quill';

@Injectable({ providedIn: 'root' }) export class SyncQuillService /implements QuillService/ { // eslint-disable-next-line @typescript-eslint/naming-convention private Quill: any = quill; private document: Document; private quill$: Observable = defer(async () => { // Quill adds events listeners on import https://github.com/quilljs/quill/blob/develop/core/emitter.js#L8 // We'd want to use the unpatched addEventListener method to have all event callbacks to be run outside of zone. // We don't know yet if the zone.js is used or not, just save the value to restore it back further. const maybePatchedAddEventListener = this.document.addEventListener; // There're 2 types of Angular applications: // 1) zone-full (by default) // 2) zone-less // The developer can avoid importing the zone.js package and tells Angular that he/she is responsible for running // the change detection by himself. This is done by "nooping" the zone through CompilerOptions when bootstrapping // the root module. We fallback to document.addEventListener if __zone_symbol__addEventListener is not defined, // this means the zone.js is not imported. // The __zone_symbol__addEventListener is basically a native DOM API, which is not patched by zone.js, thus not even going // through the zone.js task lifecycle. You can also access the native DOM API as follows target[Zone.__symbol__('methodName')]. // eslint-disable-next-line @typescript-eslint/dot-notation this.document.addEventListener = (this.document as any)['__zone_symbol__addEventListener'] || this.document.addEventListener; // const quillImport = await import('quill'); this.document.addEventListener = maybePatchedAddEventListener;

// this.Quill = (quillImport.default ? quillImport.default : quillImport) as any;

// Only register custom options and modules once
this.config.customOptions?.forEach((customOption) => {
  const newCustomOption = this.Quill.import(customOption.import);
  newCustomOption.whitelist = customOption.whitelist;
  this.Quill.register(newCustomOption, true, this.config.suppressGlobalRegisterWarning);
});

this.config.customModules?.forEach(({implementation, path}) => {
  this.Quill.register(path, implementation, this.config.suppressGlobalRegisterWarning);
});

return this.Quill;

}).pipe(shareReplay({bufferSize: 1, refCount: true}));

constructor(injector: Injector, @Optional() @Inject(QUILL_CONFIG_TOKEN) public config: QuillConfig) { this.document = injector.get(DOCUMENT);

if (!this.config) {
  this.config = {modules: defaultModules};
}

}

getQuill() { return this.quill$; } }

— Reply to this email directly, view it on GitHub https://github.com/KillerCodeMonkey/ngx-quill/issues/1481#issuecomment-1036313777, or unsubscribe https://github.com/notifications/unsubscribe-auth/AARI4YBORZ64IMXMESJFZFDU2URC7ANCNFSM5OA7SYMA . You are receiving this because you commented.Message ID: @.***>

KillerCodeMonkey commented 2 years ago

But then I guess it happens because of quilljs

Another idea: do not import quill in your typescript.

Use window.quill and import the quilljs source from a cdn in the head of your html file.

Bengt Weiße @.***> schrieb am Fr., 11. Feb. 2022, 16:14:

I must say. It looks like how I would do it

Aaron Friedman @.***> schrieb am Fr., 11. Feb. 2022, 16:09:

import {QuillService, defaultModules, QUILL_CONFIG_TOKEN, QuillConfig} from 'ngx-quill'; import {DOCUMENT} from @./common'; import {Injectable, Inject, Injector, Optional} from @./core'; import {defer, Observable} from 'rxjs'; import {shareReplay} from 'rxjs/operators'; import * as quill from 'quill';

@Injectable({ providedIn: 'root' }) export class SyncQuillService /implements QuillService/ { // eslint-disable-next-line @typescript-eslint/naming-convention private Quill: any = quill; private document: Document; private quill$: Observable = defer(async () => { // Quill adds events listeners on import https://github.com/quilljs/quill/blob/develop/core/emitter.js#L8 // We'd want to use the unpatched addEventListener method to have all event callbacks to be run outside of zone. // We don't know yet if the zone.js is used or not, just save the value to restore it back further. const maybePatchedAddEventListener = this.document.addEventListener; // There're 2 types of Angular applications: // 1) zone-full (by default) // 2) zone-less // The developer can avoid importing the zone.js package and tells Angular that he/she is responsible for running // the change detection by himself. This is done by "nooping" the zone through CompilerOptions when bootstrapping // the root module. We fallback to document.addEventListener if __zone_symbol__addEventListener is not defined, // this means the zone.js is not imported. // The __zone_symbol__addEventListener is basically a native DOM API, which is not patched by zone.js, thus not even going // through the zone.js task lifecycle. You can also access the native DOM API as follows target[Zone.__symbol__('methodName')]. // eslint-disable-next-line @typescript-eslint/dot-notation this.document.addEventListener = (this.document as any)['__zone_symbol__addEventListener'] || this.document.addEventListener; // const quillImport = await import('quill'); this.document.addEventListener = maybePatchedAddEventListener;

// this.Quill = (quillImport.default ? quillImport.default : quillImport) as any;

// Only register custom options and modules once
this.config.customOptions?.forEach((customOption) => {
  const newCustomOption = this.Quill.import(customOption.import);
  newCustomOption.whitelist = customOption.whitelist;
  this.Quill.register(newCustomOption, true, this.config.suppressGlobalRegisterWarning);
});

this.config.customModules?.forEach(({implementation, path}) => {
  this.Quill.register(path, implementation, this.config.suppressGlobalRegisterWarning);
});

return this.Quill;

}).pipe(shareReplay({bufferSize: 1, refCount: true}));

constructor(injector: Injector, @Optional() @Inject(QUILL_CONFIG_TOKEN) public config: QuillConfig) { this.document = injector.get(DOCUMENT);

if (!this.config) {
  this.config = {modules: defaultModules};
}

}

getQuill() { return this.quill$; } }

— Reply to this email directly, view it on GitHub https://github.com/KillerCodeMonkey/ngx-quill/issues/1481#issuecomment-1036313777, or unsubscribe https://github.com/notifications/unsubscribe-auth/AARI4YBORZ64IMXMESJFZFDU2URC7ANCNFSM5OA7SYMA . You are receiving this because you commented.Message ID: @.***>

abf7d commented 2 years ago

This is interesting. When I run console.log(quill) in the constructor in my Angular app, I get

quil value:
 ƒ Quill(container) {
    var _this2 = this;

    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

    _classCallCheck(this, Quill);

    this.options = expandConfi…

When I print from the Element I get:

quil value:
Module {__esModule: true, Symbol(Symbol.toStringTag): 'Module'}
default: (...)
__esModule: true
Symbol(Symbol.toStringTag): "Module"
get default: ()=>r
KillerCodeMonkey commented 2 years ago

did you tried my idea to not bundle quilljs in your angular element and use quilljs from then window object?

KillerCodeMonkey commented 2 years ago

any updates on this?

abf7d commented 2 years ago

I was having issues trying to get this to work and I sidelined it. I will try to pick this up again as soon as I have a moment and get back to you. I think the problem I was having was that quill was undefined. I couldn't access the instance on the window object. I've done something like this before, I just have to work through it.

KillerCodeMonkey commented 2 years ago

Hey. I will close this issue then.

Feel free to reopen it, when you think it is really an issue with ngx-quill

nerd-cs commented 2 years ago

@KillerCodeMonkey I have the same issue. Could you please reopen the issue? angular v13, ngx-quill v16.2.0

I have also checked this repo and it worked properly. https://github.com/KillerCodeMonkey/ngx-quill-universal-example But if I use in the other routing, there is a problem.

KillerCodeMonkey commented 2 years ago

Since there is nothing I can do or change. I would leave it closed.

Did you tried the things mentioned in the comments above? Overwriting the quill service and using quilljs from the window Object instead of importing it?

In general I have never used angular elements. But if you need an alternative to ngx-quill try stencil-quill. It compiles to a native web component and can be used in angular, but without angular form controls/models.

RazeiXello commented 2 years ago

For any fellow Googlers.

I modified the above code and it seems to work for our Angular 13 project with Angular Elements.

import { Inject, Injectable, Injector, Optional } from '@angular/core';
import { defaultModules, QuillConfig, QuillService, QUILL_CONFIG_TOKEN } from 'ngx-quill';
import quill from 'quill';

@Injectable({
  providedIn: 'root'
})
export class CustomQuillService extends QuillService {
  constructor(
    injector: Injector,
    @Optional() @Inject(QUILL_CONFIG_TOKEN) public override config: QuillConfig
  ) {
    super(injector, config);

    // https://github.com/KillerCodeMonkey/ngx-quill/blob/6a42c119e2a4bb66db158f1cdbc085b9cb801fb4/projects/ngx-quill/src/lib/quill.service.ts#L16
    this['Quill'] = quill;

    if (!this['config']) {
      this['config'] = { modules: defaultModules };
    }
  }
}

and in the module

  providers:[
    { provide: QuillService, useClass: CustomQuillService }
  ],

I assume because this prevents the code in this if statement from executing:

https://github.com/KillerCodeMonkey/ngx-quill/blob/6a42c119e2a4bb66db158f1cdbc085b9cb801fb4/projects/ngx-quill/src/lib/quill.service.ts#L16

Though this does also bypass the addEventListener unpatching.