Reusing Angular routes across different modules for backbutton functionality

Insights on my Application (Angular 12):

  • Comprises of 3 Modules, each containing an overview page with a list and specific detail pages
  • Each route is assigned an area tag to identify the user's navigation within the module

Goal for Angular´s RouteReuseStrategy implementation:

  • Reusing the list component when a user navigates from list -> detail page and uses the back button (detect back button trigger)

  • Avoiding the reuse of the list component when a user navigates to it from a different module / area

  • Reusing the detail component when a user navigates from one detail page to another (default behavior?)

  • Clearing / destroying stored components when a user leaves a module by navigating elsewhere or logging out

Current Status:

  • Implemented a custom RouteReuseStrategy successfully that reuses the list component ✓

    • Issue with scroll position restoration which requires separate investigation ✕
  • Attempting to check the areatag within the route, but ActivatedRouteSnapshots are empty ✕

  • Facing difficulties in detecting back button press due to frequent event firing and issues with implementing a basic back flag ✕

Missing Components:

  • Detecting back button navigation and adjusting component reuse accordingly

  • Identifying the module of the route to modify reuse behavior or clean up stored components

Code Snippets:

Sample route in Module A

{
  path: 'lista',
  component: ListAComponent,
  data: {
    title: 'List overview',
    areaCategory: AreaCategory.A,
    reuseRoute: true,
  },
},
{
  path: 'lista/:id',
  component: DetailAComponent,
  data: {
    title: 'Detail',
    areaCategory: AreaCategory.A,
    reuseRoute: false,
  },
},

Sample route in Module B

{
  path: 'listb',
  component: ListBComponent,
  data: {
    title: 'List overview',
    areaCategory: AreaCategory.B,
    reuseRoute: true,
  },
},
{
  path: 'listb/:id',
  component: DetailBComponent,
  data: {
    title: 'Detail',
    areaCategory: AreaCategory.B,
    reuseRoute: false,
  },
},

app.module.ts

providers: [
{
  provide: RouteReuseStrategy,
  useClass: CustomReuseRouteStrategy,
}
],

Would this placement suffice globally or should it be moved to each of the 3 modules?

ReuseRouteStrategy Implementation:

@Injectable()
export class CustomReuseRouteStrategy implements RouteReuseStrategy {
// Code details omitted for brevity 

The inconsistency in the future & current snapshot prompts the need to correctly identify routes/components to access areaCategory for desired functionality.

If you spot any discrepancies or have insights to share, your assistance would be greatly valued.

For further reference, here is a link to a simplified stackblitz showcasing my setup

Answer №1

This adheres to your specified conditions

  1. When a user navigates from the list to the detail page and then uses the back button, the list component should be reused (detect when the back button is triggered). Yes. Assuming there are no repeated navigation sequences like list -> detail -> list
  2. If a user navigates to the list component from a different module or area, the list component should not be reused. Yes. Assuming different areas cannot share the same areaCategory tag
  3. When a user navigates from one detail page to another, the detail component should be reused (default behavior?). Yes
  4. Whenever a user leaves a module by navigating to another module or logging out, the stored components should be cleared or destroyed. Yes, similar to point 2.

I am providing additional comments and a diagram to help you understand how to utilize the RouteReuseStrategy.

Below is a diagram along with pseudocode illustrating how Angular core uses the strategy (based on my observations as official documentation is unavailable):

https://i.sstatic.net/2Edsu.png

transition(current, future) {
  if (shouldReuseRoute(future, current)) {
    // No navigation is performed, current route is recycled 
    return current;
  } else {
    if (shouldDetach(current)) {
      // Store current route if not reused and shouldDetach() == true
      store(current, getRouteHandler(current));
    }
    if (shouldAttach(future)) {
      // Retrieve stored route if not reused and shouldAttach() == true
      return createRouteFromHandler(retrieve(future));
    } else {
      // Do not recycle if shouldAttach() == false
      return future;
    }
  }
}

Please note that this is just an example. getRouteHandler and createRouteFromHandler are introduced for illustrative purposes, and no distinction is made between route component, route instance, and route snapshot.

@Injectable()
export class CustomReuseRouteStrategy implements RouteReuseStrategy {
  private handlers: { [key: string]: DetachedRouteHandle } = {};

