Как лениво загрузить компоненты 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>

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

PS: если это помогает, у TabView есть событие onChange.

6 ответов

Решение

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

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

Слава Богу! знак равно

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

Живой Плункер

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

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

Смотрите также

Я пробовал ленивый атрибут, который не работает. Использование router и 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;
}

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

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

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

Хотя это только в документации 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>
</p-tabView>

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

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

есть свойство [cache]=false для работы p-tabPanel. Пожалуйста, посмотрите фрагмент:

       <p-tabView class="nav-tabs-primary">
  <p-tabPanel [cache]="false" header="Tab1" *ngIf="condition1">
    <ng-template pTemplate="content">
      <child1></child1>
    </ng-template>
  </p-tabPanel>
  <p-tabPanel [cache]="false" header="Tab2" *ngIf="condition2">
    <ng-template pTemplate="content">
      <child2></child2>
    </ng-template>
  </p-tabPanel>
</p-tabView>

Подход с использованием *ngFor для дочернего компонента на основе activeTabIndex не работает, если есть условие для отображения/скрытия вкладок, поскольку индекс вкладок может варьироваться в зависимости от установленных вкладок.

Другие вопросы по тегам