角度ソートフィルターをアップグレードする方法
序章
AngularJSの最初の製品の最も便利な機能の1つは、テンプレート変数とフィルターのみを使用してページ上のデータをフィルター処理およびソートする機能でした。 双方向のデータバインディングは、AngularJSへの多くの変換に勝ちました。
しかし今日、多くのフロントエンド開発者は一方向のデータバインディングを好み、それらのorderByおよびfilterフィルターはAngularの出現で廃止されました。
注:この記事全体を通して、「AngularJS」は1.xを指すために使用され、「Angular」は2+を指すために使用されます。
この記事では、ngUpgradeを使用してorderByとfilterを再適用します。
ステップ1—プロジェクトの設定
新しく書き直されたコンポーネントのテンプレートを更新する手順を実行します。 次に、並べ替えとフィルタリングを追加して、AngularJSにあったすべての機能を復元します。 これは、ngUpgradeプロセスのために開発するための重要なスキルです。
開始するには、使用するサンプルプロジェクトのクローンを作成してください。
git clone https://github.com/upgradingangularjs/ordersystem-project.git
出発点として、このコミットを確認してください。
git checkout 9daf9ab1e21dc5b20d15330e202f158b4c065bc3
このサンプルプロジェクトは、AngularJS1.6とAngular4の両方を使用するngUpgradeハイブリッドプロジェクトです。 動作するExpressAPIと、開発と本番の両方のWebpackビルドがあります。 自由に探索し、フォークして、独自のプロジェクトでパターンを使用してください。
publicフォルダーの両方でnpm installを実行することを忘れないでください。
cd public npm install
そしてserverフォルダー:
cd server npm install
Angular 5を使用するこのプロジェクトのバージョンを確認したい場合は、このリポジトリを確認してください。 このチュートリアルでは、2つのバージョンの違いは重要ではありません。
ステップ2—AngularJS構文を置き換える
アプリケーションのこの段階で、ordersコンポーネントはAngularで書き直され、すべての依存関係が注入されて解決されます。 ただし、アプリケーションを実行しようとすると、テンプレートに問題があることを示すエラーがコンソールに表示されます。 それが最初に修正する必要があるものです。 注文テンプレート(orders/orders.html)のAngularJS構文を置き換えて、ページに表示されるルートの読み込みと注文を取得できるようにします。 次に、フィルタリングと並べ替えを修正します。
最初に行う必要があるのは、このテンプレート内の$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 server npm start
Expressサーバーが起動します。 Webpack devサーバーを実行するには、別のターミナルを開いて次のコマンドを実行します。
cd public npm run dev
このチュートリアルの残りの部分では、これらのプロセスを実行し続けることができます。
アプリケーションが再び読み込まれていることを確認する必要があります。 注文ルートに移動すると、注文コンポーネントが正しく表示されていることがわかります。
注文の作成ボタンをクリックすることもできます。これにより、注文の作成ルートとフォームに正しく送信されます。
では、HTMLに戻りましょう。 divのコメントを外します(アプリは再び壊れます)。
残りのすべてのインスタンスdata-ng-clickを(click)イベントハンドラーに置き換えましょう。 「検索と置換」を使用するか、エディターのショートカットを使用してすべてのオカレンスを選択できます(Visual Studio Code for Windowsでは、これはCTRL+SHIFT+Lです)。
次に、出現するすべてのdata-ng-showを*ngIfに置き換えます。 Angularにはng-showに直接相当するものは実際にはありませんが、それは問題ありません。 *ngIfを使用することをお勧めします。これにより、要素を非表示にして表示するのではなく、実際に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に変換しました。 これで、並べ替えとフィルタリングに取り組む準備が整いました。
ステップ3—並べ替えを追加する
画面に注文が読み込まれていますが、注文や並べ替えの方法はまだありません。 AngularJSでは、組み込みのorderByフィルターを使用してページ上のデータを並べ替えるのが非常に一般的でした。 AngularにはorderByフィルターがなくなりました。 これは、そのようなビジネスロジックをテンプレートに配置するのではなく、コンポーネントに移動することが強く推奨されているためです。 だから、それが私たちがここでやろうとしていることです。
注:ここでは、リアクティブ形式のアプローチではなく、単純な古い関数とイベントを使用します。 これは、私たちがこのことを理解するために赤ちゃんの一歩を踏み出そうとしているからです。 基本を理解したら、オブザーバブルでさらに進んでください。
コンポーネントでの並べ替え
*ngForに変更したときに、ng-repeatからorderByフィルターを既に削除しました。 次に、ordersコンポーネントで並べ替え関数を作成します。 テーブルヘッダーのクリックイベントを使用して、その関数を呼び出し、並べ替えるプロパティを渡すことができます。 また、その関数を昇順と降順の間で前後に切り替えます。
注文コンポーネント(./orders/orders.component.ts)を開き、2つのパブリックプロパティをクラスに追加しましょう。 これらは、テンプレートがすでに参照している2つのプロパティと一致します。 最初のものは、タイプstringのsortTypeになります。 2つ目はsortReverseタイプbooleanで、デフォルト値をfalseに設定します。 sortReverseプロパティは、順序を反転するかどうかを追跡するだけです。昇順または降順の同義語とは考えないでください。
したがって、クラスでタイトルを宣言した後、これが必要になります。
sortType: string; sortReverse: boolean = false;
次に、JavaScriptのArray.sortプロトタイプ関数で使用する関数を追加します。 goToCreateOrder関数の後にこれを追加します(ただし、クラス内にあります)。
dynamicSort(property) {
return function (a, b) {
let result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
return result;
}
}
この動的ソート関数は、配列内のオブジェクトのプロパティ値を比較します。 ネストされた三項関数は、一見理解するのが少し難しいかもしれませんが、基本的には、Aのプロパティの値がBより小さい場合、-1を返すと言っているだけです。 それ以外の場合は、それより大きい場合は1を返します。 2つが等しい場合は、0を返します。
さて、これは非常に洗練された、または深い比較ではありません。 ソートするために作成できるより洗練されたヘルパー関数があり、これを壊す方法を自由に試してみてください。 ただし、これは私たちの目的には役立ちます。このロジックを、任意のカスタムソートロジックと交換することができます。
これが私たちのヘルパー関数です。 配列プロトタイプの並べ替え関数には、配列内のアイテムを比較するために使用できる関数を渡すことができます。 新しい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かfalseかを確認できます。 trueの場合、ソート順変数を1に設定します。
if (this.sortReverse) {
sortOrder = 1;
}
デモンストレーションのためにsort関数でトグルを実行しているため、このように関数を結合しています。 より徹底的に言うと、別のアプローチは、別の関数を介して制御されるsortReverseの代わりに、sortDescendingと呼ばれる変数を使用することです。 このルートを使用する場合は、逆になります。sortDescendingが真でない限り、sortOrderは1になります。
これらの最後の2つを組み合わせて3値式にすることもできますが、わかりやすくするために、もう少し冗長なままにしておきます。 そして、結果を通常とは逆にするために、resultにsortOrderを掛けることができます。 したがって、dynamicSort関数は次のようになります。
dynamicSort(property) {
let sortOrder = -1;
if (this.sortReverse) {
sortOrder = 1;
}
return function(a, b) {
let result = a[property] < b[property] ? -1 : a[property] > b[property] ? 1 : 0;
return result * sortOrder;
};
}
}
繰り返しになりますが、これは並べ替えのデモンストレーション実装であるため、コンポーネントでカスタム並べ替え機能を使用する際の重要な概念を理解できます。
並べ替えの確認
これまでに、dynamicSortヘルパー関数とsortOrders関数をクラスに追加して、テンプレートではなくコンポーネントで並べ替えることができるようにしました。
これらの関数が機能しているかどうかを確認するために、ngOnInit関数にデフォルトの並べ替えを追加しましょう。
forkJoinサブスクリプション内で、顧客名プロパティを追加するforEachの後に、this.sortOrdersを呼び出して、合計アイテムプロパティを渡します。
this.sortOrders('totalItems');
画面が更新されると、注文が合計アイテムで並べ替えられていることがわかります。
次に、テーブルヘッダーリンクからsortOrders関数を呼び出して、この並べ替えをテンプレートに実装する必要があります。
テンプレートに並べ替えを追加する
sortOrders関数が注文コンポーネントで正しく機能するようになりました。これは、テーブルヘッダーを再度クリックできるように、テンプレートに追加する準備ができたことを意味します。
その前に、ngOnInit関数のデフォルトの並べ替えをIDに変更しましょう。
this.sortOrders('id');
これは、合計アイテムを使用するよりも少し普通です。
これで、テンプレートで作業できます。 最初に実行したいのは、すべてのクリックイベントでsortOrders関数を呼び出すことです。 sortType = のインスタンスを選択して、sortOrders(に置き換えることができます。 次に、; sortReverse = !sortReverseのインスタンスを)に置き換えることができます。
また、ここで渡す2つのプロパティ名と、*ngIfインスタンスを修正する必要があります。 orderIdの3つのインスタンスをidに置き換え、customernameの3つのインスタンスをcustomerNameに置き換えます。
最後に行う必要があるのは、hrefタグのそれぞれを角かっこで囲んだヘッダーでラップして、Angularが引き継ぎ、これらのリンクが実際にはどこにも行かないようにすることです。 クリックイベントが発生します。 したがって、ヘッダーは次のパターンに従う必要があります。
<th>
<a [href]="" (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でそれを修正しましょう。
カーソルの修正
注文ページで並べ替えが正しく機能するようになりましたが、カーソルがポインターではなくセレクターになり、煩わしいものになりました。
CSSを使用してこれを修正する方法はいくつかあります。
- メインアプリのSCSSファイルでクラスを作成できます。
- インラインCSSを作成することもできますが、それはほとんど望ましいことではありません。
- コンポーネントデコレータのstylesオプションを使用して、Angularのスコープ付きCSSを利用できます。
この特定のコンポーネントのスタイルに1つのルールを追加するだけなので、最後のオプションを使用します。
注文コンポーネントクラスを再度開きます。 コンポーネントデコレータで、stylesという新しいプロパティを追加できます。 Stylesは文字列の配列ですが、文字列はCSSルールです。 カーソルを修正するには、テーブルの行にリンクがある場合は、カーソルのプロパティをポインタに変更するというルールを書き出すだけです。 デコレータは次のようになります。
@Component({
selector: 'orders',
template: template,
styles: ['tr a { cursor: pointer; }']
})
ここで、行ヘッダーにカーソルを合わせると、ポインターカーソルがあることがわかります。 このアプローチの優れている点は、このCSSルールが他のコンポーネントに影響を与えないことです。 それは私たちの注文コンポーネントにのみ適用されます!
それでは、フィルタリングについて何かできるかどうか見てみましょう。 その「フィルターフィルター」はAngularから削除されたため、創造性を発揮して、コンポーネントに実装する方法を考え出す必要があります。
ステップ4—フィルタリングを追加する
AngularJSフィルターを使用して、検索していた文字列に基づいて注文コレクションを検索するために使用していたフィルターボックスを置き換える準備ができました。 AngularJSフィルターはテンプレート上に存在し、コントローラーやコンポーネントにコードを必要としませんでした。 現在、テンプレート内のそのようなロジックは推奨されていません。 コンポーネントクラスでそのような並べ替えとフィルタリングを行うことをお勧めします。
フィルタ機能の追加
コンポーネントに戻って、filteredOrdersと呼ばれる新しい注文の配列を作成します。 次に、orders配列を、filteredOrders配列を設定するフィルター関数に渡します。 最後に、元の配列の代わりに、*ngForのテンプレートでfilteredOrdersを使用します。 そうすれば、サーバーから返されるデータを変更することはなく、そのサブセットを使用するだけです。
最初に行うことは、クラスで新しいプロパティを宣言することです。
filteredOrders: Order[];
次に、元の注文の配列を設定するforkJoinで、filteredOrdersの初期状態を注文の配列に設定できます。
this.filteredOrders = this.orders;
これで、実際にフィルタリングを行う関数を追加する準備が整いました。 コンポーネントの下部にある並べ替え関数の直後に、この関数を貼り付けます。
filterOrders(search: string) {
this.filteredOrders = this.orders.filter(o =>
Object.keys(o).some(k => {
if (typeof o[k] === 'string')
return o[k].toLowerCase().includes(search.toLowerCase());
})
);
}
この関数で何が起こっているかについて話しましょう。 まず、関数にsearchの文字列プロパティを指定します。 次に、注文をループして、オブジェクトのすべてのキーを見つけます。 すべてのキーについて、検索語に一致するプロパティのsome値があるかどうかを確認します。 このJavaScriptのビットは、最初は少し混乱しているように見えるかもしれませんが、基本的にはそれが起こっていることです。
ifステートメントでは、文字列を明示的にテストしていることに注意してください。 この例では、クエリを文字列に制限します。 ネストされたプロパティ、数値プロパティ、またはそのようなものを処理しようとはしません。 検索語は顧客名プロパティと一致し、住所やその他の文字列プロパティを表示することを選択した場合は、それらも検索します。
もちろん、この関数を変更して数値をテストしたり、ネストされたオブジェクトの別のレイヤーを調べたりすることもできます。それは完全にあなた次第です。 並べ替えと同じように、デモンストレーションの実装から始めて、想像力を使ってより複雑にします。
sortOrders関数と言えば、先に進む前に、コンポーネントに対して最後に1つのことを行う必要があります。 sortOrdersを変更して、元のordersではなく、filteredOrdersを使用するようにする必要があります。これは、フィルターが並べ替えよりも優先されるようにするためです。 これに変更するだけです:
sortOrders(property) {
this.sortType = property;
this.sortReverse = !this.sortReverse;
this.filteredOrders.sort(this.dynamicSort(property));
}
これで、このフィルタリングをテンプレートに実装する準備が整いました。
テンプレートへのフィルタリングの追加
テンプレートに戻り、フィルタリングを使用するように修正してみましょう。
最初に行う必要があるのは、data-ng-modelを置き換えることです。 その代わりに、keyupイベントを使用するので、「keyup」と記述し、括弧((keyup))で囲みます。 これはAngularの組み込みイベントであり、入力のキーアップで関数を実行できます。 関数にfilterOrdersという名前を付けました。これは、AngularJSフィルターに渡すプロパティの名前であったため、その横に括弧を追加するだけです。 これまでの入力は次のようになります。
<input type="text" class="form-control" placeholder="Filter Orders (keyup)="filterOrders()">
しかし、フィルター次数関数に何を渡しますか? デフォルトでは、イベントは$eventと呼ばれるものを渡します。 これには、targetと呼ばれるものが含まれ、入力の値が含まれます。 $eventの使用には1つの問題があります。 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">
- 製品の検査
コンポーネントにフィルタリングと並べ替えが含まれるようになったため、テンプレートがどれだけクリーンになったかがわかります。
それでは、ブラウザでこれを確認してみましょう。 ボックスにテキストを入力すると、注文が変更され、その上で並べ替えが機能することがわかります。
素晴らしい、別のAngularJS機能を置き換えました!
これで、このコンポーネントで最後に行う必要があることが1つあります。それは、通貨パイプを修正することです。
ステップ5—通貨パイプの修正
最後の仕上げは、以前の通貨フィルターを更新することです。これは、Angularでは通貨pipeと呼ばれています。 AngularJSで指定する必要がなかった、テンプレートのパイプにいくつかのパラメーターを追加する必要があります。 この部分は、Angular4またはAngular5を使用している場合は異なります。
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です。
これで、期待される記号が表示されます。
これで完了です。 完成したコードを確認するには、このコミットを確認してください。
結論
あなたはこれを最後までこだわって素晴らしい仕事をしました! このガイドで達成したことは次のとおりです。
- AngularJSテンプレート構文をAngular構文に置き換える
- コンポーネントへの並べ替えの移動
- スコープ付きCSSスタイルの使用
- フィルタリングをコンポーネントに移動する
- AngularJS通貨フィルターをAngular通貨パイプに置き換える
ここからどこへ行けばいいの? あなたができることはたくさんあります:
- 並べ替えをより洗練されたものにします(たとえば、ユーザーが新しいヘッダーをクリックしたときに、順序をリセットするか、同じままにする必要がありますか?)
- フィルタリングをより高度にします(数値またはネストされたプロパティを検索します)
- リアクティブアプローチに変更します。
keyup関数の代わりに、観測可能な値の変化をリッスンし、そこで並べ替えとフィルタリングを行うことができます。 オブザーバブルを使用すると、入力をデバウンスするなど、本当にクールなこともできます。