Search code examples
javascriptangulartypescriptlazy-loadingangular-ng-if

How to build lazy-loading sidebar in Angular?


I have built a component in my Angular App that handles client information; this is my ClientDetailsComponent, inside this component there are other subcomponents that each fetch their own data when a user enters the main ClientDetailsComponent; this of course causes the main component to load a lot of stuff that users might not even need at the moment so I was trying to look for a way to lazy-load this sections.

Now my component has a sidebar that serves as a navigation menu to only display specific sections when clicked, right now I've built a very primitive navigation menu, relying on classes to hide/show each section.

ClientDetailsCompnent

<div class="wrapper">
    <div class="sidebar-nav">
        <nav>
            <ul class="sections-list" btnRadioGroup [formControl]="navControl">
                <li btnRadio="0" tabindex="0" role="button">Basic Information</li>
                <li btnRadio="1" tabindex="0" role="button" *ngIf="!isNew">PPC Accounts</li>
                <li btnRadio="2" tabindex="0" role="button" *ngIf="!isNew">Campaign Groups</li>
                <li btnRadio="4" tabindex="0" role="button" *ngIf="!isNew">Optimizations</li>
                <li btnRadio="5" tabindex="0" role="button" *ngIf="!isNew">Branding</li>
                <li btnRadio="3" tabindex="0" role="button" *ngIf="!isNew">Sharing</li>
            </ul>
        </nav>
    </div>
    <div class="content">
        <div class="wsm-container">

            <!-- Basic Information -->
            <section id="basic-info" class="wsm-card mx-auto d-none" [ngClass]="navControl.value === '0' ? 'd-flex' : 'd-none'">
            </section>

            <!-- PPC Accounts -->
            <section *ngIf="!isNew" id="ppc-accounts" class="wsm-card mt-4 d-none" [ngClass]="navControl.value === '1' ? 'd-flex' : 'd-none'">
            </section>

            <!-- Campaign Groups -->
            <section *ngIf="!isNew && navControl.value === '2'" class="wsm-card mt-4 d-none" [ngClass]="navControl.value === '2' ? 'd-flex' : 'd-none'">
                <app-campaign-groups [clientID]="param"></app-campaign-groups>
            </section>

            <!-- Optimizer History -->
            <section *ngIf="!isNew && navControl.value === '4'" id="optHistory" class="wsm-card mt-4 d-none" [ngClass]="navControl.value === '4' ? 'd-flex' : 'd-none'">
                <app-optimization-history></app-optimization-history>
            </section>

            <!-- Branding -->
            <section id="brnading" class="wsm-card mx-auto d-none" [ngClass]="navControl.value === '5' ? 'd-flex' : 'd-none'">
            </section>
        </div>
    </div>
</div>

Regarding the navigation, everything works pretty much as I'd expect, only one section displayed at a time, depending on which item I clicked on the sidebar; however, just doing that doesn't prevent all request to be made at once when I access the component.

So one workaround I found while trying various options, was using the navControl.value === '' condition on the *ngIf directive of the section, so instead of only changing classes with the [ngClass] directive, I also prevent the section to be added to the DOM and in fact I see that the request for each subcomponent are only made once I click on the needed tab.

The issue now is that the requests are made eveytime I click on a different tab, since the component is added/removed everytime I move between sections.

Is there a way to keep the subcomponent loaded once I accessed it so it doesn't need to re-render it and fetch data again?


Solution

  • Create service with ReplaySubject:

    @Injectable({
      providedIn: "root"
    })
    export class DataService {
      private _replay: ReplaySubject<any>;
      get replay() {
        if (!this._replay) {
          this._replay = new ReplaySubject();
          this.fakeFetch().then(data => this.replay.next(data));
        }
        return this._replay;
      }
    
      async fakeFetch() {
        console.log("fakeFetch");
        await new Promise(c => setTimeout(() => c(), 10));
        return { name: "world" };
      }
    }
    

    And in your component subscribe to replay subject :

    export class HelloComponent {
      name: string;
      constructor(private api: DataService) {
        console.log('render HelloComponent')
        this.api.replay.subscribe(data => {
          this.name = data.name;
        });
      }
    }
    

    stackblitz