基于Material2的Tab实现

原始需求:

一个正常的Tab(有Tab的基本功能),Tab的内容基于Component。

具体功能描述:

  • 根据左侧Navigator,在页面Middle部分动态加载Tab页,Content部分是由路由决定的组件。
  • Tab页可以追加、切换和关闭。
  • Tab页在打开过多的情况下可以左右箭头移动。
  • Tab页的切换(包括关闭等一系列操作)不会引起组件的销毁。

具体实现:

问题一:

根据左侧的导航项,向右侧组件传值:

app-sidebar-nav.component.ts

import { Component, ElementRef, Input, OnInit, Renderer2 } from '@angular/core';

// Import navigation elements
import { navigation } from './../../_nav';
import { Router } from '@angular/router';
import { LocalStorage } from '../../providers/local.storage';
import { SubmitService } from '../../providers/submit.service';
import { FullLayoutComponent } from '../../containers/full-layout/full-layout.component';

@Component({
  selector: 'app-sidebar-nav',
  template: `
    <nav class="sidebar-nav">
      <ul class="nav">
        <ng-template ngFor let-navitem [ngForOf]="navObj">
          <li *ngIf="isDivider(navitem)" class="nav-divider"></li>
          <ng-template [ngIf]="isTitle(navitem)">
            <app-sidebar-nav-title [title]='navitem'></app-sidebar-nav-title>
          </ng-template>
          <ng-template [ngIf]="!isDivider(navitem)&&!isTitle(navitem)">
            <app-sidebar-nav-item [item]='navitem'></app-sidebar-nav-item>
          </ng-template>
        </ng-template>
      </ul>
    </nav>`
})
export class AppSidebarNavComponent implements OnInit {
  public navObj;
  private respData: string;
  private errorMessage: any;

  public isDivider(item) {
    return item.divider ? true : false
  }

  public isTitle(item) {
    return item.title ? true : false
  }

  constructor(private ls: LocalStorage) {
    // this.navObj = navigation;
  }
  ngOnInit() {
    const srcArray = navigation;
    const distArray = [];
    const privilegeCodeObj = this.ls.getObject('privilegeCode');
    for (let priviCodeLvlCount_1 = 0; priviCodeLvlCount_1 < srcArray.length; priviCodeLvlCount_1++) {
      if (srcArray[priviCodeLvlCount_1].privilegeCode === 'static') {
        distArray.push(srcArray[priviCodeLvlCount_1]);
      } else {
        if (privilegeCodeObj[srcArray[priviCodeLvlCount_1].privilegeCode]) {
          distArray.push(srcArray[priviCodeLvlCount_1]);
          const srcChildrenArray = srcArray[priviCodeLvlCount_1]['children'];
          const distChildrenArray = [];
          for (let priviCodeLvlCount_2 = 0; priviCodeLvlCount_2 < srcChildrenArray.length; priviCodeLvlCount_2++) {
            if (privilegeCodeObj[srcChildrenArray[priviCodeLvlCount_2].privilegeCode]) {
              distChildrenArray.push(srcChildrenArray[priviCodeLvlCount_2]);
            }
          }
          distArray[distArray.length - 1]['children'] = distChildrenArray;
        }
      }
    }
    this.navObj = distArray;
  }
}
@Component({
  selector: 'app-sidebar-nav-item',
  template: `
    <li *ngIf="!isDropdown(); else dropdown" [ngClass]="hasClass() ? 'nav-item ' + item.class : 'nav-item'">
      <app-sidebar-nav-link [link]='item'></app-sidebar-nav-link>
    </li>
    <ng-template #dropdown>
      <li [ngClass]="hasClass() ? 'nav-item nav-dropdown ' + item.class : 'nav-item nav-dropdown'"
          [class.open]="isActive()"
          routerLinkActive="open"
          appNavDropdown>
        <app-sidebar-nav-dropdown [link]='item'></app-sidebar-nav-dropdown>
      </li>
    </ng-template>
  `
})
export class AppSidebarNavItemComponent {
  @Input() item: any;

