动态组件做单页应用(非路由跳转)
博客专区 > SubinY 的博客 > 博客详情
动态组件做单页应用(非路由跳转)
SubinY 发表于5个月前
动态组件做单页应用(非路由跳转)
  • 发表于 5个月前
  • 阅读 82
  • 收藏 0
  • 点赞 0
  • 评论 0

【腾讯云】如何购买服务器最划算?>>>   

摘要: ComponentFactoryResolver,ComponentFactory

angular单页面应用另外一种做法,通过动态生成组件方法并配合UI库primeNG的tabView组件实现无路由单页面跳转

Dynamic Component Loader(动态组件加载器)

组件的模板不会永远是固定的。应用可能会需要在运行期间加载一些新的组件。

这个案例为你展示如何使用ComponentFactoryResolver来动态添加组件。

下面详细叙述这个案例是如何实现标题所说的组件加载实现非路由跳转(关键地方标红,附github/SubinY

管理模块module

module.module.ts

...
@NgModule({
    imports: [
        ...
    ],
    declarations: [
        ...
        HeaderTopComponent,
        NavigationMenuComponent,
        ...ENTRY_COMPONENTS
    ],
    entryComponents: [
        ...ENTRY_COMPONENTS  //在客户端编译的组件均写在这,即传统所说懒加载页面
    ],
    providers: [
        TabViewResponseService
    ]
})
export class ModulesModule {
}

module.html

<div id="main-header">
    <header-top [widthSpread]="widthSpread" (widthChange)="widthChange($event)" (gotoPerson)="navPerson($event)"
                (headerHome)="headerHome($event)" (selectSubMenu)="changeSubMenu($event)"
                (personalCenter)="menuClickHandler($event)" (gotoYujing)="menuClickHandler($event)"></header-top>
    <app-navigation-menu [widthSpread]="widthSpread" [menuData]="navigateInfo"
                         (menuClickHandler)="menuClickHandler($event)"></app-navigation-menu>
</div>
<!--路由页面-->
<!--<router-outlet></router-outlet>-->
<div id="main-con" [ngStyle]="setContentStyle()">
    <div class="tab-view">
        <ui-tabview #tabview [tabs]="tabs" [activeIndex]="tabActiveIndex"
                    (tabClose)="handlerTabClose($event)" (tabChange)="handleTabChange($event)"></ui-tabview>
    </div>
</div>

以往是通过router-outlet输出页面,现在没路由控制就变成上图所示封装一层ui-tabview组件装在primeNG的组件代替路由输出

module.component.ts

...

@Component({
    ...
})
export class ModulesComponent implements OnInit, AfterViewInit {
    ...

    constructor(
        public tabViewService: TabViewService,
        public tabViewResponseService: TabViewResponseService) {
    }

    ngOnInit(): void {
        this.tabsComponent = this.tabViewService.getTabs();
        this.tabs.push(this.tabsComponent[0]);
        this.navigateInfo = navigateInfo;
    }

    /**
     * 菜单点击处理事件
     * @param $event
     */
    menuClickHandler($event) {
        let isOpen = false;
        this.tabs.forEach((tab, index) => {
            if (tab.data.name === $event.name) {
                this.tabActiveIndex = index;
                isOpen = true;
            }
        });
        if (!isOpen) {
            this.tabsComponent.forEach((tab, index) => {
                if (tab.data.name === $event.name) {
                    let selectedTab = this.tabview.findSelectedTab();
                    if (selectedTab) {
                        selectedTab.selected = false;
                    }
                    this.tabs.push(tab);
                    setTimeout(() => {
                        this.tabActiveIndex = this.tabs.length - 1;
                    }, 100)
                }
            });
            setTimeout(() => {
                this.tabViewResponseService.openTabNav(this.uiTabView.el.nativeElement);
            })
        }
    }

    /**
     * tab关闭触发事件
     * @param $event
     */
    handlerTabClose($event) {
        let index = $event.index;
        this.tabs.splice(index, 1);

        this.tabViewResponseService.closeTabNav(this.uiTabView.el.nativeElement);
    }

    handleTabChange($event) {

    }

}

menuClickHandler、handlerTabClose这两个方法就是控制组件数组的添加和删除具体实现看primeNG的tabview组件。tabViewResponseService是用来动态计算页面最大宽度来控制tab的宽度,并且样式是通过scss的一些变量去控制的,有兴趣的可以去github/SubinY去看看。

EntryComponents.ts

/*订单管理*/
import { OrderManageComponent } from "./order-manage/order-manage.component";

/*库存管理*/
import { WarehouseEntryManageComponent } from './warehouse-manage/page/warehouse-entry-manage/warehouse-entry-manage.component';
import { WarehouseLeaveManageComponent } from './warehouse-manage/page/warehouse-leave-manage/warehouse-leave-manage.component';
import { WarehouseCheckComponent } from './warehouse-manage/page/warehouse-check/warehouse-check.component';
import { WarehouseInfoComponent } from './warehouse-manage/page/warehouse-info/warehouse-info.component';
import { WarehouseAbnormalComponent } from './warehouse-manage/page/warehouse-abnormal/warehouse-abnormal.component';
import { WarehouseAreaAdjustmentComponent } from "./warehouse-manage/page/warehouse-area-adjustment/warehouse-area-adjustment.component";

export const ENTRY_COMPONENTS = [
    // 第一个页面必须写在第一位,其他组件随便写位置
    OrderManageComponent,
    WarehouseEntryManageComponent,
    WarehouseLeaveManageComponent,
    WarehouseCheckComponent,
    WarehouseInfoComponent,
    WarehouseAbnormalComponent,
    WarehouseAreaAdjustmentComponent,
]

由于初始化modules模块时,必须有一个页面在登录后显示,所以要显示的页面组件必须放在第一位,这是由modules.component.ts生命周期设定的取数组第一个。

 

TabView(动态组件的核心)

tabview.html

<!--ui-tabview-->
<div class="tab-view">
    <p-tabView #tabRef  [activeIndex]="activeIndex" (onChange)="handleChange($event)" (onClose)="handleClose($event)">
        <p-tabPanel *ngFor="let tab of tabs;let i=index;" header="{{tab.data.name}}" selected="tab.data.selected"  [closable]="true" (click)="tabPanelClick(i)">
            <ui-tab-panel [tabItem]="tab"></ui-tab-panel>
        </p-tabPanel>
    </p-tabView>
    <div id="contextMenu">
        <p-menu [model]="items"></p-menu>
    </div>
</div>

这是primeNG的tabview结构,其中ui-tab-panel就是我们要显示的页面,tabs是外界传进来的页面组件数组。

tab-item.ts

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

export class TabItem {
    constructor(public component: Type<any>, public data: any) {}
}

定义所需组件类型Type和参数data,在后面的tabview.service.ts会用到

tabview.service.ts

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

import { TabItem } from "./ui/tabview/tab-item";
import { ENTRY_COMPONENTS } from "../../modules/EntryComponents";

@Injectable()
export class TabViewService {

    constructor() {

    }

    getTabs() {
        let tabs = [];
        for (let c of ENTRY_COMPONENTS) {
            let item = new TabItem(c, {name: c['componentName'] || '无名称'});
            tabs.push(item);
        }
        return tabs;
    }
}

tab-panel.ts

import {
    Component,
    OnInit,
    Input,
    AfterViewInit,
    ViewChild,
    ComponentFactoryResolver,
    Type,
    ViewContainerRef
} from "@angular/core";
import {TabConfig} from "./TabConfig";


@Component({
    selector: "ui-tab-panel",
    template: "<div #dynamicContainer></div>",
})
export class UITabPanel implements OnInit,AfterViewInit {

    @Input()
    tabItem: TabConfig;

    @ViewChild( "dynamicContainer", { read: ViewContainerRef } ) dynamicContentContainer: ViewContainerRef;

    currentTabIndex: number = 1;

    constructor(private componentFactoryResolver: ComponentFactoryResolver) {
    }

    ngOnInit(): void {
        this.loadComponent();
    }

    ngAfterViewInit(): void {
    }


    loadComponent() {
        // this.currentTabIndex = (this.currentTabIndex + 1) % this.tabs.length;

        let componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.tabItem.component);

        // let viewContainerRef = this.tabHost.viewContainerRef;
        this.dynamicContentContainer.clear();

        let componentRef = this.dynamicContentContainer.createComponent(componentFactory);
        (<TabConfig>componentRef.instance).data = this.tabItem.data;
    }


}

Angular 4.x 动态创建组件

这里解析非常详细,我就不在这再啰嗦了。这次blog过多代码,很少解释,希望大家看懂了动态创建组件这篇文章再回头看这篇案例,应该会让你更加清晰,动态创建组件的用法。

 

1、tabview组件包括tabview.html(装载primeng的p-tabview组件)tabview.ts(封装p-tabview的逻辑代码)tab-panel.ts(包裹有动态加载逻辑的组件)TabConfig.ts(外来组件的接口核心component,data)tab-item.ts(类似抽象类定义需要动态加载的组件,实质是一个对象)tab.directive.ts(需要装载动态加载组件的指令,指向装载的模板,可以用模板变量配合viewChild就无需指令)tabview.service.ts(获取EntryComponents中的懒加载组件,用TabItem定义)

在菜单栏点击菜单,通过TabItem的name去判断是否已经打开指定的tab,有则高亮tab,没有则为局部变量tabs(数组,装载已经打开的tab)放进一个新的组件。当遇到相同组件需要重复打开的时候,也是通过name去控制,当然这里的name必须要动态改变,不能重复。为何能重复打开一个组件,原因是每个组件都动态加载在一个ng-template里面,都是独立的组件,可以说把外面的组件当成内容加载进去每一个不同的ng-template上面而已

 

细节: tab页切换的同时顶部标题栏也会跟着你改变

核心代码

import { Title } from "@angular/platform-browser";
this.title.setTitle('IPS-'+tab.data.name);

 

https://segmentfault.com/a/1190000010086185

Angular4 动态加载组件杂谈

官网动态组件加载器

动态创建模块,动态添加组件,拖拽组件生成UI例子

共有 人打赏支持
粉丝 7
博文 84
码字总数 61265
×
SubinY
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: