daikiojm’s diary

ブログを書くときがやってきたどん!

Angular MaterialでRouterと連携するTabを実装

Angular Materialで次のようなTabsを使ったUIを実装する方法です。
今回実現したのは👇こんな感じの動きです。

f:id:daikiojm:20170910191112g:plain

動きを見て分かるように、URLの変化に合わせてアクティブなtabと内容が変更されます。

以下のStackOverflowにあるサンプルコードを参考に、routerとmd-tab-nav-barの連携を行いましたが、これだけでは、URLが直接変更された場合に対応できていなかったため、別途修正を加えました。

stackoverflow.com

環境

$ ng --version
@angular/cli: 1.3.0
node: 8.1.3
os: darwin x64
"@angular/material": "^2.0.0-beta.10",
"@angular/router": "^4.2.4",

構成

今回作成したプロジェクトの構成は👇

./app
├── ./app/app-routing.module.ts
├── ./app/app.component.css
├── ./app/app.component.html
├── ./app/app.component.ts
├── ./app/app.module.ts
└── ./app/container
    ├── ./app/container/newest
    │   ├── ./app/container/newest/newest.component.css
    │   ├── ./app/container/newest/newest.component.html
    │   └── ./app/container/newest/newest.component.ts
    ├── ./app/container/random
    │   ├── ./app/container/random/random.component.css
    │   ├── ./app/container/random/random.component.html
    │   └── ./app/container/random/random.component.ts
    └── ./app/container/search
        ├── ./app/container/search/search.component.css
        ├── ./app/container/search/search.component.html
        └── ./app/container/search/search.component.ts

app.componentに<router-outlet>を配置して、Tabの内容はそれぞれsearch、newest、randomを切り替えます。

サンプル

まずは、routingの設定 (app-routing.module.ts)

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { SearchComponent } from './container/search/search.component';
import { NewestComponent } from './container/newest/newest.component';
import { RandomComponent } from './container/random/random.component';

const routes: Routes = [
  { path: '', redirectTo: 'search', pathMatch: 'full' },
  {
    path: 'search',
    component: SearchComponent
  },
  {
    path: 'newest',
    component: NewestComponent
  },
  {
    path: 'random',
    component: RandomComponent
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

メインのcomponent (app.component.html, app.component.ts)

<app-header></app-header>
<div class="main-area-container">
<nav md-tab-nav-bar>
  <a md-tab-link
      *ngFor="let routeLink of routeLinks; let i = index"
        [routerLink]="routeLink.link"
        [active]="activeLinkIndex === i"
        (click)="activeLinkIndex = i">
        {{routeLink.label}}
    </a>
</nav>
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
import { Component } from '@angular/core';
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
  private routeLinks: any[];
  private activeLinkIndex = 0;
  private currentRoute = '';

  constructor(
    private router: Router,
    private activatedRoute: ActivatedRoute
  ) {
    this.routeLinks = [
      { label: 'Search', link: 'search' },
      { label: 'Newest', link: 'newest' },
      { label: 'Random', link: 'random' }
    ];
    router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        this.currentRoute = event.url.slice(1);
        console.log(this.currentRoute);
        this.routeLinks.forEach((elm, index) => {
          if (elm.link === this.currentRoute) {
            this.activeLinkIndex = index;
          }
        });
      }
    });
  }
}

ActivatedRouteを使って、コンストラクタ内で現在のパスをsubscribeしています。 routingが変更されるたびに、activeLinkIndexも更新されviewに反映されるようになっています。

その他のrouterで読み込んでいるsearch、newest、randomに関しては省略します。

参考

Angular Material

angular - Angular2 material design mdtabs with router - Stack Overflow

typescript - Angular 2 Get current route - Stack Overflow