Angular Router
The Angular Router, shipped in the @angular/router
package, is a central and critical part of building SPAs that act and behave like regular websites that are easy to navigate, using browser controls or the zoom or micro zoom controls.
The Angular Router has advanced features such as lazy loading, router outlets, auxiliary routes, smart active link tracking, and the ability to be expressed as an href
, which enables a highly flexible Router-first app architecture leveraging stateless data-driven components, using RxJS BehaviorSubject
or a signal
.
A class (a component or a service in Angular) is stateless if it doesn’t rely on instance variables in executing any of its behavior (via functions or property getters/setters). A class is data-driven when it’s used to manage access to data. A stateless data-driven component can hold references to data objects and allow access to them (including mutations via functions) but would not store any bookkeeping or state information in a variable.
Large teams can work against a single code base, with each team responsible for a module’s development, without stepping on each other’s toes while enabling easy continuous integration. With its billions of lines of code, Google works against a single code base for a very good reason: integration after the fact is very expensive.
Small teams can remix their UI layouts on the fly to quickly respond to changes without having to rearchitect their code. It is easy to underestimate the time wasted due to late-game changes in layout or navigation. Such changes are easier for larger teams to absorb but costly for small teams.
Consider the following diagram; first off, depending on the bootstrap configuration, the app will either be a standalone or NgModule
project. Regardless, you’ll define a rootRouter
at the root of your application; components a
, master
, and detail
; services; pipes; directives; and other modules will be provided. All these components will be parsed and eagerly loaded by the browser when a user first navigates to your application.
Figure 1.16: Angular architecture
If you were to implement a lazily loaded route, /b
, you would need to create a feature module named b
, which would have its childRouter
; components /b/a
and /b/b
; services; pipes; directives; and other modules provided for it. During transpilation, Angular will package these components into a separate file or bundle, and this bundle will only be downloaded, parsed, and loaded if the user ever navigates to a path under /b
.
In a standalone project, you can lazy load other standalone components represented by the triangles. You can organize components in a route configuration file. The /c/a
and /c/b
components will have access to providers at the root level. You may provide an environment injector for a specific component in the route config file. Practically speaking, this is only useful if you want to provide a service only ever used by that component or one with a specific scope, e.g., a state that’s only used by that component. In contrast to a NgModule
app, you will have to declare the modules you’re using in each component granularly. However, unlike an NgModule
app, root-level providers not used by any component are tree-shakable. The combination of these two properties results in a small app bundle, and given each module can be individually lazy loaded, the size of each bundle will be smaller as well, leading to better overall performance.
Let’s investigate lazy loading in more detail.
Lazy loading
The dashed line connecting /b/...
to rootRouter
demonstrates how lazy loading works. Lazy loading allows developers to achieve a sub-second First Meaningful Paint quickly. By deferring the loading of additional modules, we can keep the bundle size delivered to the browser to a minimum. The size of a module negatively impacts download and loading speeds because the more a browser has to do, the longer it takes for a user to see the app’s first screen. By defining lazily loaded modules, each module is packaged as separate files, which can be downloaded and loaded individually and on demand.
The Angular Router provides smart active link tracking, which results in a superior developer and user experience, making it very easy to implement highlighting features to indicate to the user the current tab or portion of the currently active app. Auxiliary routes maximize components’ reuse and help easily pull off complicated state transitions. With auxiliary routes, you can render multiple master and detail views using only a single outer template. You can also control how the route is displayed to the user in the browser’s URL bar and compose routes using routerLink
in the template and Router.navigate
in the component class, driving complicated scenarios.
In Chapter 4, Creating a Router-First Line-of-Business App, I cover implementing router basics, and advanced recipes are covered in Chapter 8, Recipes – Reusability, Forms, and Caching.
Beyond routing, state management is another crucial concept to master if you want to build sophisticated Angular applications.