  clearHandlers() {
    Object.keys(this.handlers).forEach(h => {
      // https://github.com/angular/angular/issues/15873
      (h as any).componentRef.destroy();
    })
    this.handlers = {};
  }

  areSameArea(future: ActivatedRouteSnapshot, current: ActivatedRouteSnapshot): boolean {
    return future.routeConfig.data && current.routeConfig.data
      && future.routeConfig.data.areaCategory === current.routeConfig.data.areaCategory;
  }

  /**
   * This function determines whether the current route should be retained.
   * If it returns `true`, neither attach nor detach procedures are executed.
   * If it returns `false`, an attach/detach procedure is initiated.
   */
  shouldReuseRoute(future: ActivatedRouteSnapshot, current: ActivatedRouteSnapshot): boolean {
    console.log('shouldReuseRoute', future, current);

    if (!this.areSameArea(future, current)) {
      // Area has changed, so clear the cache
      this.clearHandlers();
    }

    return this.getUrl(future) === this.getUrl(current);
  }

  /**
   * DETACH PROCEDURE: Called when performing a detach, if it returns
   * `true`, then the current context is stored.
   */
  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    console.log('shouldDetach', route);
    // We always detach them (assuming all pages are recycled by default)
    return true;
  }

  store(route: ActivatedRouteSnapshot, handler: DetachedRouteHandle): void {
    console.log('store', route, this.getUrl(route));
    if (!handler && !this.getUrl(route)) return;
    this.handlers[this.getUrl(route)] = handler;
  }

  /**
   * ATTACH PROCEDURE: Called when performing an attach, if it returns
   * `true`, then the current context is retrieved.
   */
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    console.log('shouldAttach', route);
    return !!this.handlers[this.getUrl(route)];
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    console.log('retrieve', route, this.getUrl(route));
    return this.getUrl(route) && this.handlers[this.getUrl(route)];
  }


  private getUrl(route: ActivatedRouteSnapshot): string {
    // Note: Behavior may differ for parametric routes
    return route.routeConfig && route.routeConfig.path;
  }

}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The npm warning indicates that the file node_modules/.staging/typescript-8be04997/lib/zh-CN/diagnosticMessages.generated.json does not exist due to an ENOENT error

I am currently in the process of running npm install on a Linux machine where I do not have sudo access. Unfortunately, I have a machine-specific package.json and package-lock.json that cannot be changed. However, I encountered some errors during the insta ...

The functionality of multiple UI-Views is not meeting the intended outcomes

I'm facing an issue with integrating multiple UI-views in my AngularJS dashboard app. Despite following the directory structure, I am unable to get it working as expected. This is how my directories are organized: index.html app/ app.js dash ...

Executing a function within JSX to dismiss a modal in NextJS

