Course outline

Angular / Advanced / Lesson 15

Deferred loading and route boundaries

Separate when code is loaded from when data is fetched, make deferred states usable, and verify the actual build rather than assuming a block creates an effective split.

About 5 min readContent reviewed
On this page

Lesson goals

  • Explain defer triggers, supporting states, and dependency constraints.
  • Choose route-level and view-level boundaries for different work.
  • Test loading, failure, and accessibility without fabricated timings.

Loading code and requesting data are separate operations

A feature can defer a component’s JavaScript while still fetching data eagerly, or load code eagerly while delaying a request. Define the intended boundary before choosing a mechanism. Route-level loadComponent can delay a page dependency until navigation, while @defer can delay a portion of a view according to triggers. Neither automatically secures data, cancels every request, or guarantees a smaller initial bundle in every layout. A dependency referenced eagerly elsewhere can defeat the intended split. Choose boundaries from real user tasks and measure the emitted chunks and interaction behavior under the stated build configuration.

Check deferred-template grammar locally

The complete program uses the official Angular compiler parser on a synthetic deferred view. It includes a native trigger button, an unloaded-state block, and an error block. Passing the parser establishes syntactic validity, not whether ReportPanel exists, is standalone, is imported, or is emitted in a separate chunk. The selector is intentionally contextual. A full application test must verify those semantic and build conditions. This local check starts no browser, network request, or application server and makes no timing claim. Keep that limitation visible when reusing parser checks in a build workflow.

typescript Complete local deferred-template parser check
import { parseTemplate } from "@angular/compiler";
const template = `
<button #trigger type="button">Show report</button>
@defer (on interaction(trigger)) {
  <app-report-panel />
} @placeholder {
  <p>Report not loaded</p>
} @error {
  <p>Report could not load</p>
}`;
const parsed = parseTemplate(template, "report.html");
if (parsed.errors?.length) { throw new Error("defer parse failed"); }
console.log("defer template parsed");
Expected output
defer template parsed

The success line confirms parsing only. Report rendering, code splitting, trigger activation, and error presentation are not executed.

Make deferred dependencies eligible and states understandable

Angular’s documented defer constraints include standalone direct dependencies and avoiding eager references to those dependencies elsewhere in the same file. Review the exact baseline rules instead of assuming any child inside a block is automatically deferred. Supporting blocks can describe unloaded, loading, and failed states, with optional timing controls that should serve the user rather than hide errors. Keep the trigger keyboard-accessible and provide a recovery path for loading failure. Code download is not the same as data readiness, so a loaded report can still need its own loading or empty state. Test layout stability and focus when the deferred content appears.

Use a route boundary for page-level loading

The contextual route assumes a ReportsPage standalone export in reports-page.ts. Its dynamic import lets the application build treat the page as a lazy route dependency when no other eager path retains it. The router still needs provision and an outlet in the shell. A direct URL must be served by the chosen host, and a failed chunk download needs a deliberate navigation outcome. The fragment is not run here because it depends on the page file and application configuration. Do not claim exact chunk names or size savings without inspecting the actual generated build.

typescript Contextual lazy page route
import { Routes } from "@angular/router";
export const routes: Routes = [
  { path: "reports", loadComponent: () => import("./reports-page").then(module => module.ReportsPage) }
];

With the declared file and router setup, navigation can load the page dependency. No route, chunk request, or deployed host is executed here.

Measure download and interaction together

Inspect the built dependency graph to confirm the intended code is absent from the initial path and present when needed. Test unloaded, activated, loading, failure, repeated activation, and navigation-away cases. Compare cold and cached loading separately, with realistic device and network conditions only in an authorized test environment. A faster initial view can still harm the feature if the user waits unexpectedly after every action. Prefetching changes when resources are used and should follow the workload, not a universal prescription. Preserve accessible names and focus in all supporting states. Report parser checks, build inspection, and browser measurements as different evidence.

Common errors and fixes

A deferred component remains in the initial bundle.

Why it happens
An eager reference or unsupported dependency shape defeats splitting.
How to fix it
Review defer eligibility and the emitted dependency graph.
Prevent it next time
Inspect actual build output before claiming savings.

The unloaded view has no usable keyboard trigger.

Why it happens
Loading was optimized without the interaction contract.
How to fix it
Use an accessible native trigger and recovery path.
Prevent it next time
Test keyboard and focus across every deferred state.

A successful parser check is treated as chunk verification.

Why it happens
Syntax acceptance was confused with application build semantics.
How to fix it
Add build inspection and a complete host test.
Prevent it next time
Label each verification layer precisely.

Check your understanding

1. The report component is referenced outside the defer block in the same file. Explain the likely design problem and the evidence needed before fixing it.

Show worked solution

The eager reference can prevent the intended dependency from being deferred under the documented rules. Inspect all references and the emitted chunks before restructuring the view. Remove or move the eager dependency only if the feature contract permits it, then test the unloaded and activated behavior. A valid template alone does not establish the desired loading boundary.

2. A page loads its code lazily but starts its expensive data request at application startup. Is the whole feature deferred? Describe the missing decision.

Show worked solution

No. The code boundary and data-request boundary are independent. Decide whether the request should begin at startup, navigation, interaction, or an explicit prefetch condition, based on latency and resource requirements. Test request counts and cancellation separately from chunk loading. Do not move the request merely to satisfy a slogan; preserve the intended user experience and server policy.

Key takeaways

  • Separate code loading from data readiness.
  • Verify eligibility and actual chunks.
  • Make every deferred state usable and testable.

Official references