Routing
Route Reuse and Component Caching
To preserve component state during browser back navigation (such as scroll position or input values in search forms), Subato implements a custom RouteReuseStrategy (CustomReuseStrategy). This strategy caches components when navigating away and restores them when returning through browser back navigation instead of creating new component instances. Since route reuse is not required for every component, it must be enabled explicitly. To cache a page component and all of its child components, the reuseRoute option needs to be set to true for the corresponding route.
Components remain in the cache until they are restored through a back navigation. If a component is accessed through another path (e.g. a forward navigation), the component is created again and not reused. This matches the behavior users expect from traditional web applications.
A reused component must implement the onAttach/onDetach lifecycle hooks from the
ComponentWithOptionalLifecycleHooks interface, so that observables are paused when navigating away
(onDetach) and resumed on back navigation (onAttach). Otherwise, observables may keep emitting
while the component is no longer in the foreground, which may cause unexpected side effects that are difficult to localize.
State management approaches (such as NgRx stores) are a viable alternative, but were not adopted due to the additional complexity and implementation effort involved.
Lazy Loading
Routes are currently not lazy loaded. Although lazy loading is generally recommended, displaying breadcrumbs requires all routes to be available upfront. At the current application size, the additional bundle size is not considered problematic. With lazy loading enabled, the bundle size is approximately 5 MB. Without lazy loading, it is approximately 15 MB.
Named Routes
Angular currently does not support named routes, which makes reusing links in templates and navigation logic difficult. As such, route paths must be hard-coded and duplicated throughout the application, making route changes more difficult to maintain. The following example illustrates the problem:
<a [routerLink]="['/', 'submissions', exerciseResult.submission.id]">
...
</a>
this.router.navigate(['/', 'submissions', exerciseResult.submission.id]);
A prototype was implemented to address this limitation. CustomRouterLinkDirective allows link objects to be bound instead of manually constructing route paths. This enables the creation of statically typed directives, such as SubmissionDetailLinkDirective, which can be used as follows:
<button [submissionDetail]="{ submissionId: submission.id }">
...
</button>
or:
<a [submissionDetail]="{ submissionId: submission.id }">
...
</a>
This approach is not yet implemented consistently throughout the application.