diff --git a/src/declarations/runtime.ts b/src/declarations/runtime.ts index fa2c7fd1f40..d59977304eb 100644 --- a/src/declarations/runtime.ts +++ b/src/declarations/runtime.ts @@ -92,6 +92,7 @@ export interface HostRef { $onReadyPromise$?: Promise; $onReadyResolve$?: (elm: any) => void; $vnode$?: d.VNode; + $queuedListeners$?: [string, any][]; $rmListeners$?: () => void; $modeName$?: string; } diff --git a/src/runtime/host-listener.ts b/src/runtime/host-listener.ts index f668dad5964..48bcad8a138 100644 --- a/src/runtime/host-listener.ts +++ b/src/runtime/host-listener.ts @@ -1,10 +1,11 @@ import * as d from '../declarations'; import { BUILD } from '@build-conditionals'; -import { consoleError, doc, plt, supportsListenerOptions, win } from '@platform'; -import { LISTENER_FLAGS } from '@utils'; +import { doc, plt, supportsListenerOptions, win } from '@platform'; +import { HOST_FLAGS, LISTENER_FLAGS } from '@utils'; export const addEventListeners = (elm: d.HostElement, hostRef: d.HostRef, listeners: d.ComponentRuntimeHostListener[]) => { + hostRef.$queuedListeners$ = hostRef.$queuedListeners$ || []; const removeFns = listeners.map(([flags, name, method]) => { const target = (BUILD.hostListenerTarget ? getHostListenerTarget(elm, flags) : elm); const handler = hostListenerProxy(hostRef, method); @@ -18,15 +19,15 @@ export const addEventListeners = (elm: d.HostElement, hostRef: d.HostRef, listen const hostListenerProxy = (hostRef: d.HostRef, methodName: string) => { return (ev: Event) => { if (BUILD.lazyLoad) { - if (hostRef.$lazyInstance$) { + if (hostRef.$flags$ & HOST_FLAGS.isMethodsCallable) { // instance is ready, let's call it's member method for this event - return hostRef.$lazyInstance$[methodName](ev); + hostRef.$lazyInstance$[methodName](ev); } else { - return hostRef.$onReadyPromise$.then(() => hostRef.$lazyInstance$[methodName](ev)).catch(consoleError); + hostRef.$queuedListeners$.push([methodName, ev]); } } else { - return (hostRef.$hostElement$ as any)[methodName](ev); + (hostRef.$hostElement$ as any)[methodName](ev); } }; }; diff --git a/src/runtime/set-value.ts b/src/runtime/set-value.ts index b721a0a9da8..c0d3389f1d7 100644 --- a/src/runtime/set-value.ts +++ b/src/runtime/set-value.ts @@ -24,7 +24,7 @@ export const setValue = (ref: d.RuntimeRef, propName: string, newVal: any, cmpMe if (!BUILD.lazyLoad || hostRef.$lazyInstance$) { // get an array of method names of watch functions to call - if (BUILD.watchCallback && cmpMeta.$watchers$ && flags & HOST_FLAGS.isWatchReady) { + if (BUILD.watchCallback && cmpMeta.$watchers$ && flags & HOST_FLAGS.isMethodsCallable) { const watchMethods = cmpMeta.$watchers$[propName]; if (watchMethods) { diff --git a/src/runtime/test/listen.spec.tsx b/src/runtime/test/listen.spec.tsx index f0e554d6fcf..fc84c42bc00 100644 --- a/src/runtime/test/listen.spec.tsx +++ b/src/runtime/test/listen.spec.tsx @@ -133,6 +133,57 @@ describe('listen', () => { expect(root).toEqualHtml(` 1,2,4,5,6 `); + }); + + it('listen before load', async () => { + let log = ''; + @Component({ tag: 'cmp-a'}) + class CmpA { + @Listen('event') + onEvent(ev: CustomEvent) { + log += ev.detail; + } + + connectedCallback() { + log += 'connectedCallback '; + } + + componentWillLoad() { + log += 'componentWillLoad '; + } + componentDidLoad() { + log += 'componentDidLoad '; + } + + render() { + return `${log}`; + } + } + + const { doc, waitForChanges } = await newSpecPage({ + components: [CmpA], + html: '', + }); + + const a = doc.createElement('cmp-a'); + doc.body.appendChild(a); + + a.dispatchEvent(new CustomEvent('event', { + detail: 'event1 ' + })); + a.dispatchEvent(new CustomEvent('event', { + detail: 'event2 ' + })); + a.dispatchEvent(new CustomEvent('event', { + detail: 'event3 ' + })); + + await Promise.resolve(); + expect(log).toEqualHtml(''); + + await waitForChanges(); + expect(log).toEqualHtml(`connectedCallback event1 event2 event3 componentWillLoad componentDidLoad`); }); + }); diff --git a/src/runtime/update-component.ts b/src/runtime/update-component.ts index a0f83bcf297..013a6029cbe 100644 --- a/src/runtime/update-component.ts +++ b/src/runtime/update-component.ts @@ -7,10 +7,10 @@ import { HYDRATED_CLASS, PLATFORM_FLAGS } from './runtime-constants'; import { renderVdom } from './vdom/vdom-render'; -export const safeCall = async (instance: any, method: string) => { +export const safeCall = async (instance: any, method: string, arg?: any) => { if (instance && instance[method]) { try { - await instance[method](); + await instance[method](arg); } catch (e) { consoleError(e); } @@ -23,8 +23,12 @@ export const scheduleUpdate = async (elm: d.HostElement, hostRef: d.HostRef, cmp } const instance = BUILD.lazyLoad ? hostRef.$lazyInstance$ : elm as any; if (isInitialLoad) { - if (BUILD.watchCallback) { - hostRef.$flags$ |= HOST_FLAGS.isWatchReady; + if (BUILD.watchCallback || BUILD.hostListener) { + hostRef.$flags$ |= HOST_FLAGS.isMethodsCallable; + } + if (BUILD.hostListener && hostRef.$queuedListeners$) { + hostRef.$queuedListeners$.forEach(([methodName, event]) => safeCall(instance, methodName, event)); + hostRef.$queuedListeners$ = null; } emitLifecycleEvent(elm, 'componentWillLoad'); if (BUILD.cmpWillLoad) { diff --git a/src/utils/constants.ts b/src/utils/constants.ts index ed3c97360a4..0eed348754c 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -43,7 +43,7 @@ export const enum HOST_FLAGS { isQueuedForUpdate = 1 << 4, hasInitializedComponent = 1 << 5, hasLoadedComponent = 1 << 6, - isWatchReady = 1 << 7, + isMethodsCallable = 1 << 7, } export const enum CMP_FLAGS {