What lifecycle hooks are available?
Overridable hooks
All protected, all no-op by default except generateTemplate()/registerChildren():
protected onBeforeRender(): void {}
protected onAfterRender(): void {}
protected onBeforeDestroy(): void {}
protected onAfterDestroy(): void {}
protected onKept(): void {}
protected generateTemplate(): Nutin.Template { return ''; } // Component/View override this
public registerChildren(): Nutin.ComponentConfig[] { return []; }
View additionally exposes:
public onEnter(): void {}
public onExit(): void {}
Firing order
render():
onBeforeRender()
element.replaceChildren(sanitizeToFragment(generateTemplate()))
compose() → addChildren() (mount data-component/data-catalog children)
child.onKept() for every kept child (see below)
hydrate() → parseDataAttributes() (data-i18n, data-pipe)
→ cleanupOptionalContent() (data-optional)
autoBindEvents() → rebinds all data-event listeners
onAfterRender()
hydrate() leaves data-pipe inside child components alone: each child already piped its own content.
onKept():
Called on a child kept across its parent's re-render — one registered with keep, or a catalog item with keepBy whose item didn't change. A kept child isn't rendered or destroyed, so none of the other hooks fire for it: its existing DOM is moved into the parent's new template as-is, then onKept() is called. Use it for light work on that existing DOM (re-measuring, re-reading a value); don't rebuild the DOM in it — that's what a new keep value is for. See How do I register child components?.
destroy():
onBeforeDestroy()
EventHelper.destroyEvents(...) — remove all data-event DOM listeners
AppEventBus.off(...) for every bus subscription made via this.listen()
ChildrenHelper.destroyChildren(...) — recursively destroy() every child
element.remove()
onAfterDestroy()
onEnter()/onExit():
These are called only by the router, never by render()/destroy() — they fire on navigation, not on every re-render. In the full navigation sequence:
newView = route's factory() (a lazy route's chunk loads here, while the old view stays up)
oldView.destroy()
oldView.onExit()
view-unmount event
newView.setRouteParams(params)
newView.render()
newView.onEnter()
view-mount event
document.title updated, scroll to the URL's #hash, focus moved to the new view
A view rendered outside the router (e.g. new SomeView().render() by hand) never has onEnter/onExit called.
Lifecycle event-bus facade — a separate mechanism
Lifecycle (from core/index.ts) exposes emitters/subscribers with the same names as the hooks above (beforeRender, afterRender, beforeDestroy, afterDestroy, plus viewMount(viewName)/viewUnmount(viewName)):
Lifecycle.onViewMount(({ viewName }) => console.log('mounted', viewName));
Only viewMount/viewUnmount are actually emitted by the framework (from the router, on every navigation). Lifecycle.beforeRender()/afterRender()/beforeDestroy()/afterDestroy() are not auto-emitted by render()/destroy() or by the protected hooks above — overriding onBeforeRender() on your own component customizes that component's own render step, but does not cause Lifecycle.onBeforeRender(cb) subscribers elsewhere in the app to fire. Treat the two as unrelated, same-named mechanisms.
Important
ComponentoverridesonBeforeRender()/onAfterRender()internally to applyprops/data-bindings — aComponentsubclass that overrides these must callsuper.onBeforeRender()/super.onAfterRender()or it loses that behavior.Viewdoesn't override either, so this doesn't apply there.render()guards against re-entrancy: callingthis.render()again from insideonBeforeRender/onAfterRender(e.g. synchronously from a render-event handler) is a silent no-op rather than a recursive loop.