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

ぃ、小莉子 提交于 2019-11-30 13:29:51

问题


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.


回答1:


You can use SystemJsNgModuleLoader which is used in angular2 routing

Live Plunker

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?



回答2:


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! =]




回答3:


Primeng tabview has an "lazy" attribute which is default to "false". You can set it as follows

<p-tabView [lazy]="true">



回答4:


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;
}



回答5:


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>


来源:https://stackoverflow.com/questions/41171593/how-to-lazy-load-angular-2-components-in-a-tabview-primeng

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!