I am currently utilizing the Tanstack React Query library to perform a POST request from a Modal that includes a form: const addDay = (day: TDay) => { const apiURL = process.env.NEXT_PUBLIC_SERVER_URL const queryURL = apiURL + router ...

Crafting a recursive Typescript Immutable.js Record through typing

I am currently working on representing a tree-like data structure using immutable js and typescript. At the moment, I am utilizing regular vanilla js objects to depict the nodes within the tree. Below is the type signature. type NodeType = { value: str ...

Ways to enlarge image size without compromising the image resolution?

My image is in PNG format or as a blob. It has dimensions of 800px by 600px. However, when I try to resize it using various canvas methods like the one mentioned in this Stack Overflow thread: Resize image, need a good library, it loses quality. I wou ...

The fieldset css in PrimeNG differs from the website's original design

On my website, the appearance of the fieldset can be seen here: https://i.stack.imgur.com/TTS8s.jpg. I did not make any CSS changes that altered the fieldset. I am utilizing primeNG v7 and Angular 7. <p-fieldset legend="Toggleable" [toggleable]="true" ...

Ways to deactivate a text area or mat-form-field

I need assistance with disabling a form field using Angular (4) + Angular Material and Reactive Forms. I have tried searching for syntax options like disabled="true", but haven't found the correct one yet. Can you please provide me with the right synt ...

Showing JSON response on an HTML page using AngularJS

I have limited experience with JavaScript and/or Angular, but I am required to utilize it for a research project. My task involves showcasing the JSON data returned by another component on a webpage. Here is how the process unfolds: When a user clicks on ...

A guide on utilizing portals in Next.js to move a child element beyond its direct parent container

Current Setup Wrapper export const ContainerComponent = () => { return (<ChildComponent/>); } Child Component export const ChildComponent = () => { return ReactDOM.createPortal( <aside> <div>{"I am a c ...

Troubleshooting fastify library errors related to ajv validation

Every time I try to build my TypeScript code, I encounter these errors: The following errors are showing up in my TypeScript code: 1. node_modules/@fastify/ajv-compiler/types/index.d.ts(1,10): error TS2305: Module 'ajv' has no exported member ...

Angular 4: Triggering Scroll Event when Select Dropdown Reaches End

I am attempting to trigger a Scroll Event within the component class when the end of the dropdown list is reached. I have a large list and initially only load the first 30 records in ngOnInit(). As the user scrolls down, I want to update the dropdown list ...

Speedier display of information in angular2

I have been exploring ways to optimize data rendering with Angular2 for increased performance. While using the Edge F12 profiler, I noticed that there is a significant amount of processing time, taking around 250-500ms (on a core i7u CPU) to render a list ...

Using Express and Node.js, fetch an image from one localhost and transfer it to another localhost

I am currently developing a NodeJs and Angular app The server is running on http://localhost:9000 While the app itself runs on http://localhost:4200 One of my endpoints looks like this http://localhost:9000/users, which I use to fetch all user data The ...

Implementing Expand/Collapse functionality for multiple TableRow components in Material-UI

Using a Material UI table and attempting to expand the `TableRow` inside a collapse table, but encountering an issue. Currently, all collapses are connected to one state for "open," causing all lists to open if one is clicked. What is the optimal approach ...

Discover the type of generic keyof in TypeScript

My types implementation is structured as follows: type GenericType<T, K extends keyof T = keyof T> = { name: K; params: T[K] } type Params = { a: 1; b: 2; } const test: GenericType<Params> = { name: "a", params: 2 } ...

Is the transcluded content visible to the class as a whole?

Angular2 makes it simple to create a component like this: @Component({ selector: 'some', properties: ['header'] }) @View({ template: ` <div> <h2>{{ getFormattedHeader() }}</h2> <p><conte ...

Dealing with ObjectUnsubscribedError in RxJS subscriptions with Angular

In my application, I have a service that manages several BehaviorSubject objects which are subscribed to by the main component. These subjects receive data from the backend and are utilized by the component through subscription. To prevent memory leaks, I ...

Issue with launching Angular 6 project

I attempted the solution from this link, but unfortunately, it did not work for me. I have cloned a project from GitLab and am attempting to run it. First, take a look at the output of my ng version command: https://i.sstatic.net/qxRzk.png. The project i ...

Please exclude any files with the name npm-debug.log.XXXXXX in the .gitignore file

Is there a way to exclude this file from the repository using .gitignore? https://i.stack.imgur.com/mK84P.png I am currently working on Gitlab. I have attempted the following: https://i.stack.imgur.com/2kN21.png Appreciate any help in advance ...

Loading only specific HTML list elements in segments

Within my Angular4 application, I am faced with a challenge involving a large list of li elements. The browser struggles to handle the thousands of li's being displayed when the user interacts with the ul element. This results in slow loading times an ...