LoginSignup
125
136

More than 3 years have passed since last update.

Angularのルーティング設定(基礎編)

Last updated at Posted at 2017-06-22

この記事はAngular+Firebaseでチャットアプリを作るのエントリーです。
前記事:AngularのNgModuleを使って、アプリの構成を管理する
次記事:AngularのRxJSを使ってデータの受け渡しをする

この記事で行うこと

前回の記事で中規模アプリのモジュール構成を扱いました。
本稿ではモジュールをベースにしたAngularのルーティングを扱っていきます。

SPA(シングルページアプリケーション)のルーティング

IT関係者が「ルーティング」と聞くとネットワークのルーティングを思い浮かべてしまうかもしれませんが、ここでいうルーティングとは全くの別物です。SPAの文脈で使われる「ルーティング」とは、表示されているDOMをJavaScriptで書き換え、ページ遷移を擬似的に再現することを指します。

従来のHTMLベースの静的サイトや、Railsなどサーバーサイドで出力される動的サイトの場合、ルーティングはhttpリクエストに紐づく純粋な「ページ遷移」でした。
しかし、SPAの場合、ページ遷移にhttpリクエストが必ずしも必要ではありません。コンテンツ部分などの対象となるDOMだけを書き換え、「見た目上」のページ遷移を再現します。DOMの描画はかなり負荷の高い処理であり、その処理を大幅に削減することでSPAは高速な画面遷移を可能にしています。

ただし、この「必要なDOMだけを書き換える」という処理だけでルーティングが終わってしまうと、「URLの変更が行われない」というサービスによっては致命的な問題を引き起こします。URLの変更が行われないということは、URLをキーとして動作する解析ツールや、検索ボットなどが正常に動作しなくなるということです。
サイトの表現がリッチになっても、サイトの訪問者が減るようではSPAを採用する意味がなくなります。

最近のJavaScriptフレームワークでは、「URL変更されない」問題に対してそれぞれ独自のルーティングライブラリを使うことで対応するようになっています。Angularもその例に漏れず、パッケージの一つとして@angular/routerを持っていますので、その実装方法を紹介していきます。


(2018/1追記)記述を現時点で最新のものに差し替えました。
(2018/9追記)記述を現時点で最新のものに差し替えました。
(2020/6追記)記述を現時点で最新のものに差し替えました。


実装内容

ルーティングの基本設定

Angularのルーティング設定を行うにあたり、まずは今回のアプリ制作に必要なURLとそれに対応する画面を確認します。

URL 画面名
/ チャット画面
/account/login ログイン画面
/account/sign-up サインアップ画面
/(それ以外) 404画面

/(ルート)にアクセスがあった場合はチャット画面に遷移し、アカウント関連のページにアクセスがあった場合はその対応する画面、それ以外の場合は404(ページが見つかりません)画面を表示するようにします。

ルートパスへのルーティング

Angularのルーティングの設定はAppRoutingModuleで行います。
URLとコンポーネントを紐付けるルートを追記します。

src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ChatComponent } from './chat/chat.component'; // 追加

const routes: Routes = [ // 更新
  { path: '', component: ChatComponent },
];

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

次にルートコンポーネントにあったChatComponentのカスタムタグを、ルーティング指定したコンポーネントを表示させる<router-outlet></router-outlet>に変更します。

src/app/app.component.ts
@Component({
  selector: 'app-root',
  template: ` 
    <app-header></app-header>
    <router-outlet></router-outlet> <!--変更-->
  `,
})

これでルーティングの最初の設定は終了です。
ng serveでサーバーを起動し、これまでと表示が変わっていないことを確認してください。

Routesrouter-outlet

上記では、モジュールにRoutesというパスとコンポーネントの組み合わせを示した配列を追加しました。この配列をRouterModuleに追加することで@angular/routerライブラリがパスを認識し、/(ルート)にきたアクセスがrouter-outletタグで指定された要素に反映されるようになります。

404画面へのルーティング

次に、指定されていないパスにアクセスがあった時、404画面を表示させるためのルーティング設定を行いますので、まずは404画面を作成します。

ng g component error/page-not-found
src/app/error/page-not-found.html
<div class="page card">
    <section class="card text-center">
        <div class="card-block">
            <div class="error-code">404</div>
            <h3 class="font-bold">ページが見つかりません</h3>
        </div>
    </section>
</div>
src/app/error/page-not-found.css
.page {
  max-width: 960px;
  height: 100%;
  margin: 0 auto;
  padding: 10px 0;
  background-color: #fff;
  border-left: 1px solid #DDDDDD;
  border-right: 1px solid #DDDDDD;
  box-shadow: 0 0 15px #DDDDDD;
  overflow: scroll;
}

.page section {
  margin: 50px 10px 30px;
}

