From harness-claude
Implements Angular lazy loading with loadComponent, loadChildren, preloading strategies, and @defer blocks to reduce initial bundle size and improve time-to-interactive for large apps.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Reduce initial bundle size with loadComponent, loadChildren, preloading strategies, and deferrable views (@defer)
Builds module-free Angular 17+ apps with standalone: true components, bootstrapApplication, and lazy-loaded routes. Use for new projects, NgModule migrations, or reusable libraries to cut boilerplate.
Provides Angular best practices for performance optimization including signals, OnPush, zoneless change detection, bundle optimization, SSR, rendering, and state management. Use when writing, reviewing, or refactoring code.
Designs Angular modules using feature modules, lazy loading, dependency injection, and RxJS for organizing large applications with separation of concerns.
Share bugs, ideas, or general feedback.
Reduce initial bundle size with loadComponent, loadChildren, preloading strategies, and deferrable views (@defer)
@defer to defer heavy components below the fold until the user scrolls or interactsloadComponent in route config for standalone components — the dynamic import() creates a code-split point:
{ path: 'settings', loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent) }
loadChildren with a separate routes file for feature areas with multiple routes — this creates a single chunk for the entire feature:
{ path: 'admin', loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES) }
withPreloading(PreloadAllModules) to load all lazy routes in the background after the app boots. For fine-grained control, use QuicklinkStrategy from ngx-quicklink to preload only routes linked in the current viewport.@defer blocks in templates to defer heavy components until needed. Combine with @placeholder, @loading, and @error blocks for smooth UX.@defer (on viewport) for below-the-fold content, @defer (on interaction) for on-demand panels, and @defer (on idle) for low-priority widgets.ng build --stats-json and webpack-bundle-analyzer to verify expected chunking.// app.routes.ts — mixing loadComponent and loadChildren
export const routes: Routes = [
{
path: 'dashboard',
loadComponent: () =>
import('./dashboard/dashboard.component').then((m) => m.DashboardComponent),
canActivate: [authGuard],
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes').then((m) => m.ADMIN_ROUTES),
canMatch: [adminGuard],
},
{
path: 'profile',
loadComponent: () => import('./profile/profile.component').then((m) => m.ProfileComponent),
},
];
// admin/admin.routes.ts — feature route file
export const ADMIN_ROUTES: Routes = [
{ path: '', component: AdminDashboardComponent },
{
path: 'users',
loadComponent: () => import('./users/users.component').then((m) => m.UsersComponent),
},
{
path: 'reports',
loadComponent: () => import('./reports/reports.component').then((m) => m.ReportsComponent),
},
];
// main.ts — preloading configuration
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes, withPreloading(PreloadAllModules), withComponentInputBinding()),
],
});
<!-- @defer — deferrable views (Angular 17+) -->
<main>
<app-hero />
<!-- loads immediately, in critical bundle -->
@defer (on viewport) {
<app-product-recommendations />
@placeholder {
<div class="skeleton" style="height: 200px"></div>
} @loading (minimum 300ms) { <app-spinner /> } @error {
<p>Failed to load recommendations.</p>
} } @defer (on idle) {
<app-chat-widget />
}
</main>
loadComponent vs loadChildren: loadComponent is ideal for a single route that maps to a single standalone component. loadChildren points to an entire route array — Angular loads the chunk once and registers all child routes. Use loadChildren when a feature has 3+ related routes to avoid multiple separate chunks for the same conceptual feature.
Preloading strategies:
| Strategy | Behavior |
|---|---|
NoPreloading (default) | Lazy routes load on demand only |
PreloadAllModules | All lazy routes preload after app bootstraps |
QuicklinkStrategy | Preloads routes linked in current viewport |
Custom PreloadingStrategy | Full control — preload based on user role, connection speed, etc. |
Custom preloading strategy:
@Injectable({ providedIn: 'root' })
export class SelectivePreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<unknown>): Observable<unknown> {
return route.data?.['preload'] ? load() : EMPTY;
}
}
@defer trigger options:
on viewport — element enters the viewport (uses IntersectionObserver)on interaction — user clicks or focuses the placeholderon hover — user hovers the placeholderon idle — browser is idle (requestIdleCallback)on timer(2s) — after a delaywhen condition — any boolean expression becomes trueprefetch on idle — prefetch the chunk while displaying placeholder synchronouslyChunk naming: In angular.json, set namedChunks: true to get human-readable chunk names in build output. Combined with bundleBudgets, this helps catch lazy routes that grow unexpectedly.
Testing lazy routes:
it('navigates to admin', async () => {
await router.navigate(['/admin']);
await fixture.whenStable();
expect(location.path()).toBe('/admin');
});
https://angular.dev/guide/routing/lazy-loading-ngmodules