  public hasClass() {
    return this.item.class ? true : false
  }

  public isDropdown() {
    return this.item.children ? true : false
  }

  public thisUrl() {
    return this.item.url
  }

  public isActive() {
    return this.router.isActive(this.thisUrl(), false)
  }

  constructor(private router: Router) { }

}

@Component({
  selector: 'app-sidebar-nav-link',
  template: `
    <a *ngIf="!isExternalLink(); else external"
      [ngClass]="hasVariant() ? 'nav-link nav-link-' + link.variant : 'nav-link'"
      routerLinkActive="active"
      (click)="linkClick({path: link.url, label: link.name, code: link.privilegeCode})">
<!--       [routerLink]="[link.url]">-->
      <i *ngIf="isIcon()" class="{{ link.icon }}"></i>
      {{ link.name }}
      <span *ngIf="isBadge()" [ngClass]="'badge badge-' + link.badge.variant">{{ link.badge.text }}</span>
    </a>
    <ng-template #external>
      <a [ngClass]="hasVariant() ? 'nav-link nav-link-' + link.variant : 'nav-link'" href="{{link.url}}">
        <i *ngIf="isIcon()" class="{{ link.icon }}"></i>
        {{ link.name }}
        <span *ngIf="isBadge()" [ngClass]="'badge badge-' + link.badge.variant">{{ link.badge.text }}</span>
      </a>
    </ng-template>
  `
})
export class AppSidebarNavLinkComponent {
  @Input() link: any;

  public hasVariant() {
    return this.link.variant ? true : false
  }

  public isBadge() {
    return this.link.badge ? true : false
  }

  public isExternalLink() {
    return this.link.url.substring(0, 4) === 'http';
  }

  public isIcon() {
    return this.link.icon ? true : false
  }

  public linkClick(linkInfo) {
    this.fullLayoutComponent.updateArrays(linkInfo);
  }

  constructor(private fullLayoutComponent: FullLayoutComponent) { }
}

@Component({
  selector: 'app-sidebar-nav-dropdown',
  template: `
    <a class="nav-link nav-dropdown-toggle" appNavDropdownToggle href="#">
      <i *ngIf="isIcon()" class="{{ link.icon }}"></i>
      {{ link.name }}
      <span *ngIf="isBadge()" [ngClass]="'badge badge-' + link.badge.variant">{{ link.badge.text }}</span>
    </a>
    <ul class="nav-dropdown-items">
      <ng-template ngFor let-child [ngForOf]="link.children">
        <app-sidebar-nav-item [item]='child'></app-sidebar-nav-item>
      </ng-template>
    </ul>
  `
})
export class AppSidebarNavDropdownComponent {
  @Input() link: any;

  public isBadge() {
    return this.link.badge ? true : false
  }

  public isIcon() {
    return this.link.icon ? true : false
  }

  constructor() { }
}

@Component({
  selector: 'app-sidebar-nav-title',
  template: ''
})
export class AppSidebarNavTitleComponent implements OnInit {
  @Input() title: any;

  constructor(private el: ElementRef, private renderer: Renderer2) { }

  ngOnInit() {
    const nativeElement: HTMLElement = this.el.nativeElement;
    const li = this.renderer.createElement('li');
    const name = this.renderer.createText(this.title.name);

    this.renderer.addClass(li, 'nav-title');

    if (this.title.class) {
      const classes = this.title.class;
      this.renderer.addClass(li, classes);
    }

    if (this.title.wrapper) {
      const wrapper = this.renderer.createElement(this.title.wrapper.element);

      this.renderer.appendChild(wrapper, name);
      this.renderer.appendChild(li, wrapper);
    } else {
      this.renderer.appendChild(li, name);
    }
    this.renderer.appendChild(nativeElement, li)
  }
}

