Lazy loading tabs

Goal: Load tabs dynamically

Only load data for a tab when it is selected.

Please replace all occurences of exampleDetailsTab with the actual name in the following code snippets

1. State & viewmodel

Add additional member for the tabs in your state and viewmodel:

Adapt in Files: <feature>-details.state.ts

...
    activeTab: 'ExampleDetailsTab' | ... | 'Other';
...

Adapt in Files: <feature>-search.viewmodel.ts

...
    exampleDetailsTab: exampleDetailsTabViewModel;
...

2. Actions

Create following actions to handle the events regarding autocomplete:

Adapt in File: <feature>-details.actions.ts

...
    events: {
        'other tab selected': emptyProps(),
        'example details tab selected': emptyProps(),
        'example details received': props<{
        exampleDetails: ExampleResponse;
        }>(),
        'example details loading failed': props<{ error: string | null }>(),
    },
...

3. HTML

Adapt in File: <feature>-details.component.html

    <ocx-portal-page *ngrxLet="viewModel$; let vm">
        <p-tabView
            [(activeIndex)]="activeTabIndex"
            (onChange)="handleTabChange($event)"
        >
            <p-tabPanel
                [header]="'FEATURE_DETAILS.TABS.EXAMPLE_DETAILS' | translate"
            >
                <div *ngIf="vm" >
                    <app-example-details-tab
                        [vm]="vm?.exampleDetails"
                    ></app-example-details-tab>
                </div>
            </p-tabPanel>
        </p-tabView>
    </ocx-portal-page>

4. Component

Adapt in File: <feature>-details.component.ts

    handleTabChange(event: { originalEvent: Event; index: number }) {
        switch (event.index) {
            case 0:
                this.store.dispatch(FeatureDetailsActions.exampleDetailsTabSelected());
                break;
            ...
            default:
                this.store.dispatch(FeatureDetailsActions.otherTabSelected());
        }
    }

5. Reducers

In the reducers file you need to define the functions:

Adapt in File: <feature>-search.reducers.ts

...
    on(
        <%= featureClassName %>DetailsActions.exampleDetailsTabSelected,
        (state: <%= featureClassName %>DetailsState): <%= featureClassName %>DetailsState => ({
            ...state,
            activeTab: 'ExampleDetailsTab',
        })
    ),
    on(
        <%= featureClassName %>DetailsActions.exampleDetailsReceived,
        (state: <%= featureClassName %>DetailsState, { exampleDetails }): <%= featureClassName %>DetailsState => ({
            ...state,
            exampleDetails: exampleDetails,
        })
    ),
    on(
        <%= featureClassName %>DetailsActions.exampleDetailsLoadingFailed,
        (state: <%= featureClassName %>DetailsState): <%= featureClassName %>DetailsState => ({
            ...state,
            exampleDetails: undefined,
        })
    ),
...

6. Selectors

Add the missing selectors:

Adapt in File: <feature>-details.selectors.ts

...
    export const select<%= featureClassName %>DetailsViewModel = createSelector(
        ...
        selectExampleDetailsTabViewModel,
        ...
      (
        ...
        exampleDetailsTab,
        ...
      ): <%= featureClassName %>DetailsViewModel => ({
        ...
        exampleDetailsTab,
        ...
      }),
    );
...

7. Effects

Adapt in File: <feature>-details.effects.ts

    exampleDetailsTab$ = createEffect(() => {
        return this.actions$.pipe(
            ofType(
                <%= featureClassName %>DetailsActions.exampleDetailsTabSelected,
            ),
            concatLatestFrom(() =>
                this.store.select(<%= featureClassName %>DetailsSelectors.selectActiveTab),
            ),
            filter(([, activeTab]) => activeTab === 'ExampleDetailsTab'),
            concatLatestFrom(() =>
                this.store.select(<%= featureClassName %>DetailsSelectors.selectSomePropertyContainingId),
            ),
            switchMap(([, id]) => {
                return this.<%= featureClassName %>DetailsService
                    .getExampleDetailsTabDataById(id)
                    .pipe(
                        map(({ exampleDetailsTabData }) => {
                        return <%= featureClassName %>DetailsActions.exampleDetailsReceived({
                            exampleDetails: exampleDetailsTabData,
                        });
                    }),
                    catchError((error) =>
                        of(
                            <%= featureClassName %>DetailsActions.exampleDetailsLoadingFailed({
                                error,
                            }),
                        ),
                    ),
                );
            }),
        );
    });
Don’t forget to add the translations to your de.json and en.json.