AngularJS

メモ書き。何か発見するたびに更新してます。

合っているかどうかは分からない話

下記は古い(いつから?)AngularJSの記述方法です。
ng-controllerはwindowオブジェクト直下の関数を見なくなりました。

var app = angular.module('app', []);
app.controller('con', function($scope){});

ng-controllerの名前の指定は、angular.module().controllerの第一引数で指定します。

view

ng-model属性

コントローラーの$scopeのオブジェクトと一致する。

<div ng-controller="con">
    <input type="text" ng-model="a.b" />
    <input type="text" ng-model="c" />
</div>
<script>
    function con($scope){
       typeof $scope.a; //object
       $scope.c = 10; //inputに10が表示される
    }
</script>

ng-modelに記載されていれば自動で$scopeにオブジェクトが作成される。
ng-controllerが設定されていないタグの外でng-modelを指定すると、グローバルな領域にデータを置いてくれる。

ng-repeat属性

書いたタグ自体が増殖する

<div ng-repeat="data in array"></div>

<div ng-repeat="data in array"></div>
<div ng-repeat="data in array"></div>
<div ng-repeat="data in array"></div>

みたいに、array.lengthの数だけ増える。
当然、子要素も定義通り増える。
{{$index}}を使うと何番目の要素なのかが取得可能。また、ng-repeat内にng-controllerを指定した場合、$scope.$indexでも参照可能。


ng-repeatにng-clickなどでイベントを指定すると、thisには「data in array」のdata部分の値を参照する事が出来る。
「this.data」で1つのデータにアクセス可能。

ng-init属性

データを生成できる。

<div ng-controller="con">
    <input type="text" ng-init="test=100" ng-model="test" />
</div>
<script>
    function con($scope){
       $scope.test; //100
    }
</script>

「Unexpected end of expression」を出す場合、ng-init=""という書式をng-init=''に変更する事。
多分そういう文字列を囲む記号に不整合がある時に出るエラーだと思われる。

ng-click属性

クリック時に処理を実行する事が出来る。
ng-click内にバインディング({{}}の事)を行う事は出来ず、"{{hoge}}"というような文字列として解釈される(ver1.2.13時点)
そのような場合はコントローラ内で処理出来るようにする事で回避は可能。
この関数にES6的なアロー関数を使うとthisが拘束されてAngularJSが持っている情報を取得出来なくなるので注意。

<script>
    function controller($scope){
        $scope.click = function(){
            alert("クリックされました");
        }
    }
</script>
<div ng-controller="controller">
    <button ng-click="click()">押してみてね</button>
    <a href="" ng-click="click()">aタグでも使用可能</a>
</div>
img.src
<img src="{url}">

は、srcが「{url}」として認識されてしまう。
こういう場合は

<img ng-src="{url}">

とする事

Angular.jsでcoderwallのバッチ取得してみる - yaakaito.org

input[type=hidden]

なぜかng-modelを指定しても値が更新されない……

<input type="hidden" value="{{model}}" />

とすると更新が行えるようになる。

templateにif文を書きたい
{{条件式 && trueの場合の値}}

{{}}内は四則演算と比較が可能なので、if文はこのようにする。&&や||は比較なので使用可能で、ここらへんのテクニックはjsのそれと同じ。
文字列を表示させたい場合は、「trueの場合の値」の箇所は''か""で囲う。HTML自身の'',""と被らないよう配慮する事。

テンプレート内でさらにテンプレートを流し込みたい

ng-includeを使う

<div ng-include="'hoge.html'"></div>

div.ng-includeで指定されたテンプレートの中身をdivの子要素として渡す。

hoge.htmlが

<span><span>

だった場合、上記のdivは

<div ng-include="'hoge.html'">
    <span><span>
</div>

となる。


オプションのonloadは、ng-controllerで指定した$scopeに追加した関数を呼び出す事が可能。


参考:Loading...

select
<select ng-model="tests" ng-options="test.value as test.show for test in tests" ></select>

<script>
    function con($scope){
        $scope.tests = [{
            value:1,
            show:'A'
        },
        {
            value:2,
            show:'B'
        }];
    }
</script>

value値 as optionの中で表示する値 for 要素 in 配列」でoptionを生成してくれる。
オブジェクトでもoptionの作成は可能。


参考:AngularJS

関数の戻り値を表示したい({{functon()}})

コントローラーで入れた関数を使用できる

<div ng-controller="con">
    {{number()}} <!-- 100が表示される -->
</div>
<script>
    function con($scope){
        $scope.number = function(){
            return 100;
        };
    }
</script>
ng-show / ng-hide

ng-showで指定した値がtrueの時、要素を表示する。
ng-hideは逆に、trueの時、要素を非表示にする。
これらはCSS的に非表示にしているだけで、DOMとしては保持される。
ng-csp指定時はangular-csp.cssを読み込まないと正しく動かない場合があるので注意。

<div ng-contoroller="test">
    <!-- $scope.isShowがtrueなので表示される -->
    <div ng-show="isShow">表示</div>
</div>
<script>
    function test($scope){
        $scope.isShow = true;
    }
</script>
ng-if

ng-show / ng-hideがDOMを維持するのに対して、ng-ifは

  • trueの時はDOMを生成し表示
  • falseの時はDOMを削除し非表示

という挙動をする。
DOM削除を前提にng-show / ng-hideのように振る舞わせたい場合、ng-if内で比較演算子を使用すると良い。
ng-ifを持つ要素の子にng-modelが入っている場合、データバインディングが正しく行えない場合がある。

<div ng-contoroller="test">
    <!-- $scope.isShowがtrueなので表示される -->
    <div ng-if="isShow">表示</div>
    <!-- 比較の結果がfalseなので、この部分はDOMが削除される。 -->
    <div ng-if="isShow == false">非表示になる</div>
</div>
<script>
    function test($scope){
        $scope.isShow = true;
    }
</script>
ng-readonly

ng-readonlyで指定した値がtrueの時、inputを入力不可にする。

<div ng-contoroller="test">
    <!-- $scope.isReadonlyがtrueなので入力不可になる -->
    <input ng-readonly="isReadonly" />
</div>
<script>
    function test($scope){
        $scope.isReadonly = true;
    }
</script>

参考:AngularJS

ng-disabled

selectタグなどを操作不可にする。

<div ng-contoroller="test">
    <!-- $scope.isReadonlyがtrueなので入力不可になる -->
    <input ng-disabled="flg" />
</div>
<script>
    function test($scope){
        $scope.flg = true;
    }
</script>

参考:http://docs-angularjs-org-dev.appspot.com/api/ng.directive:ngDisabled

ng-class

特定のCSSを動的に適用したい時に使用する。

<style type="text/css">
    .dynamic {}
</style>
<div ng-contoroller="test">
    <div ng-click="this.css = 'dynamic'" ng-class="this.css">表示</div>
</div>

ng-classにCSSのクラス名を文字列として入れると、それを適用してくれる。
ng-class内で"" + ""などで文字列演算を行なってCSSの適用を切り替える事も可能。
ng-repeat等で動的にHTMLが作られている場合は、thisを使うと特定のデータが取得出来るので、そこにCSS名を入れるとよい。データに表示に関するものを混ぜるのは少々気が引けるかもしれないけど…

<style type="text/css">
    .dynamic {
        color:red;
    }
</style>
<div ng-app>
    <div ng-click="is = !is" ng-class="{dynamic:is}">色が変わるよ</div>
</div>

{CSS名:boolean値}で表記する事も出来る。
boolean値がtrueになった場合、そのCSSが適用されるらしい。

HTMLをエスケープせずに解釈して欲しい
<div ng-controller="con">
    <div ng-bind-html="unsafe">
        <!-- この中に$scope.unsafeのHTMLがエスケープなしで表示される -->
    </div>
</div>
<script>
    function con($scope, $sce){
        $scope.unsafe = "<div></div>";
        $scope.unsafe = $sce.trustAsHtml($scope.unsafe);
    }
</script>

