文档章节

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

SubinY
 SubinY
发布于 2017/09/04 17:32
字数 1317
阅读 120
收藏 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
码字总数 66143
作品 0
佛山
程序员
私信 提问
基于Vue的多项目整合实践

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

代码君的自白
2017/10/15
0
0
vue2.0 vue-router总结

在项目中安装: npm install vue-router --save 2. 在项目中引入: // The Vue build version to load with the command // (runtime-only or standalone) has been set in webpack.base.con......

danielnasri
2018/11/29
0
0
如何用 React 做服务端渲染

Photo by Stage 7 Photography 原文链接:如何用 React 做服务端渲染 - 知乎专栏 服务端渲染的一些优缺点这里就不说了,相信大家都已经非常清楚地知道了,本文意在讲述如何将一个简单的浏览器...

大板栗
2018/12/18
0
0
vue-router前端路由的使用

路由是根据不同的url展示不同的内容,前端路由就是把这个工作由服务器转到前端来做。 Vue.js+Vue-router可以很简单的实现单页应用。 单页应用 单页应用(SPA)能够更新视图而不重新请求页面。...

Carol小朋友
2018/11/27
0
0
Vue单页及多页应用全局配置404页面实践

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

FrankCheung
2018/05/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

脚本配置java开发环境

@echo off&setlocal enabledelayedexpansion cls @echo "This script is used to registe envionment variables......" @echo. @echo. @echo. set var=%~dp0 set var=%var:~,-1% @echo "regi......

默克鱼
34分钟前
1
0
c++中友元函数理解与使用

在学习c++这一块,关于友元函数和友元类,感觉还是不好理解,但是井下心来,理解,需要把我一下几点。 首先讲友元函数。 (1)友元函数: 1)C++中引入友元函数,是为在该类中提供一个对外(除...

天王盖地虎626
今天
2
0
OSChina 周一乱弹 —— 白掌柜说了卖货不卖身

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @爱漫爱 :这是一场修行分享羽肿的单曲《Moony》 手机党少年们想听歌,请使劲儿戳(这里) @clouddyy :开不开心? 开心呀, 我又不爱睡懒觉…...

小小编辑
今天
917
11
大数据教程(11.7)hadoop2.9.1平台上仓库工具hive1.2.2搭建

上一篇文章介绍了hive2.3.4的搭建,然而这个版本已经不能稳定的支持mapreduce程序。本篇博主将分享hive1.2.2工具搭建全过程。先说明:本节就直接在上一节的hadoop环境中搭建了! 一、下载apa...

em_aaron
今天
5
0
开始看《JSP&Servlet学习笔记》

1:WEB应用简介。其中1.2.1对Web容器的工作流程写得不错 2:编写Servlet。搞清楚了Java的Web目录结构,以及Web.xml的一些配置作用。特别是讲了@WebServlet标签 3:请求与响应。更细致的讲了从...

max佩恩
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部