.page section.card .error-code {
  margin: 30px 0;
  color: #2d353c; 
  font-size: 96px;
  line-height: 100px;
}

これで404画面の作成は完了です。
先ほど設定したルートモジュールのRoutesに、作成したPageNotFoundComponentを登録します。

src/app/app.module.ts
const routes: Routes = [
  { path: '', component: ChatComponent }, 
  { path: '**', component: PageNotFoundComponent }, // 追加
];

今回追加したパスはワイルドカードで指定しています。
Routes内では上から順(配列のキーが小さい順)に評価が行われるので、この場合は次のような順序でパスが決まります。

  1. パスがルート''と合致すれはChatComponentを表示
  2. パスがルート'**'と合致すれはPageNotFoundComponentを表示(全てのパスが対象)

実行結果

cGnXXts6V5.gif

参考)リダイレクトさせたい場合

もし特定のページにアクセスがあった場合に、404画面ではなくTOPページにリダイレクトさせたいような時は、Routesに対し次のような記述を加えます。

src/app/app.module.ts
const routes: Routes = [
  { path: 'test', redirectTo: '', pathMatch: 'full'}, // 追加
  { path: '', component: ChatComponent }, 
  { path: '**', component: PageNotFoundComponent },
];

今回追加したredirectToプロパティを追加することで、/testにきたアクセスを`/`ルートにリダイレクトすることができます。
この記述の最後にpathMatch: 'full'というプロパティを加えていますが、これは「URLが完全一致した場合」という条件を指定しているものです。これがないと/test/aaのようなパスにアクセスがあった場合/aaへとリダイレクトをしてしまいます。

機能モジュールへのルーティング

上記ではパスに対してコンポーネントを指定していましたが、今度は機能モジュールをパスと紐付けてみます。

機能モジュールの作成

機能モジュール公式でFeature Moduleと表現されています。ある特定の機能群を1つにまとめ、他のモジュールとは独立して機能するよう定義されたモジュールです。
今回はアカウント(認証)にかかるログインやサインアップといった機能をAccountModuleとして扱えるようにしていきます。

では早速、Angular CLIを使ってモジュールとコンポーネントを作成します。

ng g module account --routing --module app-routing.module
ng g component account/login
ng g component account/sign-up

ng g module account --routing--routingというオプションコマンドは、ルーティング用ファイルaccont-routing.module.tsを自動的に作成してくれます。また、--moduleオプションは宣言するモジュールの指定ができます。
設定するURLが複数あるのであれば、このコマンドを使ってルーティング用ファイル作成しておいた方が良いです。

それでは本記事の最初で定義したURLのように、/accountにアクセスがあった場合にAccountModuleを読み込むようルートモジュールのRoutesで指定します。

src/app/app.module.ts

const routes: Routes = [
  {
    path: 'account',
    loadChildren: () => import('./account/account.module').then(m => m.AccountModule), // 追加
  },
  { path: '', component: ChatComponent },
  { path: '**', component: PageNotFoundComponent },
];

これで「/accountにアクセス→AccountModuleの読み込み」という動作をさせることができるようになったので、今度はAccountModule内でのルーティング設定を行います。

src/account/account-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { SignUpComponent } from "./sign-up/sign-up.component"; // 追加
import { LoginComponent } from "./login/login.component"; // 追加

const routes: Routes = [
  { path: 'login', component: LoginComponent }, // 追加
  { path: 'sign-up', component: SignUpComponent }, // 追加
];

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

http://localhost:4200/account/loginhttp://localhost:4200/account/sign-upにアクセスし、LoginComponentSignUpComponentのテンプレートが表示されれば機能モジュールのルーティングは完了です。

RouterModuleのメソッドがforRoot(appRoutes)ではなくforChild(appRoutes)となっている点に注意してください。

他のページに遷移する

これまでは直接URLを叩くことでルーティングを確認してきましたが、今度はコンポーネント内のリンクから他の画面に遷移するための実装を行います。

まず、前回作成したヘッダーからログイン画面へのリンクを有効化します。
ルーターに関するディレクティブを使えるようにするため、ルートモジュールにRouterModuleをインポートしておきます。

src/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { environment } from '../environments/environment';
import { RouterModule } from '@angular/router'; // 追加
import { AppRoutingModule } from './app-routing.module';
import { SharedModule } from './shared/shared.module';
import { AngularFireModule } from '@angular/fire';
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { AngularFireAuthModule } from '@angular/fire/auth';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

import { AppComponent } from './app.component';
import { ChatComponent } from './chat/chat.component';
import { HeaderComponent } from './header/header.component';
import { PageNotFoundComponent } from './error/page-not-found/page-not-found.component';

