diff --git a/src/runtime/connected-callback.ts b/src/runtime/connected-callback.ts index 84839f636b5..947a2ee5f07 100644 --- a/src/runtime/connected-callback.ts +++ b/src/runtime/connected-callback.ts @@ -106,7 +106,11 @@ export const connectedCallback = (elm: d.HostElement) => { addHostEventListeners(elm, hostRef, cmpMeta.$listeners$, false); // fire off connectedCallback() on component instance - fireConnectedCallback(hostRef.$lazyInstance$); + if (hostRef?.$lazyInstance$) { + fireConnectedCallback(hostRef.$lazyInstance$); + } else if (hostRef?.$onReadyPromise$) { + hostRef.$onReadyPromise$.then(() => fireConnectedCallback(hostRef.$lazyInstance$)); + } } endConnected(); diff --git a/src/runtime/disconnected-callback.ts b/src/runtime/disconnected-callback.ts index a0d978ee354..137142cddee 100644 --- a/src/runtime/disconnected-callback.ts +++ b/src/runtime/disconnected-callback.ts @@ -5,10 +5,18 @@ import type * as d from '../declarations'; import { PLATFORM_FLAGS } from './runtime-constants'; import { safeCall } from './update-component'; -export const disconnectedCallback = (elm: d.HostElement) => { +const disconnectInstance = (instance: any) => { + if (BUILD.lazyLoad && BUILD.disconnectedCallback) { + safeCall(instance, 'disconnectedCallback'); + } + if (BUILD.cmpDidUnload) { + safeCall(instance, 'componentDidUnload'); + } +}; + +export const disconnectedCallback = async (elm: d.HostElement) => { if ((plt.$flags$ & PLATFORM_FLAGS.isTmpDisconnected) === 0) { const hostRef = getHostRef(elm); - const instance: any = BUILD.lazyLoad ? hostRef.$lazyInstance$ : elm; if (BUILD.hostListener) { if (hostRef.$rmListeners$) { @@ -17,11 +25,12 @@ export const disconnectedCallback = (elm: d.HostElement) => { } } - if (BUILD.lazyLoad && BUILD.disconnectedCallback) { - safeCall(instance, 'disconnectedCallback'); - } - if (BUILD.cmpDidUnload) { - safeCall(instance, 'componentDidUnload'); + if (!BUILD.lazyLoad) { + disconnectInstance(elm); + } else if (hostRef?.$lazyInstance$) { + disconnectInstance(hostRef.$lazyInstance$); + } else if (hostRef?.$onReadyPromise$) { + hostRef.$onReadyPromise$.then(() => disconnectInstance(hostRef.$lazyInstance$)); } } }; diff --git a/src/runtime/test/lifecycle-sync.spec.tsx b/src/runtime/test/lifecycle-sync.spec.tsx index 8df0a3173d0..fc4749a5d76 100644 --- a/src/runtime/test/lifecycle-sync.spec.tsx +++ b/src/runtime/test/lifecycle-sync.spec.tsx @@ -346,4 +346,78 @@ describe('lifecycle sync', () => { `); }); + + it('call disconnectedCallback even if the element is immediately removed', async () => { + let connected = 0; + let disconnected = 0; + + @Component({ tag: 'cmp-a' }) + class CmpA { + connectedCallback() { + connected++; + } + + disconnectedCallback() { + disconnected++; + } + + render() { + return ; + } + } + + const { doc, waitForChanges } = await newSpecPage({ + components: [CmpA], + }); + + const a1 = doc.createElement('cmp-a'); + doc.body.appendChild(a1); + a1.remove(); + + await waitForChanges(); + + expect(connected).toEqual(1); + expect(disconnected).toEqual(1); + }); + + it('calls disconnect and connect when an element is moved in the DOM', async () => { + let connected = 0; + let disconnected = 0; + + @Component({ tag: 'cmp-a' }) + class CmpA { + connectedCallback() { + connected++; + } + + disconnectedCallback() { + disconnected++; + } + + render() { + return ; + } + } + + const { doc, waitForChanges } = await newSpecPage({ + components: [CmpA], + }); + + const cmp = doc.createElement('cmp-a'); + doc.body.appendChild(cmp); + + await waitForChanges(); + + // Create a container we will move the component to + const container = doc.createElement('div'); + doc.body.appendChild(container); + + // Move the component + container.appendChild(cmp); + + container.remove(); + + expect(connected).toEqual(2); + expect(disconnected).toEqual(2); + }); });