HOME備忘帳AngularJSチュートリアル

3.繰り返し要素のフィルタ

<戻る(2.Angularテンプレート) 次へ(4.2種類のバインディング)>

 

今度は、テキスト全文検索機能を追加してみましょう。
簡単にできます。

また、E2E(エンド・トゥ・エンド)テストも書きます。

変更済みのファイル一式は、git checkout -f step-3で取得できます。

コントローラ

コントローラはステップ2から変更ありません。

テンプレート

app/index.htmlの変更箇所は以下の通りです。

<div class="container-fluid">
  <div class="row">
    <div class="col-md-2">
      <!-- サイドバー -->
      検索: <input ng-model="query">
    </div>
    <div class="col-md-10">
      <!-- メイン -->
      <ul class="phones">
        <li ng-repeat="phone in phones | filter:query">
          {{phone.name}}
          <p>{{phone.snippet}}</p>
        </li>
      </ul>
    </div>
  </div>
</div>

検索テキストを入力するための、<input>タグを追加しました。
また、ngRepeatディレクティブにAngularのfilter関数を加えました。

ユーザーが検索テキストを入力すると、即座にスマホリストに反映されます。

テスト

ステップ2ではユニットテストのやり方を学びました。
ユニットテストは、コントローラはじめJavascript部分のテストにはぴったりですが、DOM操作やアプリ全体のテストには向いていません。
その用途には、エンド・ツー・エンド(E2E)テストがよい選択です。

今回追加した検索機能が正しく動いているか、E2Eテストで確認してみましょう。

test/e2e/scenarios.jsの内容は以下の通りです。

describe('スマホ表示アプリ', function() {

  describe('スマホリスト表示', function() {

    beforeEach(function() {
      blowser.get('app/index.html');
    });


    it('検索ボックスへの入力でリストをフィルタ', function() {
      var phoneList = element.all(by.repeater('phone in phones'));
      var query = element(by.model('query'));

      expect(phoneList.count()).toBe(3);

      query.sendKeys('nexus');
      expect(phoneList.count()).toBe(1);

      query.clear();
      query.sendKeys('motorla');
      expect(phoneList.count()).toBe(2);
    });
  });
});

このテストは、検索ボックスとリストが一緒にちゃんと動くことを確認しています。

Protractorを使ったE2Eテストの実行

テストの構文はKarmaのユニットテストのものと似ていますが、E2EテストにはProtractorのAPIを使います。
ProtractorのAPIについてはこちらを参照してください:http://angular.github.io/protractor/#/api

npm run protractorで実行できます。

Karmaは常駐してファイルが更新されると自動で再テストを行いますが、Protractorは一度テストが終わると終了します。E2Eテストには時間がかるためです。再テストをする時は、もう一度コマンドを実行してください。

注意:
Protractorによるテストには、アプリをサーバ経由で実行する必要があります。npm startコマンドでサーバを起動できます。
また、Protractorとウェブドライバがインストールされていることも確認してください。npm installおよびnpm run update-webdriverコマンドを使ってください。

実験

現在の検索文字列を表示

index.htmlを変更し、queryモデルの現在の値をバインディング{{query}}で表示してみてください。

その後、検索枠にテキストを入力して、どう動くか確認してください。

検索文字列をtitleに表示

HTMLのページタイトルに検索文字列を表示してみましょう。

まずはE2Eテストを追加します。test/e2e/scenarios.jsを以下のように更新します。

describe('スマホ表示アプリ', function() {

  describe('スマホリスト表示', function() {
  
    beforeEach(function() {
      browser.get('app/index.html');
    });

    var phoneList = element.all(by.repeater('phone in phones'));
    var query = element(by.model('query'));

    it('検索ボックスへの入力でリストをフィルタ', function() {
      expect(phoneList.count()).toBe(3);
      query.sendKeys('nexus');
      expect(phoneList.count()).toBe(1);
      query.clear();
      query.sendKeys('motorola');
      expect(phoneList.count()).toBe(2);
    });

    it('現在の検索文字列をtitleに表示', function() {
      query.clear();
      expect(browser.getTitle()).toMatch(/Google Phone Gallery:\s*$/);
      query.sendKeys('nexus');
      expect(browser.getTitle()).toMatch(/Google Phone Gallery: nexus$/);
    });
  });
}); 

そしてprotractorを実行して(npm run protractor)テストが失敗することを確認します。

正しく動かすためには、単に、titleタグに{{query}}を足せばいい、と、思うかもしれません:

<title>Google Phone Gallery: {{query}}</title>

ですが、ページを再読み込みしても、思う結果になりません。

原因は、queryというモデルは、ng-controllerディレクティブで定義されたスコープの中だけで有効なことです。ng-controller="PhoneListCtrl"はbodyタグに設定されており、titleタグはbodyの外にあります。

解決方法のひとつは、ng-controllerディレクティブをbodyではなくhtmlに移動させることです。

<html ng-app="phonecatApp" ng-controller="PhoneListCtrl">

htmlタグを上記のように書き換え(もちろん、bodyタグのng-controller部分は削除します)、E2Eテストにパスすることを確認してください。

この方法は上手く動いているように見えますが、ページの読み込みが完了する前の一瞬、{{query}}というバインド文字列そのものが表示されてしまいます。

これを防ぐより良い解決策は、(直接{{query}}を使うのではなく)ngBindまたはngBindTemplateディレクティブを使用することです。

<title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>

このステップの成果

テキスト全文検索機能と、その検索が正しく動いているか確認するためのテストができました。
次のステップ4では、並び替え機能の追加方法を学びましょう。

最終更新日:2015/12/24

[ ページ先頭へ ]