Skip to content

Application in depth ​

Creating an application closed the loop for “create and mount.” This page goes one step further: how the app instance behaves at runtime, and how to organize provide, use, render, and destroy in larger codebases.

Runtime lifecycle ​

Think of the app instance as a fixed sequence:

  1. createApp(root, ...) — construct the instance and queue configuration.
  2. provide(...) / use(...) — wire root providers and modules before startup.
  3. mount(container) — start the app and render once.
  4. render() (optional) — manually schedule another paint.
  5. destroy() — tear down the app and run module cleanup.

Two rules matter: each instance allows only one successful mount—a second call throws an “already started” style error; after destroy() do not treat that instance as reusable—create a new one to restart.

autoUpdate and render() refresh modes ​

createApp accepts either:

  • createApp(root, autoUpdate?: boolean)
  • createApp(root, config?: Partial<ConfigWithoutRoot>)

Default: automatic refresh ​

autoUpdate defaults to true. Reactive changes follow the normal scheduling path into the view.

tsx
const app = createApp(<App />)
app.mount(document.getElementById('app')!)
// subsequent reactive updates repaint automatically

Manual: explicit refresh ​

With autoUpdate set to false, only the first render runs; later updates require app.render().

tsx
const app = createApp(<App />, { autoUpdate: false })
app.mount(document.getElementById('app')!)

// after state that affects the view changes, trigger a render
app.render()

Common in tests, externally driven schedulers, or when batching many writes into one controlled paint. For the basics, see Creating an application.

Module hooks ​

Register modules with app.use(module). Typical hooks:

  • setup(app) — runs during mount, before the first render; wire providers and one-time setup.
  • onAfterStartup(app) — after the first render; work that needs a “UI is up” environment.
  • onDestroy() — when app.destroy() runs; release listeners, timers, connections, etc.
tsx
import type { Module } from '@viewfly/core'

const MetricsModule: Module = {
  setup(app) {
    app.provide([{ provide: METRICS_ENABLED, useValue: true }])
  },
  onAfterStartup() {
    startMetricsCollect()
  },
  onDestroy() {
    stopMetricsCollect()
  },
}

Multiple modules run in use(...) registration order. If modules depend on each other, make that order explicit to avoid hidden coupling.

App-level wiring: provide and use ​

Two common strategies:

Central entry assembly ​

Declare base dependencies and core modules in one place—clear structure for most apps.

tsx
createApp(<App />)
  .provide([ConfigService, LoggerService])
  .use([RouterModule, MetricsModule])
  .mount(document.getElementById('app')!)

Self-wiring modules ​

Modules add their own providers inside setup—good for pluggable or well-bounded features.

tsx
const FeatureModule: Module = {
  setup(app) {
    app.provide([{ provide: FEATURE_FLAG, useValue: true }])
  },
}

Combine both: globals at the entry, domain providers inside modules.
For scope, dynamic registration, and advanced DI, see Dependency injection in depth.

Teardown and cleanup ​

destroy() is not only “remove the page”—it must run the app-level cleanup chain, especially each module’s onDestroy().

Call destroy() when unmounting micro-frontend child apps, replacing the app on a new root, or in test teardown.

If a module opens side-effectful resources (subscriptions, timers, sockets), release them in onDestroy() to avoid leaks and duplicate work.

Troubleshooting ​

1) Second mount throws ​

One start per instance. Fix: destroy the old app and createApp again, or use a fresh instance.

2) State changes but UI stays stale ​

Often autoUpdate: false without app.render(). Fix: call render() after relevant updates, or re-enable auto update.

3) Subscriptions/logging after navigation or unmount ​

Side effects created in setup / onAfterStartup without matching onDestroy. Fix: unsubscribe, clear timers, close connections in onDestroy().

Next steps ​

MIT License