
How to Develop Scalable Web Applications with Angular.js

Angular has been around long enough that most engineering teams have an opinion about it. Some love the structure. Some find it heavy. The honest answer is that it is genuinely well-suited to large, team-built web applications — but only if you set it up correctly from the start. This post covers what that looks like in practice.
What makes Angular different from other front-end frameworks?
Angular is a full framework, not a library. It ships with a router, an HTTP client, a forms module, a dependency injection system, and a CLI that scaffolds and builds your project. React gives you a view layer and asks you to assemble the rest. Vue sits somewhere between the two.
That distinction matters for scalability. When every team member reaches for the same built-in tools, the codebase stays coherent as it grows. The trade-off is that Angular has a steeper initial learning curve and a larger bundle baseline. For a small marketing site or a single-page tool, it is probably the wrong choice. For a multi-feature product with several engineers working in parallel, the structure pays off.
Angular 17 (released November 2023) introduced standalone components as the default, removing the need to declare every component inside an NgModule. That change meaningfully reduces boilerplate. If you are starting a new project, use standalone components from day one rather than trying to retrofit them later.
How should you structure an Angular project for a team?
Folder structure is where most Angular projects go wrong. The Angular CLI default puts everything in src/app with no further organisation. That works for a demo. It breaks down fast in a real product.
A structure that scales well looks like this:
src/
app/
core/ # Singleton services, guards, interceptors
shared/ # Reusable components, pipes, directives
features/ # One folder per feature domain
billing/
dashboard/
user-profile/
layout/ # Shell components: nav, sidebar, footer
Each feature folder should be self-contained: its own components, services, routes, and state. The rule is simple — if a component is used in only one feature, it lives in that feature folder. If it is used in two or more, it moves to shared.
Lazy loading is non-negotiable
Every feature module (or standalone route group in Angular 17+) should be lazy-loaded. Angular's router supports this with loadComponent() for standalone components or loadChildren() for route files. Lazy loading keeps your initial bundle small. On a mid-sized application, the difference between an eagerly loaded and a lazy-loaded build can be 40–60% in initial bundle size.
Configure route-level code splitting in app.routes.ts:
{
path: 'billing',
loadComponent: () =>
import('./features/billing/billing.component').then(m => m.BillingComponent)
}
This is one of those things that is cheap to do at the start and expensive to retrofit.
State management: keep it boring
Do not reach for NgRx on every project. NgRx is powerful but it adds real complexity: actions, reducers, effects, selectors, and a non-trivial amount of boilerplate. For most features, Angular signals (stable since Angular 17) or a simple service with a BehaviorSubject will do the job with far less overhead.
Use NgRx when you have genuinely complex asynchronous state that needs to be shared across many unrelated parts of the UI, or when you need time-travel debugging. Otherwise, keep state local to the feature.
/// Not sure where to start?
Get the architecture before you commit
Tell us what you're building and we'll map the technical approach, stack, and rough timeline. No cost, no obligation, no sales call required.
Performance patterns that actually matter
Angular's change detection is zone-based by default, using Zone.js to patch browser APIs and detect when something might have changed. On a large component tree, this can become a bottleneck.
Two changes make a significant difference:
OnPush change detection. Setting changeDetection: ChangeDetectionStrategy.OnPush on a component tells Angular to skip that subtree unless an input reference changes or an event fires within it. Apply this to every component you write. It is a discipline, not an optimisation you add later.
Signal-based reactivity. Angular signals, introduced as stable in Angular 17, offer fine-grained reactivity without Zone.js overhead. A signal-based component only re-renders the specific template bindings that depend on changed signals. This is closer to how SolidJS works and is the direction Angular is heading. The Angular team has indicated that Zone.js will eventually become optional.
HTTP and caching
Angular's HttpClient supports interceptors. Use them for auth token injection, error normalisation, and response caching rather than scattering that logic across individual services. A well-written cache interceptor can eliminate redundant network calls for reference data that doesn't change often, which matters on slow connections.
For data fetching patterns, consider pairing HttpClient with Angular's toSignal() utility to convert observables to signals at the component boundary. It reduces subscription management and avoids common memory leak patterns.
How do you keep a large Angular codebase maintainable?
Strict TypeScript configuration is the single highest-return investment in maintainability. Enable strict: true in tsconfig.json. This catches null reference errors, implicit any types, and unreachable code at compile time rather than at runtime. The Angular CLI generates projects with strict mode off by default, which is a poor default.
Use ESLint with @angular-eslint. The Angular team deprecated TSLint in 2021 and the community has fully moved to @angular-eslint, which provides Angular-specific rules on top of standard ESLint. Enforce rules like no-unused-vars, @angular-eslint/no-empty-lifecycle-method, and @angular-eslint/use-lifecycle-interface in CI rather than relying on individual discipline.
Component testing with the Angular Testing Library (built on top of @testing-library/angular) is more resilient than the default TestBed approach for most component tests. It tests behaviour from the user's perspective rather than testing implementation details, so tests do not break every time you refactor internals.
Monorepo considerations
If you are building multiple Angular applications that share components or services, a monorepo with Nx is worth the setup cost. Nx provides project graph analysis, affected-only builds, and enforced module boundaries via @nx/enforce-module-boundaries. The alternative — shared npm packages for internal libraries — works but slows down the inner development loop significantly.
Deployment and build configuration
Angular's production build (ng build --configuration production) enables Ahead-of-Time (AOT) compilation, tree shaking, and minification by default. There is little to configure here beyond what the CLI provides.
What does require attention is differential loading. From Angular 16 onwards, the default build target is ES2022. If you need to support older browsers, configure browserslist in your project and check that your target in tsconfig.json matches your actual browser support requirements. Shipping ES2022 to a browser that needs ES5 is a silent failure.
For serving, Angular applications are static files after build. Serve them from a CDN with aggressive cache headers on hashed asset files and short cache or no-cache headers on index.html. The build output hashes filenames by default, so this is safe. Server-side rendering via Angular Universal (now integrated into Angular CLI as @angular/ssr from v17) is worth adding if SEO or first-paint performance on slow connections is a priority.
Conclusion
Angular scales well when the structure is deliberate from the start. Standalone components, lazy-loaded routes, OnPush change detection, strict TypeScript, and a clear folder convention are not advanced topics — they are the baseline for a project you want to maintain in two years. Get those right before adding complexity elsewhere.
If you are starting a new Angular project or untangling an existing one, the most useful next step is an audit of your current change detection strategy and bundle analysis using @angular/build and webpack-bundle-analyzer. Those two things will show you where the real problems are faster than anything else.
FAQ
Is Angular still a good choice in 2024 given how popular React is? Yes, for the right use cases. Angular's integrated toolchain and enforced structure make it particularly suited to large teams and enterprise applications. React has a larger ecosystem and lower initial friction, but requires more architectural decisions upfront. The choice should come down to team familiarity and project complexity, not trends.
When should I use Angular signals instead of RxJS observables?
Use signals for local component state and derived values that update reactively. Use RxJS for event streams, HTTP responses, and anything genuinely asynchronous with operators like switchMap or debounceTime. The two work together: toSignal() and toObservable() convert between them cleanly. Signals reduce boilerplate; they do not replace RxJS entirely.
How do I reduce Angular's initial bundle size?
Lazy load all feature routes using loadComponent() or loadChildren(). Audit your shared module for components that are imported globally but used rarely — move them to feature-level imports. Run ng build --stats-json and analyse the output with webpack-bundle-analyzer. Most large bundles contain duplicated vendor code or eagerly loaded features that should be lazy.
Does Angular support micro-frontend architectures?
Yes. Module Federation via Webpack 5 is the most common approach, and there is tooling support through @angular-architects/module-federation. The setup is non-trivial and introduces deployment coordination overhead. Before committing to micro-frontends, be certain the team size and deployment independence requirements justify it. Many teams adopt it prematurely and spend more time on the architecture than on the product.
What is the difference between Angular Universal and the new @angular/ssr?
They are the same thing with a name change. Angular Universal was the community project for server-side rendering. From Angular 17, SSR support was folded into the Angular CLI as @angular/ssr, making it easier to add with ng add @angular/ssr. The underlying mechanism, rendering Angular on a Node.js server and hydrating on the client, is unchanged.
Have a project in mind? Contact Sodio Technologies to discuss your requirements and explore the right technology solution for your business.
/// Work with us
Talk to the engineers who'd build it
You'll get a technical scope, timeline and cost estimate from the people doing the work, not an account manager. In-house team, no subcontracting, since 2016.
