AngularJSで動的にフィルタリングしてみる(今回ご紹介するのは二つ)

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
僕はあまりコーヒーは飲まないですが

この記事を三行にまとめると

ng-modelを使ってフィルタリングする
ng-clickを使ってフィルタリングする
いやーやっぱりjQueryは便利だわー
AngularJSにはfilterって機能があって、これを使うとデータのフィルタリングが容易に行なえます。

<div ng-controller="ctrlRead">
  <p ng-repeat="item in items | filter:'abc'"></p>
</div>

何かいろいろ端折って書くと、こんな感じ。

これだと、データの文字列の中に『abc』ってのを含むデータだけが絞り込めるわけですが、ここをもうちょい動的にやりたい、そんな時。

やり方はいくつかあると思いますが、今回は二通りのやり方でやってみましょう。



ng-modelを使う

AngularJSには『ng-model』っていうのがあって、えーと……言葉で説明すると何て言えば良いんだろうね。AngularJSはMVCフレームワークだから、そのモデルに相当する部分だと思うんだけど、まあとにかくそういうのがあるんですよ。

このmg-modelを使って、フィルタリングを行なうことができる。実際に簡単なコードを書いてみよう。

<div ng-controller="ctrlRead">
  <p ng-repeat="item in items | filter:value"></p>
  <input ng-model="value" />
</div>

<script>
function ctrlRead($scope) {
  $scope.items = [
    {"id":1,"name":"マイケル","age":29},
    {"id":2,"name":"ジョージ","age":18},
    {"id":3,"name":"ナンシー","age":24}
  ];
};
</script>

ng-model属性に書いた値をfilterのところにセットすると、この場合で言えばテキストボックスの中にある値でフィルタリングできる。

実際にこの動きを実装したサンプルページがこちらっす。試しにテキストボックスに1とか2とか入力してみると、表示項目数が変わる。もちろんマルチバイト文字でもオッケーよ。『マイケル』とかね。

まあこの場合、ID、名前、年齢の全ての値がフィルタリングの対象になるから、『2』とか入力した場合は項目が何も減らないけどね。これを年齢だけ対象にしたい場合には、filterのところをちょびっと書き換える必要がある。

<div ng-controller="ctrlRead">
  <p ng-repeat="item in items | filter:{age:value}"></p>
  <input ng-model="value" />
</div>

<script>
function ctrlRead($scope) {
  $scope.items = [
    {"id":1,"name":"マイケル","age":29},
    {"id":2,"name":"ジョージ","age":18},
    {"id":3,"name":"ナンシー","age":24}
  ];
};
</script>

これならageだけが対象になるので、『2』って入力するとマイケルとナンシーだけが絞り込まれる。

ちなみにAngularJSのフィルタリングは完全一致ではないので、上記のデータであれば、例えば『8』って入力したらジョージだけが絞り込まれるし、『9』って入力したらマイケルだけが絞り込まれる。

テキストボックスに限らず、ラジオボタンとかチェックボックスとか使っても、同じようなことはできます。何らかの入力値が取れるものであればって感じですかね。



ng-clickを使う

例えばリンクをクリックしたときにフィルタリングが発動するような場合。こういう場合は、『ng-click』ってのを使う。いや、まあ使わなくても良いから、ng-clickを使っても実装できるって言い方が良いかな。

ng-clickっていうのは、onclickみたいなもんです。クリックしたときに発動するものです。

さっきと同じ要領で、リンクをクリックしたら、年齢に2が含まれてる人(今回の場合は20代の二人)が絞り込まれるようにしてみましょう。

<div ng-controller="ctrlRead">
  <p ng-repeat="item in items | filter:{age:link}"></p>
  <a href="javascript:void(0)" ng-click="link=2">年齢が20代の人</a>
</div>

<script>
function ctrlRead($scope) {
  $scope.items = [
    {"id":1,"name":"マイケル","age":29},
    {"id":2,"name":"ジョージ","age":18},
    {"id":3,"name":"ナンシー","age":24}
  ];
};
</script>

ng-clickの中で、linkっていう変数に2をセットしてますね。動きとしては、テキストボックスに2を入力したときと同じになります。これもサンプルページに実際の動きを用意してみたので、参考になれば。サンプルページの方ではリセットボタンも用意してますけど、やってることは一緒ですんで。linkって変数に空の値を入れてるだけです。



ng-clickはonclickと同じ感じなんで、関数を呼び出すこともできます。

<div ng-controller="ctrlRead">
  <p ng-repeat="item in items | filter:{age:link}"></p>
  <a href="javascript:void(0)" ng-click="setValue(2)">年齢が20代の人</a>
</div>

