Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to lazy load Angular 2 components in a TabView (PrimeNG)?

It is my app.component.ts:

import { Component } from '@angular/core';

@Component({
    templateUrl: 'app/app.component.html',
    selector: 'my-app'
})
export class AppComponent {

}

And this is my app.component.html:

<p-tabView>
    <p-tabPanel header="Home" leftIcon="fa-bar-chart-o">
        <home-app></home-app>
    </p-tabPanel>
    <p-tabPanel header="Hierarquia" leftIcon="fa-sitemap">
        <tree-app></tree-app>
    </p-tabPanel>
    <p-tabPanel header="Configurações" leftIcon="fa-cog">
        <config-app></config-app>
    </p-tabPanel>
</p-tabView>

My three components (home, tree and config) are been loaded at the same time when the tabView is loaded. However, I would like that a component was loaded just when its tab was selected. How to do that?

P.S.: if it helps, TabView has an onChange event.

like image 651
Marcel Avatar asked Dec 15 '16 19:12

Marcel


4 Answers

You can use SystemJsNgModuleLoader which is used in angular2 routing

Live Plunker

enter image description here

First you can write component which will load module:

@Component({
  selector: 'dynamic-container',
  template: `
    <template #container></template>
    <div *ngIf="!loaded" class="loader"></div>
  `,
  styles: [`
    .loader {
      position: relative;
      min-height: 100px;
    }

    .loader:after {
      content: 'Loading module. Please waiting...';
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
  `]
})
export class DynamicContainerComponent implements OnDestroy {
  @ViewChild('container', { read: ViewContainerRef }) vcRef: ViewContainerRef;
  loaded: boolean;

  constructor(private moduleLoader: SystemJsNgModuleLoader) { }

  compRef: ComponentRef<any>;

  @Input() modulePath: string;
  @Input() moduleName: string;

  _inited: boolean
  set inited(val: boolean) {
    if(val) {
      this.loadComponent();
    }
    this._inited = val;
  };

  get inited() {
    return this._inited;
  }

  loadComponent() {
    this.moduleLoader.load(`${this.modulePath}#${this.moduleName}`)
      .then((moduleFactory: NgModuleFactory<any>) => {
        const vcRef = this.vcRef;
        const ngModuleRef = moduleFactory.create(vcRef.parentInjector);
        const comp = ngModuleRef.injector.get(LazyLoadConfig).component;
        const compFactory = ngModuleRef.componentFactoryResolver.resolveComponentFactory(comp);
        this.compRef = vcRef.createComponent(compFactory, 0, ngModuleRef.injector);

        this.loaded = true;
      });
  }

  ngOnDestroy() {
    this.compRef.destroy();
  }
}

And then use it in your component:

@Component({
  selector: 'my-app',
  template: `
    <h2 class="plunker-title">How to lazy load Angular 2 components in a TabView (PrimeNG)?</h2>
    <p-tabView (onChange)="handleChange($event)">
    <p-tabPanel header="Home" leftIcon="fa-bar-chart-o">
        <home-app></home-app>
    </p-tabPanel>
    <p-tabPanel header="Hierarquia" leftIcon="fa-sitemap">
        <dynamic-container modulePath="./src/modules/tree/tree.module" moduleName="TreeModule"></dynamic-container>
    </p-tabPanel>
    <p-tabPanel header="Configurações" leftIcon="fa-cog">
        <dynamic-container modulePath="./src/modules/config/config.module" moduleName="ConfigModule"></dynamic-container>
    </p-tabPanel>
</p-tabView>
  `
})
export class AppComponent {
  @ViewChildren(DynamicContainerComponent) dynamicContainers: QueryList<DynamicContainerComponent>;

  handleChange(e) {
    let dynamicContainer = this.dynamicContainers.toArray()[e.index - 1];
    if (!dynamicContainer || dynamicContainer.inited) return;

    // prevent fast clicking and double loading
    dynamicContainer.inited = true;
  }
}

See also

  • How to manually lazy load a module?
like image 60
yurzui Avatar answered Sep 27 '22 22:09

yurzui


After much research, I could solve the problem using router. Now the application is really fast.

app.component.ts:

import { Component } from '@angular/core';
import { Router } from '@angular/router';

@Component({
    templateUrl: 'app/app.component.html',
    selector: 'my-app'
})
export class AppComponent {

    constructor(
        private router: Router) {
    }

    handleChange(e) {
        let index = e.index;
        let link;
        switch (index) {
            case 0:
                link = ['/home'];
                break;
            case 1:
                link = ['/hierarquia'];
                break;
            case 2:
                link = ['/config'];
                break;
        }
        this.router.navigate(link);
    }
}

