Advanced
Lazy
Content loaded through a dynamic import or any Promise; the loading flow, options, retries, cancellation and error reporting.
Lazy calls a function that produces a Promise, turns the result into a component and places it in a Frame. It is designed for code splitting (() => import('./X')), but it works with any Promise.
Usage
In the example, the folder chart is loaded lazily so that Chart.js is downloaded only when needed. The component is a named export, so mapResult picks it from the module:
<Lazy
caller={() => import("../parts/FolderChart")}
options={{
mapResult: (m) => m.FolderChart,
Loaderview: <p class="loading">Loading chart…</p>,
Fallbackview: <p class="error">Couldn't load the chart.</p>,
minDelayMs: 300,
timeoutMs: 10000,
retry: 3,
}}
/>
const chart = Lazy({
caller: () => import("../parts/FolderChart"),
options: {
mapResult: (m) => m.FolderChart,
Loaderview: motifComponent("p", (p: ComponentBase) => {
p.class.add("loading");
p.setText("Loading chart…");
}),
Fallbackview: motifComponent("p", (p: ComponentBase) => {
p.class.add("error");
p.setText("Couldn't load the chart.");
}),
minDelayMs: 300,
timeoutMs: 10000,
retry: 3,
},
});
<Lazy
caller={() => import("../parts/FolderChart")}
options={{
mapResult: (m) => m.FolderChart,
Loaderview: <p class="loading">Loading chart…</p>,
Fallbackview: <p class="error">Couldn't load the chart.</p>,
minDelayMs: 300,
timeoutMs: 10000,
retry: 3,
}}
/>
const chart = Lazy({
caller: () => import("../parts/FolderChart"),
options: {
mapResult: (m) => m.FolderChart,
Loaderview: motifComponent("p", (p) => {
p.class.add("loading");
p.setText("Loading chart…");
}),
Fallbackview: motifComponent("p", (p) => {
p.class.add("error");
p.setText("Couldn't load the chart.");
}),
minDelayMs: 300,
timeoutMs: 10000,
retry: 3,
},
});
Lazy is a function; a Lazy({ caller, options }) call returns the same Frame and can be placed with controls.add. frame.navigateLazy(caller, options) and frame.navigate(promise) use the same path.
The loading flow
Placeholderview(orLoaderviewif there is none) is shown right away.caller()is called right away (it does not wait for the component to enter the DOM). IftimeoutMsis given, the load counts as failed withMJX110when the time is up; ifsignalis given,abort()cuts the load off withMJX111.- A successful result is turned into a component with
mapResult. The default converter takes the module'sdefaultexport (for the result ofimport()); if another export is needed, writemapResult: (m) => m.FolderChartas in the example. If the returned value is a class it is built withnew, and if it is a function it is built by calling it; a ready instance is used as it is (no props are given). An Options API object or factory is turned into a component as well. A module without adefaultexport, or some other object that is not a component, makes the load fail withMJX127and moves on to step 5.nullkeeps the loading view, text is placed as a text node, and a promise or an array is placed as it resolves. - If
minDelayMsis given and the load finished sooner, the remaining time is waited out (this keeps the loading view from flashing on short loads). Then the content is placed in theFrame;Loaderviewplays its leave transition and is disposed. - On failure,
onError(err)is called first. If there is aFallbackview, it is shown and the error stays there (it is not reported). Otherwise the container is emptied and the error is reported asMJX126witherrorHandler.report— it is written toconsole.errorand goes to theerrorHandler.addListenerlisteners, with the original error in thecausefield (see Error handling - Listening for reports). Having givenonErrordoes not remove the report.
For a cancelled (signal) load, onError is called once with MJX111; it makes no difference whether the cancellation arrives during an attempt or while waiting to retry. Fallbackview is not shown, the content does not change and no report is written. When the container is disposed, everything stops at the first moment of disposal: running timers and pending retries are cleared, onError is not called, no report is written, a load that finishes later is ignored and nothing is built.
Options (LazyOptions<T>)
| Option | Type | Description |
|---|---|---|
Loaderview |
ComponentBase |
The content shown while loading is in progress. |
Placeholderview |
ComponentBase |
Takes precedence over Loaderview; when both are given, only this one is shown. |
Fallbackview |
ComponentBase |
The content shown when the last attempt also fails. |
minDelayMs |
number |
The minimum time that must pass before the content is placed. |
timeoutMs |
number |
The upper limit of each attempt; MJX110 when exceeded. |
signal |
AbortSignal |
Cancelling the load from outside; MJX111. Frame.navigate(promise) gives this itself. |
mapResult |
(result) => component |
Turns the load result into a component; the default export by default. |
onError |
(error) => void |
When the last attempt also fails, and on cancellation (MJX111), before Fallbackview; not called during disposal. |
retry |
number \| LazyRetryOptions |
Retrying (below). |
onRetry |
(attempt, error) => void |
Before every retry; attempt starts at 1. |
Retrying — retry
<Lazy caller={caller} options={{ retry: 3 }} /> {/* count: 3, delayMs: 500, whenOnline: true */}
<Lazy caller={caller} options={{ retry: { count: 3, delayMs: 500, whenOnline: true } }} /> {/* explicit form (LazyRetryOptions) */}
Lazy({ caller, options: { retry: 3 } }); // count: 3, delayMs: 500, whenOnline: true
Lazy({ caller, options: { retry: { count: 3, delayMs: 500, whenOnline: true } } }); // explicit form (LazyRetryOptions)
<Lazy caller={caller} options={{ retry: 3 }} /> {/* count: 3, delayMs: 500, whenOnline: true */}
<Lazy caller={caller} options={{ retry: { count: 3, delayMs: 500, whenOnline: true } }} /> {/* explicit form (LazyRetryOptions) */}
Lazy({ caller, options: { retry: 3 } }); // count: 3, delayMs: 500, whenOnline: true
Lazy({ caller, options: { retry: { count: 3, delayMs: 500, whenOnline: true } } }); // explicit form (LazyRetryOptions)
- It is retried
counttimes; total attempts arecount + 1. - The wait grows with each attempt:
delayMs × attempt(500, 1000, 1500…). - With
whenOnline: true, if the device is offline (navigator.onLine === false), the counter does not start; the wait begins when theonlineevent arrives. - If
signalis cancelled during the wait, the attempt is dropped andonErroris called withMJX111; if the container is disposed, the attempt is dropped quietly. timeoutMsapplies to each attempt separately.
In route pages
control: () => import('./pages/X') in a route definition and fallbacks.notFound/error use their own lazy loading path; the Lazy options (loading view, retries) do not apply there. To lazily load a heavy part inside a page, use Lazy (see The Route Table - Lazy loading).
Next step
To show content in another slot, go on to Transport and TransportTo. For the framework's error reporting model, see Error Handling; for the container's timing rules, Frame.