angular js はまりどころ

32
AngularJS はははははは 2014/9/18

Upload: ayumi-goto

Post on 19-Nov-2014

8.241 views

Category:

Technology


2 download

DESCRIPTION

AngularJSを業務で使って、はまったところをご紹介

TRANSCRIPT

Page 1: Angular js はまりどころ

AngularJSはまりどころ

2014/9/18

Page 2: Angular js はまりどころ

おめでとうございます。

Page 3: Angular js はまりどころ

自己紹介

 後藤歩 (@balmychan)

株式会社オロ

CakePHP, Rails, iOS, AndroidなどのWeb系、アプリ系の受託開発

 家族向け写真共有サービス nicori の立ち上げ、開発

 クラウド管理会計 ZAC Enterprise の開発

Page 4: Angular js はまりどころ

開発対象概要

開発対象Zac Enterprise(管理会計システム)

開発体制日本 3~4名海外(ベトナム) 1名※将来的には、 20~30名のエンジニアが開発する想定

技術構成AngularJS + TypeScript + .NET

Page 5: Angular js はまりどころ

AngularJSの構成

モジュールルーティング( ui.router)は使用せず、多言語対応のための angular-translateと、 directiveの共通コンポーネント化、 serviceのビジネスロジックの共通化、実装方式の共通化を図るために導入

ディレクティブ約 40個(テンプレート代わりや、新しいインターフェース)

サービス約 31個(サーバーサイドからのデータ取得や共通処理をまとめる)

フィルター約 10個

Page 6: Angular js はまりどころ

導入のきっかけ

コード量を減らしたい  →  data binding

UIを統一したい  →  directive

クライアント側の開発を、多人数開発で効率よく行いたい  →  フルスタック

検討を開始したのは、 2013年 10月ごろ

Page 7: Angular js はまりどころ

導入前の大きな懸念

パフォーマンスは問題ないか?

業務システムでは、大量のデータを同時に表示することは多い

一部画面では大量のリスト&計算処理など、クライアント側で処理する重い処理もある

※それまでは、 jQuery or pure JS

Page 8: Angular js はまりどころ

いくつか、はまった事例を紹介します

Page 9: Angular js はまりどころ

はまりの分類

はまるポイントは、大きく分類して下記があります

・スコープ・パフォーマンス・フィルター・バリデーション

・ DI・ディレクティブ・サービス

・セキュリティ・ angular-translate

・ altJS( TypeScript)・ 1.3への移行

Page 10: Angular js はまりどころ

パフォーマンス関連ではまったところ

Page 11: Angular js はまりどころ

パフォーマンス

これは有名な話ですが、 data-bindingは 2,000を超えると重くなるといわれている

特に、 angular-translateを使っていると、ラベル部分にも data-bindingが使われるので、固定ラベルにも data-bindingが使われ、数が多くなっていく

Page 12: Angular js はまりどころ

one-time binding

下記のように、 AngularJS 1.3から使用できる one-time bindingを使うと、余計な data-bindingを初回のみにすることができます。これによって大量のリストも表示が可能になりました。

::を付けるだけ

<span>{{ ::item.title }}</span><span ng-bind="item.title></span>

<span>{{ ::('message' | translate) }}</span>

※angular-translateで one-time bindingを使う場合は、 filterにする

Page 13: Angular js はまりどころ

one-time binding

filterの結果や、 ng-class、配列にも指定できるため、とにかくリアルタイムに変更しない項目はすべてこれを使用しています

<span>{{ ::(item.date | date | convert) }}</span>

<select>  <option ng-repeat="option in ::options">{{ ::option.title }}</option></select>

<div ng-class="::{ normal: type == 0, warning: type == 1, danger: type == 2 }"></div>

※1.2までは、 angular-onceや bindonceなどのモジュールを使用する

使えるところは使う

Page 14: Angular js はまりどころ

過剰な directive

大量の data-bindingにも関連しますが、リストで表示する HTMLに directiveを使い過ぎると、ページング処理に重くなります。また、 directiveの linkで重い処理(大量の DOM処理や複雑な計算)を行うと、ページング時に生成に時間がかかり、カクカクしだします。

