How to Make Angular Apps Faster with Performance Tips
In the previous lesson, we learned how to build reusable UI components in Angular, which helps keep code clean and easy to manage. Now, we’ll focus on making those apps run faster. Slow apps frustrate users, and search engines rank them lower. I faced this issue when my Angular app took too long to load. After applying these fixes, load time dropped by 40%.
Why Angular Performance Matters
Users expect apps to load fast. If your Angular app lags, people leave. Google also ranks slow sites lower. I once built an e-commerce app that lost sales because pages took 5+ seconds to load. After optimizing, sales improved by 25%.
Key fixes include:
- Lazy loading (load only needed code)
- Ahead-of-Time (AOT) compilation (faster rendering)
- ChangeDetectionStrategy (reduce unnecessary checks)
Let’s see how each works.
Lazy Loading for Faster Load Times
Lazy loading is a technique where your Angular app loads only the necessary parts when they’re needed, rather than loading everything upfront. Think of it like a restaurant menu—instead of cooking every dish at once (which would take forever), the kitchen prepares each order only when a customer asks for it.
Why It Matters
- Faster initial load time → Users see the first page quicker.
- Smaller bundle size → Less code is sent to the browser at startup.
- Better user experience → No long waits for features they might not even use.
Real-World Problem I Faced
I built an admin dashboard with 10 different modules (user management, reports, settings, etc.). When the app loaded, it downloaded all modules at once, making the startup painfully slow (5+ seconds). Users complained, and some even left before the dashboard loaded.
The Fix: Lazy Loading
Instead of forcing users to wait for everything, I restructured the app so that:
- Only the login module loaded first (since users always start there).
- Other modules (like DashboardModule, ReportsModule) loaded only when accessed.
How to Implement Lazy Loading Step-by-Step
1. Set Up Routes with loadChildren
Angular makes this easy with loadChildren. Instead of importing modules directly, you tell Angular to load them dynamically when the route is visited.
const routes: Routes = [
// Normal route (eager-loaded)
{ path: 'login', component: LoginComponent },
// Lazy-loaded route
{ path: 'dashboard', loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule) },
// Another lazy-loaded route
{ path: 'reports', loadChildren: () => import('./reports/reports.module').then(m => m.ReportsModule) }
];
2. Verify Lazy Loading Works
After setting this up, run:
ng build --prod
Check the dist/ folder. You should see separate JavaScript files like:
- main.js (core app)
- dashboard-module.js (loaded only when visiting /dashboard)
- reports-module.js (loaded only when visiting /reports)
3. Test in the Browser
Open Chrome DevTools (F12 → Network tab). When you first load the app, only main.js downloads. When you navigate to /dashboard, you’ll see dashboard-module.js load dynamically.
When Should You Use Lazy Loading?
- Large Apps with Many Features (Admin Panels, Dashboards)
- Problem: If your app has 10+ modules (like user management, analytics, settings), loading all at startup slows things down.
- Solution: Lazy load secondary features so only the core (like login) loads first.
- Example:
An admin dashboard loads the login screen first.
Only when an admin clicks “Reports” does that module load.
Result? Faster startup time.
- Public Websites Where SEO & Speed Matter
- Problem: Google ranks slow sites lower. Users leave if pages take >3s to load.
- Solution: Lazy load non-critical pages (like “About Us,” “FAQ”) while keeping the homepage fast.
- Example:
An e-commerce site loads the product page immediately.
The “Return Policy” page loads only when clicked.
Improves Google PageSpeed score and keeps visitors engaged.
- Mobile Users on Slow Networks
- Problem: Mobile users on 3G/4G suffer with large bundle downloads.
- Solution: Lazy loading reduces initial data usage.
- Example:
A food delivery app loads the menu first.
The “Order History” section loads only when the user checks past orders.
Users in areas with poor networks get a smoother experience.
Common Lazy Loading Mistakes
- Using the Old Syntax (loadChildren: ‘./path#Module’)
- Issue: Angular has deprecated the string-based lazy loading syntax.
- Fix: Always use dynamic import():
- Why? The new syntax works better with modern bundlers like Webpack.
// Correct (modern)
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
// Deprecated (avoid)
loadChildren: './admin/admin.module#AdminModule'
- Not Checking the Build Output
- Issue: If chunks aren’t split correctly, lazy loading won’t work.
- Fix: Run ng build —prod and check the dist/ folder: If you don’t see separate files? Your lazy loading setup is broken.
dist/
├── main.js (Core app)
├── admin-module.js (Lazy-loaded)
└── reports-module.js (Lazy-loaded)
- Overusing Lazy Loading (Too Many Tiny Modules)
-
Issue: Splitting every tiny feature into separate modules causes:
- Too many HTTP requests (slows down navigation).
- Harder to maintain.
-
Fix: Group related features into larger modules.
- Bad: Separate modules for “User List,” “User Details,” “User Settings.”
- Better: One
UsersModule
with all user-related components.
Ahead-of-Time (AOT) Compilation
Angular apps rely on templates (HTML with dynamic data) that need to be compiled before the browser can render them. There are two ways Angular handles this:
-
Just-in-Time (JIT) Compilation – Compiles templates in the user’s browser at runtime.
-
Ahead-of-Time (AOT) Compilation – Compiles templates during the build process before deploying the app.
Why JIT Slows Down Your App
When I first built an Angular dashboard, I used JIT because it was the default in development mode. But in production, users faced a 3-second delay before seeing anything. Why?
- The browser had to download the Angular compiler (~150KB extra).
- Templates compiled on the fly, adding CPU overhead.
- Larger bundle size meant slower downloads.
How AOT Solves This
AOT moves compilation from the browser to the build step. Here’s what happens:
- During ng build, Angular converts templates into highly optimized JavaScript.
- The browser gets pre-compiled code, skipping runtime compilation.
- Unused code is stripped out (tree-shaking), making the app smaller.
After switching to AOT, my app:
- Loaded 2x faster (no compiler download).
- Rendered instantly (no runtime compilation lag).
- Reduced bundle size by ~30%.
How to Enable AOT in Your Angular App
AOT is automatically enabled in production builds (ng build —prod). But if you need to control it manually:
- Using the CLI
ng build --aot # Explicitly enable AOT
ng build --prod # AOT is on by default here
- Configuring angular.json
"configurations": {
"production": {
"aot": true,
"optimization": true,
"outputHashing": "all",
"sourceMap": false
}
}
- Forcing AOT in Development (Testing)
Sometimes, you want to test AOT locally:
ng serve --aot
Common AOT Errors & Fixes
AOT is stricter than JIT. If your app works in dev but breaks in prod, check:
-
Undefined variables in templates
<div>{{ user.nam }}</div>
(Typo: nam instead of name)
Fix: Always use correct property names. -
Private members in templates
{{ privateVar }}
(AOT can’t access private fields)
Fix: Make template-bound fields public. -
Function calls in templates
*ngIf="checkAccess()"
(AOT prefers pure pipes/properties)
Fix: Use a property instead:
get canAccess() { return this.checkAccess(); }
ChangeDetectionStrategy for Efficiency
Angular keeps the UI in sync with data by running change detection. By default, it checks for changes frequently, which can slow down your app if components are complex or update often.
Why Default Change Detection Can Be Slow
Angular’s default strategy checks all components whenever an event (like a click or API call) happens. If you have a large list or complex UI, this can cause lag.
I faced this issue in a dashboard app where a table with 500 rows froze for a second every time data updated. The problem? Angular was re-rendering the whole table even when only one row changed.
How OnPush Fixes This
ChangeDetectionStrategy.OnPush makes Angular update a component only when:
- Input properties change (new reference).
- An event inside the component fires (like a button click).
- You manually trigger change detection (using ChangeDetectorRef).
This reduces unnecessary checks, making rendering faster.
When to Use OnPush
- Lists with frequent updates (like real-time data feeds).
- Parent components passing data to children (prevents child re-renders if inputs don’t change).
- Performance-critical sections (charts, grids, animations).
How to Apply OnPush Correctly
- Mark inputs as immutable (avoid in-place changes).
// Bad (mutation won’t trigger OnPush updates)
this.user.name = 'New Name';
// Good (new reference triggers update)
this.user = { ...this.user, name: 'New Name' };
- Use ChangeDetectorRef for manual updates (if needed).
constructor(private cdr: ChangeDetectorRef) {}
updateData() {
this.dataService.fetchData().subscribe(data => {
this.data = data;
this.cdr.markForCheck(); // Force update
});
}
- Combine with async pipe (auto-unsubscribe + change detection).
<div *ngFor="let item of data$ | async">{{ item.name }}</div>
Other Speed Boosts for Angular Apps
To make your Angular app even faster, small optimizations can add up. Here are three key tweaks I’ve used to improve performance:
- Minify Code with
ng build --prod
When you runng build --prod
, Angular automatically:
* Minifies code (removes extra spaces, shortens variable names).
* Tree-shakes (removes unused code).
* Optimizes images (if configured).
Before, my app’s main bundle was 2MB. After minification, it dropped to 800KB—cutting load time by 30%.
- Use trackBy in
*ngFor
to Prevent Unnecessary Re-renders
When looping through lists with*ngFor
, Angular re-renders all items if the array changes—even if only one item updates. This slows down the app.
Problem:
<div *ngFor="let user of users">{{ user.name }}</div>
If users updates, Angular re-creates all div elements.
Fix with trackBy:
trackByFn(index: number, user: User) {
return user.id; // Unique identifier
}
<div *ngFor="let user of users; trackBy: trackByFn">{{ user.name }}</div>
Now, Angular only updates changed items, making list rendering faster.
- Avoid Heavy Logic in Templates (Move to Services)
Complex calculations in templates slow down change detection.
Bad Example:
<p>{{ calculateTotalPrice() }}</p>
Here, calculateTotalPrice()
runs every time Angular checks for changes.
Better Approach:
Move logic to a service and pre-calculate values:
// In component
this.totalPrice = this.priceService.calculateTotal();
<p>{{ totalPrice }}</p>
This reduces workload and speeds up rendering.
Conclusion
By applying lazy loading, AOT compilation, and ChangeDetectionStrategy.OnPush, we can make Angular apps significantly faster. These optimizations help reduce load times, improve rendering speed, and minimize unnecessary processing—key factors that keep users engaged.
In my own experience, an e-commerce app I built had an initial load time of 5 seconds, which led to high bounce rates. After implementing these techniques:
-
Lazy loading split the app into smaller chunks, loading only what was needed.
-
AOT compilation eliminated runtime template processing, making rendering near-instant.
-
OnPush change detection reduced unnecessary checks, improving UI responsiveness.
The result? Load time dropped to just 2 seconds, leading to higher user retention and more conversions.
If you want to take performance further, the next lesson covers caching and advanced lazy loading techniques, which can reduce server calls and speed up repeat visits.
Comments
There are no comments yet.