<script>
function ctrlRead($scope) {
  $scope.items = [
    {"id":1,"name":"マイケル","age":29},
    {"id":2,"name":"ジョージ","age":18},
    {"id":3,"name":"ナンシー","age":24}
  ];

  $scope.setValue = function(value) {
    $scope.link = value;
  }
};
</script>

ng-clickで関数を呼び出す場合、その関数はコントローラーの中に書くことになります。$scope.{関数名}みたいな感じで。フィルタリングの変数も、もしコントローラーの中で定義するなら、$scope.{変数名}になります。PHPとかのオブジェクトでいうところの、$thisみたいなもんですかね、たぶん。



おまけ談を少々

僕は最初ng-clickの方のやり方を知らなくて、というか、コントローラーの中で変数を作ってフィルタリングできるってところが分からなくて、リンクをクリックしたときのフィルタリングは、クリックしたときの値をテキストボックスに入力して、ng-modelを使ってやってたんですよ。テキストボックス自体はCSSでvisibilityをhiddenにして、こっそり切り替えてた。てゆーか、実際にそうやって実装している人がいたから、そういうやり方しかできないのかなーってね。

コードでいうと、こんな感じ。

<div ng-controller="ctrlRead">
  <p ng-repeat="item in items | filter:{age:value}"></p>
  <input style="visibility:hidden" id="text box" ng-model="value" />
</div>

<a href="javascript:void(0)" id="2">link</a>

<script>
$(function(){
  $('a').click(function(){
    $('#textbox').value($(this).attr('id'));
    $('#textbox').trigger('input');
  });
})
</script>

いろいろざっくりだけど、jQueryを使ってクリックしたリンクのIDの値をテキストボックスに入れて、トリガーを発動させてるわけです。

結果的には同じだから良いっちゃ良いんだけど、美しくはないかな〜って。あとこっちだと、ブラウザによって挙動が変わったりして、わりと大変だった。『trigger(‘input’)』が効かなかったりとかね。これだったら、ng-clickを使った方がお手軽だーね。

ただ一つ問題があるのは、jQueryでいうところの$(this)が取れないのよね、AngularJSの方は。調べ切れなかっただけかもしれないけど、僕が調べた限りでは、それに該当するものがない。だからリンクのIDとかを摂りたかったら、『event.target.id』とかで取得することになるんだけど、でもこれって、リンクの中に別のHTMLタグが入ってると、そっちのidが取れちゃうじゃん?

<a href="javascript:void(0)" id="link">
  <img src="sample.gif" id="image">
</a>

こんな感じになってて、リンクのついた画像をクリックすると、取得できるidって『image』になっちゃうのよね。この辺もブラウザによって若干違ったような気がするんだけど……とにかく、いつでもlinkが取れないのでは困っちゃう。これが夏祭りの射的だったら、狙ったものが取れなくても「あちゃー」の一言で済むけどね。プログラムじゃあそうは行かんのよ。

この辺は、JQueryの方が便利かなー。



だったらng-clickを使わずにjQueryでonclick属性をつければええやんって話になるんだけど、いや確かにその通りなんだけど、時と場合によっては、それだとあまりよろしくないこともある。

<div ng-controller="ctrlRead">
  <p ng-repeat="item in items | filter:{age:value}">
    <a href="javascript:void(0)" id="{{item.id}}">item.name</a>
  </p>
</div>

<script>
$(function(){
  $('a').click(function(){
     alert($(this).attr('id'));
  });
});
</script>

例えばだけど、こんな感じで、ng-repeatの中にリンクがある場合。フィルタリングされるたびに、リンクが表れたり消えたりすることになりますよね。

これだと、一度絞り込まれて消えた後にもう一度表示されたとき、onclick属性が外れちゃってるのよ。ってことは、フィルタリングのたびにonclick属性をつけ直すような処理を、どっかに入れとかなきゃいけない。

まあ、jQueryの『on』ってやつを使って、後から生成された要素にもonclick属性をつける方法は、あるんですけどね。

$('body').on('click', 'a', function(){
  alert($(this).attr('id'));
});

こんな感じで、body要素にa要素のクリックイベントをつけるって書き方をすると、後から生成されたリンクとかにも、onclick属性がついた状態になる。

実はこの書き方があるって知ったの、たった今なんですけどね(・ω<)

今この記事を書いてて、そういやそんなやり方ってできねーのかなーって思って調べたら、あったわ。もちろんこれはタグだけを指定するものじゃないから、idとかclassとかでもオッケー。

$('body').on('click', '#id', function(){
  alert($(this).attr('id'));
});

$('body').on('click', '.class', function(){
  alert($(this).attr('id'));
});

みたいにね。






何か途中からAngularJSの話じゃなくなっちゃいましたけど、これでよく分かったと思います。

jQueryはやっぱり便利だってことが……ね(>▽<)♪
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください