ng-mtg#6 AngularJS ディレクティブ・パターン
Post on 19-Jun-2015
7700 Views
Preview:
DESCRIPTION
Transcript
1
ディレクティブ・パターン
2
@frontainerFront-end Engineer
2014/4 LIG入社 フロントエンドエンジニア
プロフィール
http://github.com/frontainer
http://trifort.jp/library/cavy/
現在はWebサイトを作ったりテンプレート作ったりいろいろ
前職ではゲームのフロントエンド開発や canvasライブラリを作って遊んだりしてました。
これまでBackboneで2案件、Angularで5案件実装してきました。
林 優一
3
@frontainerFront-end Engineer
前回はLIG社内で勉強会を行いました
プロフィール
http://www.slideshare.net/frontainer/angularjs-1angularjs-lig
4
@frontainerFront-end Engineer
犬が好きです
プロフィール
5
本題に入る前に質問です
6
Q. AngularJSを実務で使ったことはありますか?
7
Q. (チュートリアル以外で)ディレクティブを作ったことはありますか?
8
今回はそんな「ディレクティブ」について
過去の案件を振り返ってパターン分けしてみました
実装のパターン
[おまけ]より良いパフォーマンスのために
まとめ
9
呼び出しのパターン
ディレクティブ・パターンとは
10
ディレクティブ・パターンとは
11
ディレクティブ・パターンとは
ディレクティブ・パターンとは
一般的に認知されているものではなく勝手に付けた呼称です。
パターン化することで使う場面を想定してもらえるようになれば嬉しいです
ディレクティブをあまり活用したことのない方に少しでもヒントになれば
過去に携わった案件でのディレクティブを振り返ってパターン分けしたもの
12
呼び出しパターン
13
呼び出しパターン
angular.module('directives').directive('dummyText',function() { return { restrict: 'ACEM', replace: true, template: '<p class="bg-success">OK</p>' }; });
ディレクティブ名呼び出し方
14
Attr ibute
Class
Element
Comment
01 Restrict: A
02 Restrict: C
03 Restrict: E
04 Restrict: M
呼び出しパターン
15
The normalization process is as follows: !
Strip x- and data- from the front of the element/attributes. Convert the :, -, or _-delimited name to camelCase.[引用] https://docs.angularjs.org/guide/directive
呼び出しパターン
16
通常以下のような処理が行われます: !
ElementやAttributeの前についているx-とdata-は取り除きます。 :、-や_などの区切り文字はキャメルケースへ変換されます。
呼び出しパターン
17
検証
18
01 Restrict: A
呼び出しパターン
- Attribute -
<div dummy-text><p class=“bg-danger">NG</p></div> <div dummy_text><p class=“bg-danger">NG</p></div> <div dummy:text><p class=“bg-danger”>NG</p></div> <div dummyText><p class=“bg-danger">NG</p></div> <div data-dummy-text><p class=“bg-danger">NG</p></div> <div data-dummy_text><p class=“bg-danger">NG</p></div> <div data-dummy:text><p class=“bg-danger”>NG</p></div> <div data-dummyText><p class=“bg-danger">NG</p></div> <div x-dummy-text><p class=“bg-danger">NG</p></div> <div x-dummy_text><p class=“bg-danger">NG</p></div> <div x-dummy:text><p class=“bg-danger">NG</p></div> <div x-dummyText><p class="bg-danger">NG</p></div>
19
いろんな属性で呼び出してみた
呼び出しパターン 01 Restrict: A
<div dummy-text><p class=“bg-danger">NG</p></div> <div dummy_text><p class=“bg-danger">NG</p></div> <div dummy:text><p class=“bg-danger”>NG</p></div> <div dummyText><p class=“bg-danger">NG</p></div> <div data-dummy-text><p class=“bg-danger">NG</p></div> <div data-dummy_text><p class=“bg-danger">NG</p></div> <div data-dummy:text><p class=“bg-danger”>NG</p></div> <div data-dummyText><p class=“bg-danger">NG</p></div> <div x-dummy-text><p class=“bg-danger">NG</p></div> <div x-dummy_text><p class=“bg-danger">NG</p></div> <div x-dummy:text><p class=“bg-danger">NG</p></div> <div x-dummyText><p class="bg-danger">NG</p></div>
20
結果・・・
呼び出しパターン
camelCaseはダメでした
01 Restrict: A
21
02 Restrict: C
呼び出しパターン
- Class -
<div class="dummy-text"><p class=“bg-danger">NG</p></div> <div class="dummy_text"><p class=“bg-danger">NG</p></div> <div class="dummy:text"><p class=“bg-danger">NG</p></div> <div class="dummyText"><p class=“bg-danger">NG</p></div> <div class="data-dummy-text"><p class=“bg-danger">NG</p></div> <div class="data-dummy_text"><p class=“bg-danger">NG</p></div> <div class=“data-dummy:text”><p class=“bg-danger">NG</p></div> <div class="data-dummyText"><p class=“bg-danger">NG</p></div> <div class="x-dummy-text"><p class=“bg-danger">NG</p></div> <div class="x-dummy_text"><p class=“bg-danger">NG</p></div> <div class=“x-dummy:text”><p class=“bg-danger">NG</p></div> <div class="x-dummyText"><p class="bg-danger">NG</p></div>
22
いろんなクラスで呼び出してみた
呼び出しパターン 02 Restrict: C
23
<div class="dummy-text"><p class=“bg-danger">NG</p></div> <div class="dummy_text"><p class=“bg-danger">NG</p></div> <div class="dummy:text"><p class=“bg-danger">NG</p></div> <div class="dummyText"><p class=“bg-danger">NG</p></div> <div class="data-dummy-text"><p class=“bg-danger">NG</p></div> <div class="data-dummy_text"><p class=“bg-danger">NG</p></div> <div class=“data-dummy:text”><p class=“bg-danger">NG</p></div> <div class="data-dummyText"><p class=“bg-danger">NG</p></div> <div class="x-dummy-text"><p class=“bg-danger">NG</p></div> <div class="x-dummy_text"><p class=“bg-danger">NG</p></div> <div class=“x-dummy:text”><p class=“bg-danger">NG</p></div> <div class="x-dummyText"><p class="bg-danger">NG</p></div>
呼び出しパターン 02 Restrict: C
結果・・・
:はダメでした
24
03 Restrict: E
呼び出しパターン
- Element -
<dummy-text><p class=“bg-danger">NG</p></dummy-text> <dummy_text><p class=“bg-danger”>NG</p></dummy_text> <dummy:text><p class=“bg-danger">NG</p></dummy:text> <dummyText><p class=“bg-danger">NG</p></dummyText> <data-dummy-text><p class=“bg-danger">NG</p></data-dummy-text> <data-dummy_text><p class=“bg-danger">NG</p></data-dummy_text> <data-dummy:text><p class=“bg-danger">NG</p></data-dummy:text> <data-dummyText><p class=“bg-danger”>NG</p></data-:dummyText> <x-dummy-text><p class=“bg-danger">NG</p></x-dummy-text> <x-dummy_text><p class=“bg-danger">NG</p></x-dummy_text> <x-dummy:text><p class=“bg-danger">NG</p></x-dummy:text> <x-dummyText><p class=“bg-danger">NG</p></x-dummyText>
25
呼び出しパターン
いろんなタグで呼び出してみた
03 Restrict: E
<dummy-text><p class=“bg-danger">NG</p></dummy-text> <dummy_text><p class=“bg-danger”>NG</p></dummy_text> <dummy:text><p class=“bg-danger">NG</p></dummy:text> <dummyText><p class=“bg-danger">NG</p></dummyText> <data-dummy-text><p class=“bg-danger">NG</p></data-dummy-text> <data-dummy_text><p class=“bg-danger">NG</p></data-dummy_text> <data-dummy:text><p class=“bg-danger">NG</p></data-dummy:text> <data-dummyText><p class=“bg-danger”>NG</p></data-:dummyText> <x-dummy-text><p class=“bg-danger">NG</p></x-dummy-text> <x-dummy_text><p class=“bg-danger">NG</p></x-dummy_text> <x-dummy:text><p class=“bg-danger">NG</p></x-dummy:text> <x-dummyText><p class=“bg-danger">NG</p></x-dummyText>
26
呼び出しパターン
結果・・・
camelCaseは ダメでした
03 Restrict: E
27
04 Restrict: M
呼び出しパターン
- Comment -
<!-- directive: dummy-text —> <!-- directive: dummy_text —> <!-- directive: dummy:text —> <!-- directive: dummyText —> <!-- directive: data-dummy-text —> <!-- directive: data-dummy_text —> <!-- directive: data-dummy:text —> <!-- directive: data-dummyText —> <!-- directive: x-dummy-text —> <!-- directive: x-dummy_text —> <!-- directive: x-dummy:text —> <!-- directive: x-dummyText -->
28
呼び出しパターン
いろんなコメントで呼び出してみた
04 Restrict: M
<!-- directive: dummy-text —> <!-- directive: dummy_text —> <!-- directive: dummy:text —> <!-- directive: dummyText —> <!-- directive: data-dummy-text —> <!-- directive: data-dummy_text —> <!-- directive: data-dummy:text —> <!-- directive: data-dummyText —> <!-- directive: x-dummy-text —> <!-- directive: x-dummy_text —> <!-- directive: x-dummy:text —> <!-- directive: x-dummyText -->
29
呼び出しパターン
結果・・・
:はダメでした
04 Restrict: M
AttributeとElementの解釈は同じ(camelCaseダメ)
ClassとCommentの解釈は同じ(:ダメ)
30
呼び出しパターン
31
呼び出しパターン
ディレクティブ名の使い分け
わかりやすい記法 → dummy-text,dummy_text
HTMLに準拠 → data-dummy-text
XMLに準拠 → dummy:text
XHTMLに準拠 → x-dummy-text
32
ちなみに・・・
呼び出しパターン
28%
2%
70%
33
04 Restrict: M
01 Restrict: A02 Restrict: C
03 Restrict: E
過去の案件で実際に呼び出された形式の割合
呼び出しパターン
1個もなかった
100%
34
dummy-text
過去の案件で実際に呼び出された名称形式の割合
呼び出しパターン
これしかなかった
ディレクティブ名はキャメルケースだけど呼び出しをキャメルにすると 呼び出せないことがある
restrict: mはあまり使われていない。ドキュメントでもあまり触れられていない
35
呼び出しパターンを調べてみて得たこと
呼び出しは-(ハイフン)つなぎが無難
実装パターン
36
実装パターン
37
01
実装パターン
テンプレートパターン
02 共通パーツパターン
04 jQueryプラグインパターン
03 イベントフックパターン
38
01 テンプレートパターン
実装パターン
- Template Pattern -
39
共通のテンプレートを反映させるためのディレクティブ。 ng-includeと同様だが、名称が明確になりマークアップがわかりやすくなる。
実装パターン
01 テンプレートパターン - Template Pattern -
<sidebar></sidebar>
40
実例
実装パターン 01 テンプレートパターン
<sidebar></sidebar>
41
実例
実装パターン 01 テンプレートパターン
アクセスしたページによって activeになる要素を変えたい
今このページを表示している
<sidebar></sidebar>
42
実例
angular.module('directives').directive('sidebar',function($location) { return { restrict: 'E', replace: true, templateUrl: 'views/sidebar.html' }; });
directives/sidebar.js
実装パターン 01 テンプレートパターン
外部にあるHTMLを呼び出し
タグをテンプレートで置換
Elementとして使う
43
controllers/RootCtrl.js
angular.module('controllers').controller('RootCtrl', function ($scope,$location,$rootScope) { $rootScope.$on('$locationChangeSuccess', function() { $scope.current = $location.path(); }); });
<sidebar></sidebar>実例
実装パターン 01 テンプレートパターン
locationが変更されたら currentの値を現在のパスを入れる
44
templates/sidebar.html
<nav> <ul class="nav nav-pills nav-stacked"> <li ng-class="{active:current==='/'}"><a href="#/">TOP</a></li> <li ng-class="{active:current==='/restrict/a'}"><a href=“#/menu/a">MENU A</a></li> <li ng-class="{active:current==='/restrict/c'}"><a href=“#/menu/b">MENU B</a></li> </ul> </nav>
<sidebar></sidebar>実例
実装パターン 01 テンプレートパターン
currentの値が一致していたら activeクラスをつける
45
<sidebar></sidebar> <div ng-include=“‘templates/sidebar.html’”></div>
<sidebar></sidebar>実例
実装パターン 01 テンプレートパターン
動作は同じですが、内容が隠蔽されてシンプルに
46
02 共通パーツパターン
実装パターン
- Common Parts Pattern -
47
共通の動作や処理をもつパーツのディレクティブ。 ボタンやインプットフォームなど繰り返し使われる要素に有効。
実装パターン
02 共通パーツパターン - Common Parts Pattern -
48
<copyright></copyright>実例
実装パターン 01 テンプレートパターン
49
<copyright></copyright>実例
実装パターン 01 テンプレートパターン
Copyright © 2014 frontainer.com All Rights Reserved.
毎年更新してくれと言われる
記述の仕方忘れがち
50
directives/copyright.js
<copyright></copyright>実例
angular.module('directives').directive('copyright',function() { return { restrict: 'E', replace: true, scope: { name: '@' }, template: ‘<div>Copyright © {{year}} {{name}} All Rights Reserved.</div>', link: function($scope) { $scope.year = new Date().getFullYear(); } }; });
実装パターン 01 テンプレートパターン
name属性の値をscopeに入れる
現在の年をscope.yearに入れる
yearとnameをバインド
@
=
&
文字列として取得親scopeとバインディング関数として取得
51
<copyright></copyright>実例
<copyright name="frontainer.com"></copyright>
<div name="frontainer.com">Copyright © 2014 frontainer.com All Rights Reserved.</div>
実装パターン 01 テンプレートパターン
こうなる
これでだいぶ使い回すことができるようになった
<select-list></select-list>
52
実例
実装パターン 02 共通パーツパターン
<select-list></select-list>
53
実例
実装パターン 02 共通パーツパターン
セレクトボックスまでいかない 簡易的な選択をさせたい
選択したデータがモデルに格納されるように
<select-list></select-list>
54
実例
<div class="list-group"> <a class="list-group-item" ng-repeat="human in data" ng-click="select(human)" ng-class="{active:ngModel.id === human.id}”> {{human.name}} </a> </div>
templates/select-list.html
実装パターン 02 共通パーツパターン
クリックしたらselect関数実行
モデルのIDと自身のIDが一致したら activeクラスをつける
controllerであらかじめ作っておいた 配列の数だけ繰り返す
<select-list></select-list>
55
実例
angular.module('directives').directive('selectList',function() { return { restrict: 'EA', replace: true, scope: { data: '=', ngModel: '=' }, templateUrl: 'js/directives/pattern/03.html', controller: function($scope) { $scope.select = function(val) { $scope.ngModel = val; } } }; });
directives/select-list.js
実装パターン 02 共通パーツパターン
dataとng-model属性に指定した値を 自身のscopeとバインドさせる
要素が選択されたときに 選択した値をモデルに格納する
<select-list></select-list>
56
実例
angular.module('controllers').controller('RootCtrl', function ($scope) { $scope.data = [{ id: 0, name: 'Ichiro', },{ id: 1, name: 'Jiro' },{ id: 2, name: 'Hanako' } ]; });
controllers/RootCtrl.js
実装パターン 02 共通パーツパターン
選択肢を配列であらかじめ作っておく
<select-list></select-list>
57
実例
実装パターン 02 共通パーツパターン
$scope.select = function(val) { $scope.ngModel = val; }
ng-click=“select(human)”
<select-list ng-model="selectedModel" data=“data"></select-list>
ng-model=“selectModel”と$scope.ngModelはバインディングしているので…
{{selectModel}}に選択したデータが入ってる
angular.module('controllers').controller('RootCtrl', function ($scope) { $scope.data = [{ id: 0, name: 'Ichiro' } ]; });
<select-list></select-list>
58
実例
実装パターン 02 共通パーツパターン
配列の中身を変えればいろんな選択肢ができる
見た目はCSSでカスタマイズできる
59
03 イベントフックパターン
実装パターン
- Event Hook Pattern -
60
一定のイベントをフックしてDOMを操作したり ファイルをアップロードしたりするディレクティブ
実装パターン
03 イベントフックパターン - Event Hook Pattern -
<textarea shift-submit></textarea>
61
実例
実装パターン 03 イベントフックパターン
<textarea shift-submit></textarea>
62
実例
実装パターン
Shiftキーを押しながらEnterで送信したい
もちろん送信ボタンでも送信できる
03 イベントフックパターン
<form id=“message-form” ng-submit=“send();”> <textarea shift-submit=“message-form”></textarea> <p><input type=“submit” class=“btn btn-primary”></p> </form>
<textarea shift-submit></textarea>
63
実例
実装パターン
SubmitしたいformタグのID
Submitされたときの処理
03 イベントフックパターン
64
実例
angular.module(“directives").directive("shiftSubmit", function() { return { restrict: ‘AC', scope: { shiftSubmit: ‘@’ }, link : function($scope, $element){ function keydownHandler(e) { var $form = angular.element('#' + $scope.shiftSubmit); if (e.shiftKey && e.keyCode === 13) { e.preventDefault(); $form.triggerHandler('submit'); } } $element.on("keydown", keydownHandler); $scope.$on('$destroy', function() { $element.off("keydown", keydownHandler); });
directives/ShiftSubmit.js
実装パターン
<textarea shift-submit></textarea>
要素にkeydownイベントをつける
shiftSubmit属性の値をもとに form要素を取得
ShiftキーとEnterキーが押されてたら form要素をsubmit
03 イベントフックパターン
65
実例
angular.module('controllers').controller('PatternCtrl', function ($scope) { $scope.messages = []; $scope.inputMessage = ‘'; $scope.send = function() { var message = $scope.inputMessage; if (!message) return; $scope.messages.unshift({ date: new Date(), message: message }); $scope.inputMessage = ''; }; });
controllers/PatternCtrl.js
実装パターン
<textarea shift-submit></textarea>
inputMessageモデルから入力された メッセージを取得
03 イベントフックパターン
66
実装パターン
04 jQueryプラグインパターン - jQuery Plugin Pattern -
67
AngularJSでjQueryプラグインを使いたいときのディレクティブ 参照が残らないように注意が必要なのと、プラグインでリークしないかどうかの注意も必要
実装パターン
04 jQueryプラグインパターン - jQuery Plugin Pattern -
<div tooltip=“ツールチップ表示”></div>
68
実例
実装パターン 04 jQueryプラグインパターン
<div tooltip=“ツールチップ表示”></div>
69
実例
実装パターン
マウスオーバーでツールチップを表示
jquery.powertip.jsを他ページで使っていたので それに合わせる必要があった
04 jQueryプラグインパターン
70
実例
angular.module('directives').directive('tooltip',function() { return { restrict: 'AC', replace: true, scope: { tooltip: ‘@’ }, link: function($scope,$element) { $element.data('powertip',$scope.tooltip).powerTip(); $scope.$on('$destroy', function() { $element.powerTip('destroy'); }); } }; });
directives/tooltip.js
実装パターン
<div tooltip=“ツールチップ表示”></div>
powertipの使い方に合わせて 要素にツールチップの文言を設定
scopeが破棄されたときには powertipも合わせて破棄
04 jQueryプラグインパターン
71
実例
実装パターン
<div tooltip=“ツールチップ表示”></div>
ng-repeatで回しても、Ajax等で追加されても ツールチップがちゃんと出る
<div><a tooltip="jquery.powertip.jsを使ってます">ツールチップを表示</a></div> <div><a tooltip="jquery.powertip.jsを使ってます2">ツールチップを表示</a></div> !<div ng-repeat="human in data"> <a tooltip="jquery.powertip.jsを使ってます{{$index}}">ツールチップを表示</a> </div>
04 jQueryプラグインパターン
意外とパターンは少ない 活用しきれていない or モジュールが充実しているので自前実装が不要
一番多かったのは共通パーツパターン 次いでイベントフックパターン
72
パターン分けしてみてわかったこと
複雑な処理がディレクティブ化されているのを見ると嬉しくなる
実装パターン
ng-grid
73
複雑な処理のディレクティブ例
実装パターン
74
/*********************************************** * ng-grid JavaScript Library * Authors: https://github.com/angular-ui/ng-grid/blob/master/README.md * License: MIT (http://www.opensource.org/licenses/mit-license.php) * Compiled At: 04/29/2014 10:21 ***********************************************/ (function(window, $) { 'use strict'; // the # of rows we want to add to the top and bottom of the rendered grid rows var EXCESS_ROWS = 6; var SCROLL_THRESHOLD = 4; var ASC = "asc"; // constant for sorting direction var DESC = "desc"; // constant for sorting direction var NG_FIELD = '_ng_field_'; var NG_DEPTH = '_ng_depth_'; var NG_HIDDEN = '_ng_hidden_'; var NG_COLUMN = '_ng_column_'; var CUSTOM_FILTERS = /CUSTOM_FILTERS/g; var COL_FIELD = /COL_FIELD/g; var DISPLAY_CELL_TEMPLATE = /DISPLAY_CELL_TEMPLATE/g; var EDITABLE_CELL_TEMPLATE = /EDITABLE_CELL_TEMPLATE/g; var CELL_EDITABLE_CONDITION = /CELL_EDITABLE_CONDITION/g; var TEMPLATE_REGEXP = /<.+>/; window.ngGrid = {}; window.ngGrid.i18n = {}; !
【引用】https://github.com/angular-ui/ng-grid
75
<div class="gridStyle" ng-grid="gridOptions"></div>
実装パターン
var app = angular.module('myApp', ['ngGrid']); app.controller('MyCtrl', function($scope) { $scope.myData = [{name: "Moroni", age: 50}, {name: "Tiancum", age: 43}, {name: "Jacob", age: 27}, {name: "Nephi", age: 29}, {name: "Enos", age: 34}]; $scope.gridOptions = { data: 'myData' }; });
【引用】https://github.com/angular-ui/ng-grid
view
controller
並べ替えもできる
76
より良いパフォーマンスのために
$templateCacheを活用しよう
gulp-angular-templatecache
$templateCache.put('hello.html', ‘<p>Hello World!!</p>’);
77
HTTPリクエストが増えすぎたときは
シンタックスハイライトが効かなかったりして読みにくいので、 GruntやGulpを使って自動的にテンプレートキャッシュを作る
grunt-angular-templates
https://docs.angularjs.org/api/ng/service/$templateCache
https://www.npmjs.org/package/grunt-angular-templates
https://www.npmjs.org/package/gulp-angular-templatecache
より良いパフォーマンスのために
78
HTTPリクエストが増えすぎたときは
より良いパフォーマンスのために
こんな感じでまとめてくれる
79
まとめ
最後駆け足になりましたが、まとめ
ディレクティブを使うことでビューがコンパクトでわかりやすくなる
HTTPリクエストの数が増えたら$templateCacheを使ってテンプレートをひとまとめに
コントローラの負担を減らすために、DOM操作系は極力ディレクティブにしよう
80
まとめ
まとめ
共通パーツを使い回して効率UP
ディレクティブは再利用しやすいので作れば作るほど財産に
81
まとめ
AngularJSの開発者が設計から実装、テスト、TIPSまでを紹介
AngularJSアプリケーション開発ガイド
http://www.oreilly.co.jp/books/9784873116679/
Web開発者にいま最も支持されているJavaScript MVCフレームワーク「AngularJS」の解説書。定型的な単純作業が繰り返されがちなWeb開発をよりシンプルで楽しいものにしようというのがAngularJSの試みです。本書ではまずAngularJSアプリケーションの構成要素を押さえた上で…
82
CM
82
LIGでは、一緒にAngularJSをやっていける
フロントエンドエンジニアを募集しています。
LIG
83
https://github.com/frontainer/angular-sample2
今回使用したサンプルはGithubにて公開しています
84
ご清聴ありがとうございました
今回使用したサンプルはGithubにて公開しています
https://github.com/frontainer/angular-sample2
top related