AngularJSはHTMLにバインドする時({{}}による表示)、自動でエスケープを行います。
何らかの形ですでにエスケープ済みな事が保証出来る場合、$sce.trustAsHtmlを使ってエスケープしない事を明示する事が出来ます。
参考:angularjs - How do you use $sce.trustAsHtml(string) to replicate ng-bind-html-unsafe in Angular 1.2+ - Stack Overflow

<div ng-controller="con">
    <div ng-bind-html-unsafe="unsafe">
        <!-- この中に$scope.unsafeのHTMLがエスケープなしで表示される -->
    </div>
</div>
<script>
    function con($scope){
        $scope.unsafe = "<div></div>"
    }
</script>

この方法はver1.2.0で廃止になりました。
ng-bind-html-unsafeの中には$scopeで設定した名前のみを入れる

時刻のフォーマットを変更したい
<div ng-app>
    <input type="text" ng-model="date_input">
    {{date_input | date : 'yyyy/MM/dd'}}
</div>

「model名 | date : '日付のフォーマット'」で日付を好きなフォーマットに変更出来る。
参考:AngularJS

Controller

$scope.$apply()

ng-clickの実行以外で$scopeのデータをいじった時、そのいじったデータをViewに反映させるためのメソッド。
ng-clickでこれを実行するとエラーになる。ng-clickやng-changのようなAngularJS配下のイベントリスナーで行われた$scopeの変更は、自動で反映されるため、呼ぶ必要もない。

filterでは出来ない複雑な検索を行いたい
<html ng-app="app">
    <input ng-model="query">
    <div ng-repeat="data in array | search:query" />
</html>
//angular.module
//    ngApp:string    ng-appで指定された名前
//    ?:array              不明
//angular.module().filter
//    name:string         絞込みを行う時の名前。ng-repeatのsearchと同じ名前にする
//    create:function    絞込みを行う時に実行される関数を生成する関数
angular.module('app',[]).filter('search', function(){
    //絞込みを行う関数
    //    array:array    ng-repeatのarray
    //    query:string  ng-repeatのquery
    return function(data, query){

        //ここで全てのデータを見て検索結果をreturnする

        return data;
    };
});

angular.moduleはjQueryの$.readyなどで囲わず直接書く。
filterの第二引数が返す関数で、全てのデータを元に欲しいデータのみを選別し、戻り値として返す形式になっている。
また、filterメソッドの第二引数には$filterが使えるため、通常の検索に近い操作をしたい場合は$filter("filter")の戻り値を使うと良い。


参考:AngularJSのFilterが2重評価されてる? - tyn-iMarketの技術メモ

$document
<script>
    function con($scope, $document){
        $document[0] == document; //true
    }
</script>

DOM構造を取得する事が出来る。
$documentはjQuery(document)と同じものだと思われる。
inputタグを使っている場合、$document[0].activeElementにはinputタグ自身が入る。

$timeout
<script>
    function con($scope, $timeout){
        $timeout(function(){
            //ここの処理が1秒後に実行される。
            
            //こうすると1秒ごとに実行するという事も可能
            $timeout(this.callee, 1000);
        }, 1000);
    }
</script>

setTimeout同様の動きをする。
setTimeoutとの違いは、この関数内で$scopeの値を変更すると、自動でViewに反映される事。

$http
<script>
    function con($scope, $http){
        $http({
            method:'post',
            url:'',
            data:$.param(data),
            headers: {'Content-Type': 'application/x-www-form-urlencoded'}
        });
    }
</script>

ajax的な通信を行う関数。
postで送信する場合、dataを$.paramで変換し、content-typeにapplication/x-www-form-urlencodedを入れないと駄目なようだ。


参考:rest - Pass array of data from Angular $http POST - Stack Overflow

別のng-controllerのデータを参照したり、メソッドを実行したりしたい

方法は2通り


・ng-controllerを入れ子にする
入れ子にすると、親のng-cotrollerで設定した$scopeを子の$scopeのプロトタイプとして設定される。

<div ng-controller="a">
    <div ng-cotroller="b" />
</div>
<script>
    function a($scope){
        $scope.value = 20;
    }

    function b($scope){
        $scope.value; //20。親の$scopeで20を設定したため
    }
