-
Notifications
You must be signed in to change notification settings - Fork 26.7k
Description
Which @angular/* package(s) are the source of the bug?
elements
Is this a regression?
No
Description
I believe the current lifecycle of Angular Elements is fundamentally flawed.
Custom elements really have two lifecycle methods which are relevant here:
connectedCallback
- Element is attached to the DOM.disconnectedCallback
- Element is detached from the DOM.
Note that an element may be reattached at any time after being detached. "Destroy" of an element just involves detaching it and dropping all references, allowing the GC to reclaim that element.
This is notable, because it means there is no explicit "destroy" interaction. Any given disconnect could be followed by a subsequent attach and any arbitrary time in the future, or could be reclaimed by the GC. There's fundamentally no way to know.
The role of Angular Elements is to translate these semantics into something reasonable for existing Angular components.
Currently on connectedCallback
we create a component and attach it to the ApplicationRef
. On disconnectedCallback
we destroy that component. In theory, reattach should then recreate the component, though that's a little broken right now.
I think this is flawed, because it loses any internal component state as part of the destroy process. We need the component to be inert and disabled but able to be reactivated in the future if it is reconnected. Basically, custom elements want an activate/deactivate lifecycle, but Angular only provides a destroy lifecycle, with no way to "undestroy" a component.
I think ideally:
connectedCallback()
should just callappRef.attachView
(pluscreateComponent
on first connect).disconnectedCallback()
should just callappRef.detachView
to deactivate it.
In this model, each reattachment to the DOM is just a pair of attachView
and detachView
calls managing its relationship with the ApplicationRef
and the overall app lifecycle. When detached, the component should be inert and eligible for GC if not otherwise referenced. I'm not sure if that's actually possible today though or if you have to call ComponentRef.prototype.destroy
to break some reference which would otherwise retain it.
We would probably need new ngOnConnected
and ngOnDisconnected
lifecycle hooks which would allow a component to acquire and release resources compatible with this model. Otherwise a component containing a simple timer would never be reclaimable, this mechanism would be necessary to disable/re-enable the timer when the component is removed/readded. These hooks need to be available for every component, not just the root component converted into a custom element, since any of them might be rendered as a descendant of the custom element.
One more challenge is that there is no appropriate time to call ngOnDestroy
since we don't know a component should be destroyed until it gets GC'd. We'd either need to remove this hook altogether, or potentially link the Angular component to its custom element via a WeakRef
and use a FinalizationRegistry
to detect when the custom element is reclaimed and call ComponentRef.prototype.destroy
in response. I'm very loathe to expose GC timing into application logic, but I don't think there's any other opportunity to do this.
Please provide a link to a minimal reproduction of the bug
Please provide the exception or error you saw
Please provide the environment you discovered this bug in (run ng version
)
v20
Anything else?
No response