文档章节

动态组件做单页应用(非路由跳转)

SubinY
 SubinY
发布于 2017/09/04 17:32
字数 1317
阅读 111
收藏 0

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

© 著作权归作者所有

共有 人打赏支持
SubinY
粉丝 8
博文 87
码字总数 65290
作品 0
佛山
程序员
基于Vue的多项目整合实践

在笔者所在的前端开发团队中,采用前后端分离方案是在整个业务线稳定后进行的。业务前期主要采用后端套模板的方式,现阶段是采用基于的单页开发模式。 这会出现一种情形,产品在不断迭代过程...

代码君的自白
2017/10/15
0
0
Vue单页及多页应用全局配置404页面实践

这篇文章也发在我的博客,欢迎围观😄 写在前面 前后端分离后,控制路由跳转的责任转移到了前端,后端只负责给前端返回一个html文档以及提供各种接口。下面我们用作例子的两个项目,均采用v...

FrankCheung
05/20
0
0
Vue2.0笔记——vue-router路由

简介 使用Vue.js开发SPA(Single Page Application)单页面应用。 vue-router可以通过html5的history API或者hash实现单页应用,即不刷新跳转,切换地址,只是页面上的组件的切换; vue-rout...

逝岁月
04/24
0
0
vue-router用法记录

###Vue-Router简介 Vue-Router是Vue.js官方维护的路由插件,同时也是官方推荐的路由插件。它与Vue.js内核深度结合,让开发单页应用更加容易简便。 ####基本配置 HTML 当 对应的路由匹配成功,...

iceuncle
2017/11/28
0
0
vue-router 路由和组件

vue-router 是 vue 中需要学习的一个重要部分, 下面我来与大家分享下自己的经验 以 饿了么APP 为例 底部是我用 mint-ui 做成的公共组件, 取名为 "BottomTab" 首先我们来配置下公共组件 , 代码...

小贤笔记
07/14
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Win10内部更新:警告用户别用chrome和Firefox

简评:别和 Chrome 和 Firefox 约行不,我 Edge 明明更美、屁股更翘、更性感。。。 微软正在测试 Windows 10 的一个更新:警告用户不要安装 Chrome 和 Firefox。是测试人员发现的这个新警告,...

极光推送
36分钟前
2
0
Java并发编程高阶技术 高性能并发框架源码解析与实战

全网唯一深度解析并发编程框架disruptor底层源码课程,助你成为并发编程高手,拿下高薪 网盘地址下载

qq__2304636824
今天
1
0
day92-20180918-英语流利阅读-待学习

健身最大的敌人不是懒惰,而是逞强 Daniel 2018-09-19 1.今日导读 还记得 2008 年北京奥运会运动员刘翔的退赛风波吗?那天几乎所有中国人都将视线聚焦在了鸟巢体育馆 110 米栏的项目上,迫不...

飞鱼说编程
今天
8
0
70.shell的函数 数组 告警系统需求分析

20.16/20.17 shell中的函数 20.18 shell中的数组 20.19 告警系统需求分析 20.16/20.17 shell中的函数: ~1. 函数就是把一段代码整理到了一个小单元中,并给这个小单元起一个名字,当用到这段...

王鑫linux
今天
3
0
分布式框架spring-session实现session一致性使用问题

前言:项目中使用到spring-session来缓存用户信息,保证服务之间session一致性,但是获取session信息为什么不能再服务层获取? 一、spring-session实现session一致性方式 用户每一次请求都会...

WALK_MAN
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部