</script>


・ng-controller属性を設定したタグを取得し、scope()の戻り値を使う。scope()の戻り値は$scopeと同じものがくる…はず。

var scope = angular.element(ng-controllerを設定したDOM).scope()

filterやorderByをコントローラ内でやりたい

<script>
    function con($scope, $filter){
        const filter = $filter("filter");
        
        $scope.f = function(){
            var data = filter([], "");
        };
    }
</script>

$filterの第一引数でビルトイン関数を呼び出す事ができる。
ビルトイン関数は第一引数にソートやフィルタリングを行いたい配列、第二引数以降は各種filterが必要とする値が入る。
戻り値にはソートやフィルタ済みのデータが入っている。
注意点として、ビルトイン関数の第一引数を破壊するわけではないので、HTMLの値を変えたい場合はビルトイン関数の戻り値を代入し直す必要がある。

DOMを直接操作したい

jQueryで頑張る or directiveを使う。

<html ng-app='app_name'>
    <body a-a></body>
</html>
angular.module('app_name', []).directive('aA', function(){
    return function(scope, iElement, iAttrs){
        scope.hoge = function(){
            
        };
		
        scope.flag = true;
    };
});
  1. module関数の第一引数は、ng-appの値と合わせる。
  2. directive関数の第一引数で指定した文字列を属性としてHTMLに付ける。aAとキャメルケースで指定した場合、「-」のスネークケースが属性名になる。

scope変数が$scopeに当たる
iElementがDOMに当たる(DOM操作はjQueryなどを使っても良い)
既存のcontrollerと共存させたい場合、属性と同じタグにcontrollerを置けば$scopeでdirectiveで反映させた値を使用する事が出来る。


directiveはng-clickなどを独自に作成したい時に使うものっぽい。
参考:AngularJSで動的に生成されるDOMに対して、jQueryプラグインの効果を適用する - えいのうにっき
参考:AngularJSのDirectiveを理解する. - Qiita

なんか動かないんだけど

なんか動かないんだけど

ng-appを設定しましたか?

<html ng-app>
イベントが実行されないんだけど

イベントリスナー関係の属性は「関数名()」という書式になっていますか?
()が必要です。

Chrome拡張で組むとCode generation from strings disallowed for this contextというエラーが出るんだけど

ng-cspというものをくっつければOK

<html ng-app ng-csp>
Firefoxでng-submitイベントが発火しないだけど

alertが入ってると出来ないようです。

ng-submitが発火しないんだけど

formの中にformを入れると発火しません。

$scope書き換えたのにHTMLが書き換わらないんだけど

$scopeを書き換えた後に$scope.$apply()を足してみてください。

$filterとかがデバッガからだと見えないんだけど

デバッガを使うと見えない場合があります。
console.logで確認すると、ちゃんと存在している可能性が高いです。

「Error: No controller: ngModel」が出るんだけど

input要素にng-modelを指定せずにng-changeを設定すると発生するようです。


駄目な例

<!-- Error: No controller: ngModelが発生する -->
<div ng-controller="a">
    <input type='text' ng-change='hoge()'>
</div>
<script>
    function a($scope){
        $scope.hoge = function(){};
    }
</script>


良い例

<!-- 正常に動く -->
<div ng-controller="a">
    <input type='text' ng-model='mo' ng-change='hoge()'>
</div>
<script>
    function a($scope){
        $scope.hoge = function(){};
    }
</script>
$scopeとか$httpとかの名前を変えたら動かないんだけど

コントローラで使われるこれらの引数は名前が固定です。名前を元に引数の中身を決定しているので、第一引数に必ず$scopeがあるわけではありません。
逆にいうと、$httpを第一引数名にすると、$httpで使用出来る関数が第一引数で渡されます。

Chrome,Safariだと頭文字が残る
<style>
    .test:first-letter{
        text-decoration: underline;
    }
</style>

<input ng-model="test"/>

<div class='test'>{{test}}</div>

first-letterが残る不具合らしきものがあります。
対処するには{{}}の部分をng-bindに置き換えると予想通りの結果が得られると思います。