@NgModule({
  declarations: [
    AppComponent,
    ChatComponent,
    HeaderComponent,
    PageNotFoundComponent,
  ],
  imports: [
    BrowserModule,
    RouterModule, // 追加
    AppRoutingModule,
    SharedModule,
    NgbModule,
    AngularFireModule.initializeApp(environment.firebase),
    AngularFirestoreModule,
    AngularFireAuthModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
}

次に、ヘッダーのテンプレートを更新します。
ルーティングを行ったパスに遷移したい場合は、routerLinkというディレクティブを使用します。

app/header/header.component.html
<nav class="navbar fixed-top navbar-dark bg-primary">
  <a class="navbar-brand" href="#">NgChat</a>
  <span class="navbar-text">
    <a routerLink="/account/login" class="navbar-text">Login</a><!-- routerLinkに変更 -->
  </span>
</nav>

これでログイン画面へのリンク貼り付けが完了です。
ここでログイン画面とサインアップ画面も作成し、それぞれに行き来できるよう、routerLinkを実装しておきます。

app/account/login/login.component.html
<div class="page">
  <section class="card">
    <form class="form-login">
        <h2 class="form-login-heading">ログイン</h2>
        <label for="inputEmail" class="sr-only">Email address</label>
        <input type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
        <label for="inputPassword" class="sr-only">Password</label>
        <input type="password" id="inputPassword" class="form-control" placeholder="Password" required>
        <button class="btn btn-lg btn-info btn-block" type="submit">LOGIN</button>
    </form>
    <a routerLink="/account/sign-up" class="login-link">アカウントを作る</a>
  </section>
 </div>
app/account/login/login.component.css
.page {
  max-width: 960px;
  height: 100%;
  margin: 0 auto;
  padding: 10px 0;
  background-color: #fff;
  border-left: 1px solid #DDDDDD;
  border-right: 1px solid #DDDDDD;
  box-shadow: 0 0 15px #DDDDDD;
  overflow: scroll;
}

.page section {
  margin: 65px 10px 30px;
}

.form-login {
  max-width: 330px;
  padding: 15px;
  margin: 0 auto;
}

.form-login .form-login-heading,
.form-login .checkbox {
  margin-bottom: 10px;
}

.form-login .checkbox {
  font-weight: normal;
}

.form-login .form-control {
  position: relative;
  height: auto;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  padding: 10px;
  font-size: 16px;
}

.form-login .form-control:focus {
  z-index: 2;
}

.form-login #inputEmail {
  margin-bottom: -1px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}

.form-login #inputPassword {
  margin-bottom: 10px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

a.login-link {
  text-align: center;
  margin: 5px 0 15px;
}

app/account/sign-up/sign-up.component.html
<div class="page">
  <section class="card">
    <form class="form-signup">
        <h2 class="form-signup-heading">アカウント作成</h2>
        <label for="inputEmail" class="sr-only">Email address</label>
        <input type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
        <label for="inputPassword" class="sr-only">Password</label>
        <input type="password" id="inputPassword" class="form-control" placeholder="Password" required>
        <label for="confirmationPassword" class="sr-only">Password</label>
        <input type="password" id="confirmationPassword" class="form-control" placeholder="Password Confirmation" required>
        <button class="btn btn-lg btn-success btn-block" type="submit">SIGN UP</button>
    </form>
    <a routerLink="/account/login"class="signup-link">ログインする</a>
  </section>
 </div>
app/account/sign-up/sign-up.component.css
.page {
  max-width: 960px;
  height: 100%;
  margin: 0 auto;
  padding: 10px 0;
  background-color: #fff;
  border-left: 1px solid #DDDDDD;
  border-right: 1px solid #DDDDDD;
  box-shadow: 0 0 15px #DDDDDD;
  overflow: scroll;
}

.page section {
  margin: 65px 10px 30px;
}

.form-signup {
  max-width: 330px;
  padding: 15px;
  margin: 0 auto;
}

.form-signup .form-signup-heading,
.form-signup .checkbox {
  margin-bottom: 10px;
}

.form-signup .checkbox {
  font-weight: normal;
}

.form-signup .form-control {
  position: relative;
  height: auto;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  padding: 10px;
  font-size: 16px;
}

.form-signup .form-control:focus {
  z-index: 2;
}

.form-signup #inputEmail {
  margin-bottom: -1px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}

.form-signup #inputPassword {
  border-radius: 0;
  border-collapse: collapse;
  margin-bottom: -1px;
}

.form-signup #confirmationPassword {
  margin-bottom: 10px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

a.signup-link {
  text-align: center;
  margin: 5px 0 15px;
}

実行結果

PK4zevaCpH.gif

これで認証画面への遷移をすることができるようになりました。
次はAngularで採用されているRxJSについて解説します。

ソースコード

この時点でのソースコード
※firebaseのapiKeyは削除しているので、試すときは自身で作成したapiKeyを入れてください。

125
136
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
125
136