Go to list of users who liked
Share on X(Twitter)
Share on Facebook
More than 5 years have passed since last update.
その使い方はもう古いかも?AngularJS老化チェック(ディレクティブ篇)
こんにちは、らこです。AngularJSのバージョン1.5.0がリリースされましたね!
コードネームはennoblement-facilitation、ざっくり訳すなら「高尚化促進」でしょうか。コンポーネント志向の「高尚」なアプリケーション設計への移行をサポートするバージョンだということでしょう。
1.5.0ではcomponentの追加をメインに、Angular2へのスムーズな移行を行うための足がかりとなるアップデートです。いい機会なので、今の自分のAngularJSの使い方がどれくらい古いのか をチェックし、どのようにモダンにしていけばいいのかを知っておきましょう。
ちなみに、1.4までに関してはAngularJSモダンプラクティス - Qiita を参考にするとよいでしょう。
このモダンプラクティスに従ったコードになっていればcomponent()対応はあっという間のはずです。
到達目標 - component()
1.5.0から可能になるcomponent()によるカスタム要素の定義は次のように記述します。
<body><my-app></my-app><scriptsrc="app.js"></script></body>constapp=angular.module("app",[]);classMyAppCtrl{}app.component("myApp",{template:`<greeting name="'World'"></greeting>`,controller:MyAppCtrl});classGreetingCtrl{getupperName(){returnthis.name.toUpperCase();}}app.component("greeting",{bindings:{name:"="},template:`<h1>Hello {{$ctrl.upperName}}!</h1>`,controller:GreetingCtrl});angular.bootstrap(document.body,[app.name]);ポイントは次の3点です
- controllerにES6 Classを渡している
$scopeを使っていない- componentのテンプレート内で
$ctrlを使っている
ES6での記述は機能とは直接関係はありませんが、Angular2を見据えたコードにするならばクラスにしておいて損はありません。フィールドの初期化もコンストラクタがあるので明確です。
データバインディングに$scopeを使っていないことも重要です。componentを使うと、デフォルトでcontrollerAs: "$ctrl"の状態になります。コンポーネント内の閉じたスコープが自動的に与えられ、コントローラのフィールドや関数に直接バインドすることができます。
なお、component()になってもdirective()と同じく定義するカスタム要素の名前はキャメルケースでmyApp のようにします。そしてHTML側ではハイフンケースでmy-app のように宣言します。
モダンなAngularJS - directive + scope + bTC + controllerAs
1.4まではcomponent()がないので、directive()でしかカスタム要素は定義できません。しかしscopeプロパティとbindToController (bTC)、controllerAsを使うことで同じようなコンポーネントを定義することができます。
functionMyAppCtrl(){}app.directive("myApp",()=>{return{restrict:"E",scope:{},template:`<greeting name="'World'"></greeting>`,controller:MyAppCtrl};});functionGreetingCtrl(){this.upperName=()=>this.name.toUpperCase();}app.directive("greeting",()=>{return{restrict:"E",scope:{},bindToController:{name:"="},template:`<h1>Hello {{$ctrl.upperName()}}!</h1>`,controller:GreetingCtrl,controllerAs:"$ctrl"};});実は1.5のcomponent()はこれと同じようなdirective()の呼び出しをラップしているだけです。また、bTCにオブジェクトを渡せるようになったのは1.4からです。
ナウいAngularJS - directive + scope + controllerAs
1.3まではbTCにオブジェクトが渡せないのでscopeと併用しないといけません。
functionMyAppCtrl(){}app.directive("myApp",function(){return{restrict:"E",scope:{},template:"<greeting name=\"'World'\"></greeting>",controller:MyAppCtrl};});functionGreetingCtrl(){this.upperName=function(){returnthis.name.toUpperCase();}}app.directive("greeting",function(){return{restrict:"E",scope:{name:"="},bindToController:true,template:"<h1>Hello {{$ctrl.upperName()}}!</h1>",controller:GreetingCtrl,controllerAs:"$ctrl"};});1.4のコードと殆ど変わらないですね。ここまでできていればほとんどコンポーネント化できていると言えるでしょう
古い - directive + scope + controllerAs
1.2以前はbTCがないので、親スコープからのデータバインディングは$scopeを介さないと受け取ることができません。しかし、controllerAsを使っているのでまだ自身のプロパティに関してはthisで扱えます。
functionMyAppCtrl(){}app.directive("myApp",function(){return{restrict:"E",scope:{},template:"<greeting name=\"'World'\"></greeting>",controller:MyAppCtrl};});functionGreetingCtrl($scope){this.upperName=function(){return$scope.name.toUpperCase();};}app.directive("greeting",function(){return{restrict:"E",scope:{name:"="},template:"<h1>Hello {{$ctrl.upperName()}}!</h1>",controller:GreetingCtrl,controllerAs:"$ctrl"};});ここまでは比較的すんなりcomponent()に移行しやすい(スコープの考え方がだいたい合ってる)コードだと思います。
時代遅れ - directive + scope + controller
controllerAsが導入されていないのでテンプレート中で変数名が直接登場し、コントローラのthisがスコープで参照されなくなりました。ただしscopeを使っていてisolated scopeがあります。また、コントローラでビューとロジックの分離がギリギリ出来ている状態です。
functionMyAppCtrl(){}app.directive("myApp",function(){return{restrict:"E",scope:{},template:"<greeting name=\"'World'\"></greeting>",controller:MyAppCtrl};});functionGreetingCtrl($scope){$scope.upperName=function(){return$scope.name.toUpperCase();};}app.directive("greeting",function(){return{restrict:"E",scope:{name:"="},template:"<h1>Hello {{upperName()}}!</h1>",controller:GreetingCtrl,};});このコードは1.5関係なく、早めにcontrollerAsを使ったスタイルに直すべきです。controllerAsについての議論は2014年あたりには決着がついており、
この辺りを読めばcontrollerAsを使うことによるメリット、使わないことによるデメリットがわかるはずです。
化石 - directive + link
directiveに紐付いたスコープが存在しないケースです。link関数やcompile関数は完全にcomponent()と互換性があるわけではないですが、カスタム要素の場合はほとんどのケースでlinkもcompileも必要ないでしょう。directiveとcontrollerでロジックの分離もできていないのでとてもメンテ性の低いコードです。
app.directive("myApp",function(){return{restrict:"E",template:"<greeting name=\"World\"></greeting>"};});app.directive("greeting",function(){return{restrict:"E",template:"<h1>Hello {{upperName()}}!</h1>",link:function(scope,element,attrs){scope.name=attrs.name;scope.upperName=function(){returnscope.name.toUpperCase();};}};});論外 - html + ng-controller
カスタム要素によるコンポーネント化が全くなされていないコードです。
<divid="myApp"ng-controller="MyAppCtrl"><divid="greeting"ng-controller="GreetingCtrl"><h1>Hello {{upperName()}}!</h1></div></div>app.controller("MyAppCtrl",["$scope",function($scope){$scope.name="World";}]);app.controller("GreetingCtrl",["$scope",function($scope){$scope.upperName=function(){return$scope.name.toUpperCase();};}]);綺麗にAngularJSを使うつもりがあればng-controllerは無くしましょう。せめてasを使って親スコープからの暗黙の継承を防ぎましょう。
これからやるべきこと
自分のAngularJSの現在地がなんとなくわかったでしょうか。それぞれの段階ごとに、適切な順序でコンポーネント志向なAngularJSにしていきましょう。
html + ng-controllerが残ってる場合
- まずは
ng-controller="SomeCtrl as some"にする ng-controllerのある要素をdirectiveに置き換える
すべてのコントローラをcontrollerAsにするのが最初の最優先課題です。
それができればあとは一番外側のコントローラから順番にdirectiveにしていきましょう。
directiveがcontrollerを使っていない場合
- directiveの
linkとcompileはコントローラで代用 - directiveの
scopeでスコープを分離する
次に最優先すべきはscopeを使ってdirectiveごとにスコープを分離することです。
bTCを使っていない場合
- AngularJS1.4以降に上げる
2.scopeに渡していたオブジェクトをbTCに渡し、scopeを空オブジェクトにする
次は$scopeを使わずにすむようにdirectiveのスコープをコントローラにバインドしましょう。
1.4でbTCを使っていた場合
- AngularJS 1.5に上げる
directive()の代わりにcomponent()を使う- できればコントローラはES6 Classにする
完成です。ここまでくればAngular2も今いる場所の延長線上にあることがわかってくると思います。
まとめ
1.2から1.3、1.4とコンポーネント志向なdirectiveを作るための機能が少しずつ増えてきており、それらのベストプラクティスとして生まれたのがcomponent()です。
1.5以降はもうAngularJSとしての新しい機能は増えず、Angular2への移行サポートがメインになることがわかっています。component()対応後にちゃぶ台を返される心配はないので、安心して1.5に向けたマイグレーションを行いましょう。
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme
