Как lazy load Angular 2 компонента в TabView (PrimeNG)?

Это мой app.component.ts:

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

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

}

И это мой 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>

Мои три компонента (дом, дерево и конфиг) загружаются одновременно с загрузкой tabView. Однако я хотел бы, чтобы компонент был загружен только тогда, когда была выбрана его вкладка. Как это сделать?

P.S.: если это помогает, TabView имеет событие onChange.

Ответ 1

После долгих исследований я смог решить проблему, используя маршрутизатор. Теперь приложение работает очень быстро.

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

Слава Богу! =]

Ответ 2

Вы можете использовать SystemJsNgModuleLoader, который используется в маршрутизации angular2

Live Plunker

введите описание изображения здесь

Сначала вы можете написать компонент, который загрузит модуль:

@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();
  }
}

И затем используйте его в своем компоненте:

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

См. также

Ответ 3

Primeng tabview имеет "ленивый" атрибут, по умолчанию "false". Вы можете установить его следующим образом:

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

Ответ 4

Я пробовал ленивый атрибут, который не работает. Использование маршрутизатора и ModuleLoader отличное, но немного сложное. Если вы хотите, чтобы ваше приложение не было слишком сложным, самым простым решением является использование NgIf для визуализации вкладок.

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

И определите флаг для отображения выбранной вкладки.

handleChange(e) {
   this.activeTab = e.index;
}

Ответ 5

Поскольку этот вопрос довольно старый, я не знаю, будет ли он полезен, но я тоже наткнулся на эту проблему, и ответ был правильным в документации PrimeNG:

Ленивая загрузка помогает начальной производительности загрузки, только инициализируя активную вкладку, неактивные вкладки не инициализируются, пока они не выбраны. По умолчанию содержимое лениво загруженной вкладки кэшируется, поэтому при повторном выборе они не создаются снова. Вы можете использовать свойство cache на TabPanel для настройки этого поведения. TabPanel указывается как ленивый, когда есть ngTemplate с pTemplate = "content" в нем.

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

Хотя это только в документации V7, это поведение работает так же хорошо в V5.2, с которым я работаю.

Я мог проверить это, проверив вкладку "сеть" в Chrome DevTools, каждая вкладка загружается отдельно, как и ожидалось. Тем не менее, свойство cache не существует, поэтому оно всегда будет кэшироваться.

Так что для автора он мог сделать:

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