export const APP_SIDEBAR_NAV = [
  AppSidebarNavComponent,
  AppSidebarNavDropdownComponent,
  AppSidebarNavItemComponent,
  AppSidebarNavLinkComponent,
  AppSidebarNavTitleComponent
];

根据左侧页面传值,打开Tab页,并加载组件(HTML部分):

full-layout.component.html

<app-header></app-header>
<div class="app-body">
  <app-sidebar></app-sidebar>
  <!-- Main content -->
  <main class="main">
    <!-- Breadcrumb -->
    <!--<ol class="breadcrumb">
          <app-breadcrumbs></app-breadcrumbs>
        </ol>-->
    <!--   routerLinkActive #rla="routerLinkActive"
        [active]="rla.isActive">-->
    <div>
      <nav mat-tab-nav-bar>
        <a mat-tab-link *ngFor="let link of navLinks" (click)="activeTabLabel(link.code)" [routerLink]="link.path" [active]="link.isActive">
          {{link.label}}
          <i class="fa fa-close fa-lg" style="margin: 2px 0px 0px 5px" (click)="closeTab(link.code)"></i>
        </a>
      </nav>
      <router-outlet></router-outlet>
    </div>
    <!-- /.conainer-fluid-------class="container-fluid"-->
  </main>
  <app-aside></app-aside>
</div>
<!--<app-footer></app-footer>-->

根据左侧页面传值,打开Tab页,并加载组件(TS部分):

full-layout.component.tsl

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

@Component({
  selector: 'app-dashboard',
  templateUrl: './full-layout.component.html'
})
export class FullLayoutComponent {
  public navLinks;
  constructor(private router: Router) {
    this.navLinks = [];
    console.log('Into Full!!!!')
  }

  public activeTabLabel(code) {
    for (let count = 0; count < this.navLinks.length; count++) {
      if (this.navLinks[count].code === code) {
        this.navLinks[count].isActive = true;
      } else {
        this.navLinks[count].isActive = false;
      }
    }
  }

  public updateArrays(linkInfo) {
    let updateFlag = true;
    for (let count = 0; count < this.navLinks.length; count++) {
      if (linkInfo.code === this.navLinks[count].code) {
        this.navLinks[count].isActive = true;
        this.router.navigate([linkInfo.path]);
        updateFlag = false;
      } else {
        this.navLinks[count].isActive = false;
      }
    }
    if (updateFlag) {
      this.navLinks.push(linkInfo);
      this.navLinks[this.navLinks.length - 1].isActive = true;
      this.router.navigate([linkInfo.path]);
    }
  }

  closeTab(code) {
    for (let count = 0; count < this.navLinks.length; count++) {
      if (this.navLinks[count].code === code) {
        this.navLinks.splice(count, 1);
        this.navLinks[count - 1].isActive = true;
        this.router.navigate([this.navLinks[count - 1].path]);
        break;
      }
    }
  }
}

问题二:

追加与切换没问题,至于关闭功能,可能得自己加一个关闭的符号“✖”。待实现。

问题三:

Tab页的实现,如果基于mat-tab-group,这个feature是具备的,但是毕竟我们是基于nav mat-tab-nav-bar,所以官方Githubissue给出的结论是:还没实现。

问题四:

Tab页切换而不销毁组件,还在查。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,231评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,116评论 4 61
  • 有没有那么一个男生,他不是你的男朋友,你也不喜欢他,甚至或许有时候对他有些不耐烦,但是你的回忆里常常是他的影子,那...
    whose崖香阅读 587评论 0 0
  • 相对跳转有如下方式,需要了解(以下的例子中,分别以你的例子和带.html尾缀进行演示):1. 本目录的使用(与本文...
    sakura_L阅读 322评论 0 0
  • 早餐吃的枸杞百合粥,还有茶叶蛋跟几碟小菜,清爽可口。 饭后灵犀拉着帅哥回到她的房间,神秘兮兮的说到:我要送你一样礼...
    灵犀心理咨询室阅读 391评论 0 0