Angularでスライドショー(2) 〜Timerによるページの自動切り替え〜

#はじめに

Angularでスライドショーを作成する方法について説明しているこの記事、第2回目はTimer機能を使ったページの自動遷移についてです。
解説はいつものとおり「恋に落ちるコード.js」の絵子と樹里がお送りします。

##人物紹介

瀬尾絵子:「恋に落ちるコード.js」の登場人物。JavaScript勉強中の女子高生。
篠宮樹里: 同じく「恋に落ちるコード.js」の登場人物。JavaScriptに詳しい女子高生。

#今回の目的

樹里「さて、前回はルーティング機能によるページ遷移について学習したわけだが」
絵子「したねー。Angular CLI便利だねー」
樹里「ただし、手動でページを切り替えるのでは、ただのWebページと一緒だ」
絵子「そりゃそうだ」
樹里「目標はスライドショーの作成なので、自動でページが切り替わってくれないと困る。そうしないと各方面から苦情が来る」
絵子「どこの方面だろう」
樹里「なので、今日はタイマーを使ったページの自動切り替え機能を実装するぞ」
絵子「おっけー」

#ルートコンポーネントを修正する

樹里「では、前回の続きだ。app.component.tsを次のように修正しよう」

```js:app.component.ts
/* ↓(1) インポートするモジュールの追加 */
// import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { SlideshowService }  from './slideshow.service';
/* ↑ */

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
/* ↓(2) メインの処理 */
/*
export class AppComponent {
  title = 'app';
}
*/
export class AppComponent implements OnInit {
  constructor(private slideshow: SlideshowService) { }
  ngOnInit() {
    this.slideshow.start();
  }
}
/* ↑ */
```

##インポートするモジュールの追加

樹里「まず、Componentモジュールに加えてOnInitモジュールもインポートする」
絵子「OnInitって、「[Redmineのチケット一覧を取得する](http://qiita.com/8amjp/items/6788ff2c87c81b175a65)」の時も出てきたよね。初期化処理はここに書くと良いんだっけ？」
樹里「そう。それから。後で作るSlideshowServiceもインポートしておく」
絵子「サービスも出てきたな。独立した機能はサービスとして別に作る、と」

##メインの処理

樹里「次に、メインの処理を書き換える。まず、SlideshowServiceのインスタンスを作成する」
絵子「ねえ、キレイに撮れてる？」
樹里「……言っておくが、インスタンスって、インスタに写真をアップする事じゃないからな」
絵子「ちょっとしたジョークじゃない……そんな冷たい目で睨まなくても」
樹里「次やったら、一生取れない犬の鼻を貼り付けるぞ……で、`ngOnInit`メソッドで、SlideshowServiceの`start`メソッドを実行する」
絵子「はい……」

#サービス

樹里「次に、ページの自動切り替え機能を提供するサービス、slideshow.service.tsをサクッと作成しよう。コマンドで次のように入力する」

```
ng g service slideshow
```

絵子「サクッと出来たね」
樹里「では、サクッと修正だ」

```js:slideshow.service.ts
import { Injectable } from '@angular/core';
/* ↓(1) インポートするモジュールの追加 */
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
/* ↑ */

@Injectable()
export class SlideshowService {

/* ↓(2) コンストラクタの修正 */
  //constructor() { }
  constructor(public router: Router) { }
/* ↑ */
/* ↓(3) タイマーを作成 */
  start(): void{
    Observable
      .interval(3000)
      .subscribe(i => this.router.navigateByUrl("page" + (i % 3 + 1)));
  }
/* ↑ */
}

```

##インポートするモジュールの追加

絵子「Observableって、また出てきたね。これって結局何なの？」
樹里「Observableとは、RxJS(Reactive Extensions for JavaScript)というライブラリの中核を成す機能だ。RxJSとは元々Microsoftが開発した非同期処理などを簡潔に処理するためのライブラリで、その非同期のストリームを観察(Observe)することにより……」
絵子「あれ、樹里の姿が遠くに見える。樹里の声が遠くから聞こえる」
樹里「要は、JSONの読み込みだとか、今回のタイマーの処理だとか、非同期の処理を上手いことやってくれる便利な機能だ」
絵子「そっかー。上手いことやってくれるならそれに任せよう」
樹里「で、上記の例ではObservableモジュールとintervalオペレータを別々に記述しているが、いちいち列挙するのが面倒ならこう書いてもいい」

```js:slideshow.service.ts(抜粋)
import { Observable } from 'rxjs/Rx';
```
絵子「あ、これで全部読み込んでくれるんだ。そういう便利なことは早く言ってよ」

##コンストラクタの修正

樹里「これもさっきと同じように、Routerモジュールのインスタンスを作成している」
絵子「……何よ、もうふざけないよ……その手に持ってる犬の鼻と接着剤は何？」

##タイマーを作成

樹里「では、メインとなるタイマーの作成だ。`interval`メソッドで、等間隔で値を発行することが出来る」
絵子「JavaScriptにも、`setInterval`メソッドってあったよね。あれと同じもの？」
樹里「出来る事は同じだな。発行間隔をミリ秒で指定するのも同じだ」
絵子「3000ミリ秒、つまり3秒間隔に指定してるのね」
樹里「で、`subscribe`メソッドで動作を指定する。その際、実行された回数が引数として渡されるので、それを`i`という変数に格納している」
絵子「しかと受け取った」
樹里「タイマーが発行される度に、Routerモジュールの`navigateByUrl`メソッドが実行される。引数は遷移先のURLだ」
絵子「引数は『"page" + (i % 3 + 1)』ってやつだね…………何これ？」
樹里「`i`にはタイマーの実行回数が格納されている。それを3で割って余りを算出し、1を足している。では、1回目の処理の時は何になる？」
絵子「えーと、1÷3だから、0余り1になって、それに1を足して……2だね」
樹里「そう、だから、1回目は『page2』に遷移することになる」
絵子「なるほど。2回目はpage3、3回目はpage1、4回目はまたpage2……になるわけだね」

#ルートモジュールを修正する

樹里「最後に、app.module.tsの修正だ」

```app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { Page1Component } from './page1/page1.component';
import { Page2Component } from './page2/page2.component';
import { Page3Component } from './page3/page3.component';
/* ↓(1) インポートするモジュールの追加 */
import { SlideshowService } from './slideshow.service';
/* ↑ */

@NgModule({
  declarations: [
    AppComponent,
    Page1Component,
    Page2Component,
    Page3Component
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [
/* ↓(2) プロパイダーの追加 */
    SlideshowService
/* ↑ */
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

##インポートするモジュールの追加

樹里「さっき作ったSlideshowServiceをインポートしている」
絵子「うん、これはもうバッチリわかるよ」

##プロパイダーの追加

樹里「サービスをサービスとして使用する時には、このようにprovidersプロパティに追加する必要がある。……これはもう、こういうルールなんだ、と覚えておくしかないな」
絵子「うん、覚えた。こういうもんだ」

#動作確認
樹里「では、完成したところでさっそく動かしてみよう」

```
ng s
```

絵子「おー、3秒毎に勝手に画面が切り替わっていく」
樹里「単純なスライドショー機能ならこれだけで実現できる。しかし……」
絵子「現実はそんなに甘くはないよね」
樹里「そう。URLが連番でないと動作しないし、ページによって表示する時間を変えることも出来ない。実際には、もっと複雑な処理が必要になる」
絵子「ですよねー」
樹里「次回は、これをベースにして、もっと凝ったものを作ってみよう」
絵子「作ってみよう！」

#次回予告
次回は、より実践的なページの切り替え方法について解説します。