app.component.html:

<div>
    <p-tabView (onChange)="handleChange($event)">
        <p-tabPanel header="Home" leftIcon="fa-bar-chart-o"></p-tabPanel>
        <p-tabPanel header="Hierarquia" leftIcon="fa-sitemap"></p-tabPanel>
        <p-tabPanel header="Configurações" leftIcon="fa-cog"></p-tabPanel>
    </p-tabView>
</div>

<router-outlet></router-outlet>

app.route.ts:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { AppHome } from './app.home';
import { AppTree } from './app.tree';
import { AppConfig } from './app.config';

const routes: Routes = [
    {
        path: 'home',
        component: AppHome
    },
    {
        path: 'hierarquia',
        component: AppTree
    },
    {
        path: 'config',
        component: AppConfig
    },
    {
        path: '',
        redirectTo: '/home',
        pathMatch: 'full'
    },
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule { }

export const routedComponents = [AppHome, AppTree, AppConfig];

app.module.ts:

import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { BrowserModule } from '@angular/platform-browser';
import 'rxjs/add/operator/toPromise';

import { AppConfig } from './app.config';
import { AppHeader } from './app.header';
import { AppHome } from './app.home';
import { AppTree } from './app.tree';
import { AppComponent } from './app.component';

import { AppRoutingModule, routedComponents } from './app.route';

import { InputTextModule, DataTableModule, ButtonModule, DialogModule, TabViewModule, ChartModule, TreeModule, GrowlModule, InputSwitchModule, BlockUIModule, InputMaskModule, DropdownModule } from 'primeng/primeng';

@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule, HttpModule, AppRoutingModule, InputTextModule, DataTableModule, ButtonModule, DialogModule, TabViewModule, ChartModule, TreeModule, GrowlModule, InputSwitchModule, BlockUIModule, InputMaskModule, DropdownModule],
    declarations: [AppHeader, AppComponent, AppHome, AppTree, AppConfig, routedComponents],
    bootstrap: [AppHeader, AppComponent]
})
export class AppModule { }

Thanks God! =]

like image 40
Marcel Avatar answered Sep 27 '22 21:09

Marcel


I've tried lazy attribute which doesn't work. Using router and ModuleLoader are great but a bit complex. If you want to keep your application not too much complex, the easiest solution is to use NgIf for rendering tabs.

<p-tabView (onChange)="handleChange($event)">
   <p-tabPanel header="Home" leftIcon="fa-bar-chart-o">
      <home-app *ngIf="activeTab === 0"></home-app>
   </p-tabPanel>
   <p-tabPanel header="Hierarquia" leftIcon="fa-sitemap">
      <tree-app *ngIf="activeTab === 1"></tree-app>
   </p-tabPanel>
   <p-tabPanel header="Configurações" leftIcon="fa-cog">
      <config-app *ngIf="activeTab === 2"></config-app>
   </p-tabPanel>
</p-tabView>

And define a flag to render the selected tab.

handleChange(e) {
   this.activeTab = e.index;
}
like image 45
mehdinf Avatar answered Sep 27 '22 21:09

mehdinf


Since this question is quite old, I don't know if this will be of any use, but I stumbled upon this problem too, and the answer was right in the PrimeNG documentation:

Lazy loading helps initial load performance by only initializing the active tab, inactive tabs are not initialized until they get selected. A lazy loaded tabpanel contents are cached by default so that upon reselection, they are not created again. You may use cache property on TabPanel to configure this behavior. A TabPanel is specified as lazy when there is a ngTemplate with pTemplate="content" in it.

from https://www.primefaces.org/primeng/#/tabview

Even though this is only in the V7 documentation, this behavior works just as well in the V5.2 that I'm working with.

I could verify it by checking the "network" tab in the Chrome DevTools, each tabPanel loads separately as expected. However, the cache property doesn't seem to exist, so it will always be cached.

So for the author, he could do:

<p-tabView>
    <p-tabPanel header="Home" leftIcon="fa-bar-chart-o">
        <ng-template pTemplate="content">
            <home-app></home-app>
        </ng-template>
    </p-tabPanel>
    <p-tabPanel header="Hierarquia" leftIcon="fa-sitemap">
        <ng-template pTemplate="content">
            <tree-app></tree-app>
        </ng-template>
    </p-tabPanel>
    <p-tabPanel header="Configurações" leftIcon="fa-cog">
        <ng-template pTemplate="content">
            <config-app></config-app>
        </ng-template>
    </p-tabPanel>
</p-tabView>
like image 22
Jerry Unkhaptay Avatar answered Sep 27 '22 22:09

Jerry Unkhaptay