Angular のソート フィルターをアップグレードする方法
はじめに
AngularJS の初期の提供で最も便利な機能の 1 つは、テンプレート変数とフィルターのみを使用してページ上のデータをフィルタリングおよびソートできることでした。 しかし今日、多くのフロントエンド開発者は一方向のデータ バインディングを好み、それらの orderBy
および filter
フィルターは Angular の出現により過去のものとなっています。 (注意: この記事では、1.x を参照するために「AngularJS」を、2 以上を参照するために「Angular」だけを使用します。)
しかし、どのようにして同じ効果を達成することになっているのでしょうか。 ngUpgrade プロジェクトを見て、これを行う方法を学びましょう!
Step 1 – Pull Code from Git
新しく書き直されたコンポーネントのテンプレートを更新するステップを説明します。 次に、ソートとフィルタリングを追加して、AngularJS で持っていた機能をすべて復元します。
始めるには、これから使用するサンプル プロジェクトをクローンします (public
と server
の両方のフォルダーで npm install
を実行することを忘れないでください)。
git checkout 9daf9ab1e21dc5b20d15330e202f158b4c065bc3
このサンプル プロジェクトは AngularJS 1.6 と Angular 4 の両方を使用する ngUpgrade のハイブリッド プロジェクトです。 Express API が動作し、開発および本番用の Webpack がビルドされています。 自由に探索し、フォークして、自分のプロジェクトでパターンを使用してください。 このプロジェクトのAngular 5を使用したバージョンを見たい場合は、このレポをチェックしてください。 このチュートリアルの目的では、2 つのバージョン間の違いは重要ではありません (マイナーなものは指摘します)。
ステップ 2 – AngularJS 構文の置き換え
アプリケーションのこの段階で、注文コンポーネントは Angular で書き直され、すべての依存関係が注入および解決されています。 しかし、アプリケーションを実行しようとすると、テンプレートに問題があることを示すエラーがコンソールに表示されます。 最初に修正する必要があるのはこの点です。 受注テンプレートのAngularJS構文(orders/orders.html
)を置き換えて、ルートをロードし、ページに受注を表示できるようにします。 次にフィルタリングとソートを修正します。
最初に行う必要があるのは、このテンプレート内の $ctrl
のインスタンスをすべて削除することです。 これらはもはやAngularでは必要ありません。 検索と置換で $ctrl.
を探し(ドットに注意)、何もない状態に置き換えることができます。
次に、13 行目のボタンの data-ng-click
を置換しましょう。 Angularでは、ng-click
の代わりにclick
イベントを使用し、括弧でイベントであることを示します。 大括弧は入力を、括弧は出力またはイベントを表します。
<button type="button" (click)="goToCreateOrder()" class="btn btn-info">Create Order</button>
私たちはここで、クリック イベントで、注文コンポーネントの goToCreateOrder
関数を実行することを述べています。 注文を読み込む div
全体 (行 17 以降) をコメントアウトします。 アプリケーションを実行するには、ターミナルを開き、次のコマンドを実行します:
cd servernpm start
これで Express サーバーが起動します。 Webpack 開発サーバーを実行するには、別のターミナルを開いて次のコマンドを実行します:
cd publicnpm run dev
(このチュートリアルの残りの期間、これらのプロセスを実行し続けることができます。)
アプリケーションが再びロードされていることを確認できます。 受注ルートに移動すると、受注コンポーネントが正しく表示されていることがわかります。
また、注文の作成ボタンをクリックすると、注文の作成ルートとフォームに正しく移動することができます。 div
のコメントを解除します (このアプリはまた壊れてしまいます)。
残りのインスタンス data-ng-click
をすべて (click)
イベント ハンドラで置き換えてみましょう。 検索」 & 「置換」を使用するか、エディターのショートカットですべての出現箇所を選択します (Windows 版 VS Code では Ctrl+Shift+L)。
次に、data-ng-show
を *ngIf
にすべて置換します。 実はAngularにはng-show
に直接相当するものはありませんが、それでもかまいません。 なぜなら、そうすれば、単に要素を隠したり表示したりするのではなく、実際にDOMに要素を追加したり削除したりすることになるからです。 つまり、data-ng-show
を見つけて *ngIf
に置き換えるだけでよいのです。
最後に、テーブル本体を修正するために 2 つのことを行う必要があります。 まず、data-ng-repeat
を*ngFor="let order of orders"
に置き換える。 その行の orderBy
と filter
のフィルターも削除して、tr
全体が次のようになることに注意してください:
<tr *ngFor="let order of orders">
次に、注文詳細ルートへの href
リンクの前の data-ng
プレフィックスを削除します。 AngularJS はここでもルーティングを処理しますが、これは Angular テンプレートになったので、このプレフィックスを使用する必要はありません。
アプリケーションをもう一度見てみると、注文が画面に正しくロードされていることがわかります。 ソート リンクはもはや機能しませんし、通貨は、Angular の通貨パイプが AngularJS の対応するものとわずかに異なるため、ちょっと混乱しています。 これについては後ほど説明します。 今のところ、これは素晴らしい兆候です。なぜなら、データがコンポーネントに届き、ページ上で読み込まれることを意味するからです。 これで、このテンプレートの基本がAngularに変換されました。
Step 3 – ソートを追加する
注文が画面に読み込まれましたが、それらを並べ替えたりする方法がまだありません。 AngularJS では、ページ上のデータを並べ替えるために組み込みの orderBy
フィルターを使用するのが一般的でした。 AngularにはもうorderBy
フィルターはありません。 これは、この種のビジネスロジックをテンプレートに持たせるのではなく、コンポーネントに移動させることが強く推奨されるようになったからです。 というわけで、ここではそれを行うことにします。 (注意: ここでは、リアクティブフォームのアプローチではなく、ごく普通の関数とイベントを使用する予定です。 これは、このようなことを理解するための第一歩を踏み出そうとしているからです。 基本を押さえたら、observable を使ってさらに進めてください!)
Sorting in the Component
ng-repeat
から orderBy
フィルターを削除し、*ngFor
に変更したときにすでに行いました。 今度は、ordersコンポーネントでソート機能を作ってみます。 テーブル ヘッダーのクリック イベントを使用してこの関数を呼び出し、並べ替えを行うプロパティを渡します。
Orders コンポーネント (./orders/orders.component.ts
) を開き、クラスに 2 つのパブリック プロパティを追加しましょう。 これらは、テンプレートがすでに参照している 2 つのプロパティと一致させるつもりです。 最初のものは、型 string
の sortType
です。 2つ目は boolean
型の sortReverse
で、デフォルト値を false に設定します。 sortReverse
プロパティは、単に順序を反転させるかどうかを記録しているだけで、昇順や降順の同義語とは考えないでください。
では、クラス内のタイトルの宣言の後に次のように記述します。
sortType: string;sortReverse: boolean = false;
次に、JavaScript の Array.sort プロトタイプ関数で使用する関数を追加します。 goToCreateOrder
関数の後(ただし、まだクラス内)にこれを追加します:
dynamicSort(property) { return function (a, b) { let result = (a < b) ? -1 : (a > b) ? 1 : 0; return result; } }
この動的ソート関数は、配列内のオブジェクトのプロパティ値を比較します。 ネストされた3項関数は一見すると少しわかりにくいかもしれませんが、基本的には「Aのプロパティの値がBより小さければ、-1を返す」というだけのものです。 そうでない場合は、より大きければ1を返します。 等しい場合は0を返します。
さて、これは超高度な、または深い比較ではありません。 ソートするために書くことができるもっと洗練されたヘルパー関数がありますし、これをどのように破ることができるか自由に試してみてください。 このロジックを好きなカスタム ソート ロジックと交換することができます。 Array プロトタイプの sort 関数は、配列内のアイテムを比較するために使用できる関数を渡すことができます。 新しい dynamicSort
関数を使用して、それを利用する sortOrders
という関数をクラスで作成してみましょう:
sortOrders(property) { }
最初に行う必要があるのは、クラスの sortType
プロパティを渡されたプロパティに等しく設定することです。 次に、sortReverse
プロパティをトグルさせます。
sortOrders(property) { this.sortType = property; this.sortReverse = !this.sortReverse;}
ここで、this.orders
の sort
関数を呼び出すことができますが、プロパティで動的ソート関数を渡すことができます。
sortOrders(property) { this.sortType = property; this.sortReverse = !this.sortReverse; this.orders.sort(this.dynamicSort(property));}
そして、最後にもう1つ必要なことがあります。 配列の昇順と降順を逆にできるように、dynamicSort
関数を少し修正する必要があります。 そのために、dynamicSort
の結果をクラスの sortReverse
プロパティに結びつけます。
最初にやることは、変数の宣言です。
let sortOrder = -1;
そして、クラスのsortReverse
プロパティが真か偽かをチェックします。 True の場合、ソート順変数を 1 に設定します。
if (this.sortReverse) { sortOrder = 1; }
このように関数を結び付けているのは、デモのためにソート関数でトグルを行っているからです。 もっと徹底して言えば、別のアプローチとして、sortReverse
の代わりにsortDescending
という変数を用意して、別の関数で制御することもできます。 このルートでは、逆のことをします。sortOrder
はsortDescending
が真でなければ1になります。
最後の2つを組み合わせて3項式にすることもできますが、わかりやすくするために、もう少し冗長なままにしておきます。 そして、結果を通常とは逆にするには、result
にsortOrder
をかければよいのです。
dynamicSort(property) { let sortOrder = -1; if (this.sortReverse) { sortOrder = 1; } return function(a, b) { let result = a < b ? -1 : a > b ? 1 : 0; return result * sortOrder; }; }
繰り返しますが、これはソートのデモ実装であり、コンポーネント上でカスタム ソート関数を使用する際の主要な概念を理解するためのものです。
これらの関数が機能しているかどうかを確認するために、ngOnInit
関数にデフォルトの並べ替えを追加します。
forkJoin
サブスクリプションの内部で、顧客名プロパティを追加する forEach
の後に、this.sortOrders
を呼び出して合計アイテム数プロパティを渡してみます。
では、テーブル ヘッダー リンクから sortOrders
関数を呼び出して、テンプレートにこの並べ替えを実装する必要があるだけです。
Add sorting to the template
注文コンポーネントで sortOrders
関数が正しく動作しているので、それをテンプレートに追加して、テーブル ヘッダーを再びクリックできるようにする準備が整いました。
その前に、ngOnInit
関数のデフォルトの並べ替えを ID だけに変更してみましょう:
this.sortOrders('id');
これは、合計アイテムを使用するより少し普通ですね。
これでテンプレートに手を加えることができる。 最初に行いたいのは、すべてのクリック イベントで sortOrders
関数を呼び出すことです。 sortType =
のインスタンスを選択し、sortOrders(
に置き換えることができます。 次に、; sortReverse = !sortReverse
のインスタンスを)
に置き換えることができます。
また、ここで渡しているプロパティ名のうち、*ngIf
のインスタンスにある2つを修正する必要があります。 orderId
の3つのインスタンスをid
に、customername
の3つのインスタンスをcustomerName
に置き換えてください。
最後に、ヘッダー内の href
タグをそれぞれ括弧で囲み、Angular が引き継いでこれらのリンクが実際にはどこにも移動しないようにします。 クリックイベントが発生することになります。 つまり、ヘッダーは次のパターンに従っている必要があります:
<th> <a ="" (click)="sortOrders('id')"> Order Id <span *ngIf="sortType == 'id' && !sortReverse" class="fa fa-caret-down"></span> <span *ngIf="sortType == 'id' && sortReverse" class="fa fa-caret-up"></span> </a></th>
ブラウザに移動して、すべてのテーブルヘッダーリンクをテストしてください。 各プロパティが昇順と降順の両方でソートされることが確認できるはずです。 素晴らしい!
これは素晴らしいことですが、1 つのことを失いました – カーソルはポインターではなくセレクタです。 いくつかの CSS でそれを修正しましょう。
Fix the Cursor
注文ページで正しく機能しているソートがありますが、カーソルがポインターではなくセレクタになり、イライラさせられます。
これを修正するために CSS を使用できるいくつかの異なる方法があります。
- メイン アプリの SCSS ファイルにクラスを作成する。
- インライン CSS を記述することもできますが、これはほとんど望ましいことではありません。
- Component Decorator の styles オプションを使用して Angular のスコープされた CSS を利用する。
この特定のコンポーネント用のスタイルに 1 つのルールを追加するだけでよいので、最後のオプションを使用することにします。
Orders コンポーネント クラスをもう一度開いてください。 コンポーネント デコレーターで、styles
という新しいプロパティを追加できます。 Styles は文字列の配列ですが、この文字列は CSS ルールです。 カーソルを修正するために必要なことは、テーブルの行で、リンクがある場合は、カーソルプロパティをポインタに変更するというルールを書き出すことです。 デコレーターは次のようになります。
@Component({ selector: 'orders', template: template, styles: })
これで、行のヘッダーにカーソルを置くと、ポインター カーソルが表示されることがわかります。 この方法の優れている点は、この CSS ルールが他のコンポーネントに影響を与えないことです。 注文コンポーネントにのみ適用されます。
さて、フィルタリングについて何かできるかどうか見てみましょう。 その「フィルター filter」は Angular から削除されたので、創造力を発揮して、私たちのコンポーネントにそれを実装する方法を考え出す必要がありそうです。
Step 4 – Add Filtering
AngularJS フィルターを使用して、検索する文字列に基づいてオーダー コレクションを検索していたフィルター ボックスを置き換える準備が整いました。 AngularJS フィルターはテンプレート上にあり、コントローラやコンポーネントにコードを必要としませんでした。 現在では、このようなテンプレート内のロジックは推奨されません。
Add a Filter Function
コンポーネントに戻って、filteredOrders
という名前の新しい注文の配列を作成します。 次に、orders
配列を filteredOrders
配列に設定するフィルタ関数に渡します。 最後に、テンプレート上の *ngFor
で、元の配列の代わりに filteredOrders
を使用します。
filteredOrders: Order;
次に、注文の元の配列を設定する forkJoin
で、注文配列に filteredOrders
の初期状態を設定できます。
this.filteredOrders = this.orders;
さて、実際にフィルタリングを行う関数を追加する準備が整いました。 コンポーネントの下部にあるソート関数のすぐ後に、この関数を貼り付けます。 まず、この関数にsearch
という文字列のプロパティを与えています。 そして、オーダーをループして、オブジェクトのキーをすべて見つけます。 すべてのキーについて、検索用語に一致するプロパティの some
値があるかどうかを確認します。 このJavaScriptの部分は、最初は少しわかりにくいかもしれませんが、基本的にはこのような処理が行われています。
if
ステートメントでは、文字列を明示的にテストしていることに注意してください。 今の例では、クエリーを文字列に限定しています。 ネストされたプロパティや数値のプロパティなどを処理するつもりはありません。 検索語は顧客名プロパティにマッチし、住所や他の文字列プロパティを表示することを選択した場合は、それらも含めて検索します。
もちろん、数字をテストしたり、入れ子オブジェクトの別の層を調べるためにこの関数を変更することもできます。 ソートと同様に、デモの実装から始めて、想像力を働かせてより複雑にしてください。
sortOrders
関数といえば、次に進む前に、コンポーネントで最後にもう 1 つ行う必要があります。 フィルタをソートより優先させたいので、sortOrders
を修正して、元の orders
ではなく filteredOrders
を使用するようにします。
sortOrders(property) { this.sortType = property; this.sortReverse = !this.sortReverse; this.filteredOrders.sort(this.dynamicSort(property));}
これで、テンプレートにこのフィルタリングを実装する準備ができました。
Add Filtering to the Template
テンプレートに戻り、フィルタリングを使用するように修正します。
最初に行う必要があるのは、data-ng-model
を置き換えることです。 その代わりに、keyup
イベントを使用するつもりなので、「キーアップ」と書いて、括弧で囲みます((keyup)
)。 これはAngularの組み込みイベントで、入力のキーアップで関数を実行することができます。 関数の名前はfilterOrders
としましたが、これはAngularJSのフィルタに渡すプロパティの名前だったので、その横に括弧を追加するだけです。 入力は今のところ次のようになります:
<input type="text" class="form-control" placeholder="Filter Orders (keyup)="filterOrders()">
しかし、フィルターオーダー関数に何を渡せばよいのでしょうか? デフォルトでは、イベントは$event
と呼ばれるものを渡します。 この中にはtarget
というものが含まれており、この中に入力の値が含まれています。 $event
を使うには一つ問題がある。 target.value
は本当に何でもあり得るので、これらの漠然とした型を追跡するのは非常に困難である。 これでは、デバッグするのも、期待される値の型を知るのも難しい。 その代わりに、Angularには本当に気の利いたことがあります。それは、この入力にテンプレート変数を割り当てることです。
幸運なことに、Angular はこれを行うためのメソッドを提供しています。 入力タグの後に、ハッシュ記号(#)と、目的のモデルの名前を追加します。 ここでは #ordersFilter
と呼ぶことにします。 タグのどこに書いてもいいし、どう呼んでもいいのですが、私は入力の後に書いて、どのモデルがどの入力に関連付けられているかを、ページを見下ろしたときにキャッチできるようにしたいのです。
ここで、その変数を keyup
イベントで filterOrders
関数に渡すことができます。 その前にハッシュ記号は必要ありませんが、.value
を追加する必要があります。 これはモデル全体ではなく、モデルの実際の値を渡すことになります。 完成した入力は次のようになります:
<input #ordersFilter type="text" class="form-control" placeholder="Filter Orders" (keyup)="filterOrders(ordersFilter.value)">
最後に、通常の orders
配列ではなく filteredOrders
配列を使用するように *ngFor
を修正する必要があります:
<tr *ngFor="let order of filteredOrders">
Inspect the Product
フィルタリングとソートがコンポーネント内にあるため、テンプレートがどれほどきれいになったかがわかると思います:
次に、ブラウザ上でこれを確認してみましょう。 ボックスに「sally」などのテキストを入力すると、注文が変更され、その上で並べ替えが動作していることがわかります。
さて、このコンポーネントで最後に行う必要があるのは、通貨パイプを修正することです。
Step 5 – 通貨パイプを修正する
最後の仕上げは、以前の通貨フィルターを更新することです。 AngularJS では指定する必要がなかったいくつかのパラメーターをテンプレート内のパイプに追加するだけです。 この部分は、Angular 4 または Angular 5 を使用している場合は異なります:.
Angular 4 では次のようにします: <td>{{order.totalSale | currency:'USD':true}}</td>
Angular 5+ では次のようにします: <td>{{order.totalSale | currency:'USD':'symbol'}}</td>
最初のオプションは通貨コードです(たくさんあります、米ドルに限定されていません!)。 2つ目はシンボル表示です。 Angular 4 では、これは通貨記号を使用するかコードを使用するかを示すブール値です。 Angular 5+では、選択肢は文字列としてsymbol
、code
、またはsymbol-narrow
です。
これで期待通りの記号が表示されるはずです。
そして完了しました!
そして、私たちは、この記号が表示されるようになりました。
Conclusion
あなたは最後までよく頑張りました! このガイドで達成したことは以下のとおりです。
- AngularJS テンプレート構文を Angular 構文に置き換える
- ソートをコンポーネントに移動する
- Using scoped CSS styles
- フィルタリングをコンポーネントに移動する
- AngularJS 通貨フィルターを Angular 通貨パイプに置き換える
ここからどこに行くべきでしょうか。
- Make the sorting more sophisticated (for example: should the ordering reset or stay the same when the user clicks a new header?)
- Make the filtering more sophisticated (search for numbers or nested properties)
- Change to a reactive approach.
keyup
関数の代わりに値の変化の observable をリッスンして、そこでソートとフィルタリングを行うことができます。 observable を使用すると、入力のデバウンスのような本当にクールなこともできるようになります!