可能な限り linkは軽くする、 directiveを使い過ぎない

※IEだとまた重い!!

<hoge-item>  <hoge-header>    <hoge-title>hoge</hoge-title>    <hoge-button>Button1</hoge-button>    <hoge-button>Button2</hoge-button>  </hoge-header></hoge-item>

Page 15: Angular js はまりどころ

スコープ関連ではまったところ

Page 16: Angular js はまりどころ

スコープ

プリミティブな型の data bindingscopeが true(継承)の directive内で inputにプリミティブな型を ng-modelで bindingすると、プロトタイプ継承の関係で想定とは違う動作をすることがある。

<body ng-controller="myController">  <div>{{ title | json }}</div>  <hoge-item>    <input type="text" ng-model="title">  </hoge-item></body>

<body ng-controller="myController">  <div>{{ obj.title | json }}</div>  <hoge-item>  <!-- このディレクティブは scope=true -->    <input type="text" ng-model="obj.title">  </hoge-item></body>

このパターンは titleが表示されない(myControllerで参照できない)

これは動作する

title = "hoge" ← Set

obj.title = "hoge" ← Set

objが無い!ので上位スコープを探索

このスコープからは参照できない

Page 17: Angular js はまりどころ

ディレクティブ関連ではまったところ

Page 18: Angular js はまりどころ

ng-transcludeの scopeは falseだが、内部で $scope.$new(false)が呼ばれているng-transcludeで使用する HTMLに、プリミティブな値を使うと思わぬところで動作しない※先ほどの scopeの問題と同様

このパターンは、 titleが表示されない

app.directive('hogeArea', function() {  return {    restrict: "EA",    template: "<div ng-transclude></div>",    transclude: true  };});

<hoge-area>  <input type="text" name="title" ng-model="title"></hoge-area><div>{{ title }}</div>

ディレクティブ

Page 19: Angular js はまりどころ

このように、 scopeを指定して transcludeすれば OK

app.directive('hogeItem', function() {  return {    restrict: "EA",    template: "<div ng-transclude></div>",    transclude: true,    link: function(scope, element, attrs, ctrl, transclude) {      if (transclude) {        transclude(scope, function(clone) {          element.find('div').append(clone);        }      }    }  };});

下記の issueで議論されていて、 <div ng-transclude="parent, sibling, child"></div>で指定できるようになるかも。https://github.com/angular/angular.js/issues/8609

Page 20: Angular js はまりどころ

input系のカスタムディレクティブを作るとき独自に入力系ディレクティブを作る際、 $viewValue, $modelValue, $renderをしっかり理解しないとはまる

たとえば、独自の selectタグのようなディレクティブを作りたい。まずは勢いとノリで実装してみるlink: function(scope, element, attrs, ngModelCtrl) {  // この inputは初期値を設定したいが、 ng-repeatで elementが未生成だから遅延させよう  $timeout(function() {    ngModelCtrl.$setViewValue('initializeValue');    select(newValue);  });  // ngModelが変わったら、選択肢を変更しよう  scope.$watch("ngModel", function(newValue) {    select(newValue);  });}

}無限に選択肢が変わる現象が発生・・・

ディレクティブ

Page 21: Angular js はまりどころ

ngModelCtrl.$viewValueは画面に表示するためのフォーマット後の値( ngModelの値が、 $formattersを全て通った後)ngModelCtrl.$modelValueは内部で保持する値(画面入力値が、 $parsersを全て通った後)ngModelCtrl.$renderは、 ng-model="hoge"に指定された値が変更された時に呼ばれる、 DOM操作関連の処理を入れる

link: function(scope, element, attrs, ngModelCtrl) {   ngModelCtrl.$render = function() {    var value = ngModelCtrl.$modelValue || ngModelCtrl.$viewValue;        // 値無い場合(初期値が無いのも含む)の処理    if (angular.isUndefined(value)) {      ngModelCtrl.$setViewValue("initialize Value");      ngModelCtrl.$render();      return;    }

    // ここで、 valueを元に、 DOMを操作する処理を入れる    element.find("div").addClass("hoge");  };}

Page 22: Angular js はまりどころ

良くあるフォームのリセットの処理をどうするか

link: function(scope, element, attrs, ngModelCtrl) {   var beforePristing;  scope.$watch(function() {    return beforePristine = ngModelCtrl.$pristine;  }, function() {    if (beforePristine == false && ngModelCtrl.$pristine) {      // リセット処理    }  };}

ディレクティブ

こんなこともしてましたがいまいち・・・。みなさんどうしてるんでしょうか、教えてください

Page 23: Angular js はまりどころ

DI関連ではまったところ

Page 24: Angular js はまりどころ

Circular Dependency errorが発生

DI

app.directive('hogeDirective', ['serviceA', 'serviceB', function(serviceA, serviceB) {  return {    link: function() {      serviceA.method();      serviceB.method();    }  }}]);

app.service('serviceA', ['serviceB', function(serviceB) {  this.method = function() {     alert("serviceA method!");   };}]);

app.service('serviceB', ['serviceA', function(serviceA) {  this.method = function() {     serviceA.method();  };});

あるディレクティブでサービス Aを使い、そこでサービス Bを使ってたらそいつがサービス Aを使ってた

Circular Dependency errorが発生するhttps://docs.angularjs.org/error/$injector/cdep?p0=serviceA%20%3C-%20serviceB%20%3C-%20serviceA%20%3C-%20hogeInputDirective

Page 25: Angular js はまりどころ

DI

app.directive('hogeDirective', ['serviceA', 'serviceB', function(serviceA, serviceB) {  link: function() {    serviceA.method();    serviceB.method();  }}]);

app.service('serviceA', ['serviceB', function(serviceB) {  this.method = function() {     alert("serviceA method!")   };}]);

app.service('serviceB', ['$injector', function($injector) {  this.method = function() {    $injector.invoke(['serviceA', function(serviceA) {      serviceA.method();    }]);  };}]);

下記で解決

$injectorサービスを使用する

Page 26: Angular js はまりどころ

angular-translate関連ではまったところ

Page 27: Angular js はまりどころ

angular-translate

app.controller("myController", ['$translate', function($translate) {  $translate("APP_TITLE").then(function(value) {    // タイトルを加工    scope.title = value;  });});

serviceとしても使用可能だが、 promiseが返ってくるので thenが入って見づらい

多言語対応用のモジュール。下記の形にしておくと、各言語用の辞書データから読みだして、表示される(動的に言語変更も簡単)

<div translate="APP_TITLE"></div>

instantを使うと値が返ってくる(読み出しが完了していることが前提)

app.controller("myController", ['$translate', function($translate) {  scope.title = $translate.instan("APP_TITLE");});

Page 28: Angular js はまりどころ

1.3への移行ではまったところ

Page 29: Angular js はまりどころ

AngularJS 1.3

isolated scope同士の directiveを同時に使用するとエラー

<custom-button translate="MESSAGE"></custom-button>

<custom-button>{{ ::('MESSAGE' | translate) }}</custom-button>

下記は、 custom-buttonと translateで isolated scopeを使っているエラーになる

今回は translateが filterでも使用できるので、そちらで回避

Page 30: Angular js はまりどころ

AngularJS 1.3

validationの実装方法が変更

ngModelCtrl.$parsers.unshift(function(viewValue) {  var valid = (value == "A") ? true : false;  ngModelCtrl.$setValidity("validA", valid);  return valid ? value : undefined;});

ngModelCtrl.$validators['validA'] = function(modelValue, viewValue) {  var value = modelValue || viewValue;  return value == "A";}

以前は下記のような形で validationを実装していた( "A"だけ許可する簡易なバリデーション)

1.3からは下記を使用する

この実装の場合、例えば validBを同時に使用していた場合、 validAを評価した時点でその次の parserへの値はundefinedになってしまうため、 validBのバリデーションがうまく動作しなかった

Page 31: Angular js はまりどころ

はまったときは

・「 AngularJSリファレンス」を読む・検索

・ angular.jsのソースを読む・ githubで issueも見る・英語に強くなる

Page 32: Angular js はまりどころ

ご清聴ありがとうございました。

今度別の機会で、 TypeScript関連について話します!