目次
order byの実装
order by(ソート)の実装をしてみた。
github
demo
SQLで言うところ
- order by hoge1 (ASC|DESC)
- order by hoge1 (ASC|DESC) , hoge2 (ASC|DESC)
の機能を提供する
SQLの場合、order by hoge1 (ASC|DESC)
でhoge1の値が同一の場合でも並び順が内部のIDによって統一されるが、Angular(というよりjs?)の場合だと並び順が統一されないためhoge1が同一だった場合のためにhoge2まで見るようにした。引数を可変化して実質無制限にしようと思ったが、そこまでする必要も無いかなと妥協したのは内緒。
実装方法
ソースは下記に記載してある
- order-by.pipe.tsの実装
- DI(依存性注入)
order-by.pipe.tsの実装
好きな場所にコピペすれば良い。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
import { Pipe, PipeTransform } from '@angular/core'; /** * orderby機能を提供する。 * * sqlで例えると下記機能を提供する * * order by param1 ASC(DESC) * * order by param1 ASC(DESC) , param2 ASC(DESC) * * example * ``` * // html * <ng-container *ngFor="let h of hoge | async | orderBy:order" > * h.param1 * </ng-container> * * // component.ts * order = new OrderByParam(); * this.order.set('param1', 'ASC'); // order by param1 ASC * this.order.set('param1', 'ASC', 'param2', 'DESC'); // order by param1 ASC , param2 DESC * ``` * * 比較する値がnull,undefinedの場合、無限大数とする。もし無限小数としたい場合、nullHandlingMin関数を呼ぶ * ``` * this.order.nullHandlingMin(); * ``` * @export * @class OrderByPipe * @implements {PipeTransform} */ @Pipe({ name: 'orderBy', pure: false // パラメータが変わった場合にソートを動作させるためにはfalseにしないといけない }) export class OrderByPipe implements PipeTransform { transform(value: any, param: OrderByParam): any { console.log(param); if (!value) { return value; } if (!param) { return value; } if (!param.sortColumn) { return value; } return value.sort((a, b) => { let propertyA: number | string = ''; let propertyB: number | string = ''; let propertyOrderBy = 'ASC'; [propertyA, propertyB] = [a[param.sortColumn], b[param.sortColumn]]; let valA = isNaN(+propertyA) ? propertyA : +propertyA; let valB = isNaN(+propertyB) ? propertyB : +propertyB; valA = (valA) ? valA : param.nullHandling; valB = (valB) ? valB : param.nullHandling; propertyOrderBy = param.orderBy; // 同じ値だった場合かつ同一時のカラム指定が存在した場合 if (valA === valB && param.reserveColumn) { [propertyA, propertyB] = [a[param.reserveColumn], b[param.reserveColumn]]; valA = isNaN(+propertyA) ? propertyA : +propertyA; valB = isNaN(+propertyB) ? propertyB : +propertyB; valA = (valA) ? valA : param.nullHandling; valB = (valB) ? valB : param.nullHandling; propertyOrderBy = param.reserveOrderBy; } return (valA > valB ? -1 : 1) * (propertyOrderBy === 'ASC' ? -1 : 1); }); } } /** * ソートする規則を設定する. * * デフォルトではカラム値がnullもしくはundefineの場合はNumber.MAX_VALUEとして扱う * カラム値がnullもしくはundefineの取り扱いをNumber.MIN_VALUEとして扱う場合は{{nullHandlingMin()}}を呼ぶ * @export * @class OrderByParam */ export class OrderByParam { sortColumn: string; orderBy: 'ASC' | 'DESC'; reserveColumn: string; reserveOrderBy: 'ASC' | 'DESC'; nullHandling = Number.MAX_VALUE; /** * Creates an instance of OrderByParam. * @param {string} [sortColumn] ソート対象カラム名 * @param {('ASC' | 'DESC')} [orderBy] ソート順(default:ASC(昇順)) * @memberof OrderByParam */ constructor(sortColumn?: string, orderBy?: 'ASC' | 'DESC') { this.nullHandling = Number.MAX_VALUE; if (!sortColumn) { this.sortColumn = sortColumn; } if (!orderBy) { this.orderBy = 'ASC'; this.reserveOrderBy = 'ASC'; } } /** * ソートするカラム名、order順を設定する * * sortColumnをhoge1、reserveColumnをhoge2にした場合、<code>order by hoge1 ,hoge2</code>と同様になる。 * * @param {string} sortColumn カラム名 * @param {string} [orderBy] ASC or DESC * @param {string} [reserveColumn] カラム名。sortColumnの値が同一だった場合の第二ソートカラム名 * @param {string} [reserveOrderBy] ASC or DESC * @memberof OrderByParam */ set(sortColumn: string, orderBy?: string, reserveColumn?: string, reserveOrderBy?: string) { this.sortColumn = sortColumn; this.orderBy = this.getOrderBy(orderBy); if (reserveColumn) { this.reserveColumn = reserveColumn; } if (reserveOrderBy) { this.reserveOrderBy = this.getOrderBy(reserveOrderBy); } } /** * null,undefined値の取り扱いを{Number.MIN_VALUE}とする * @memberof OrderByParam */ nullHandlingMin() { this.nullHandling = Number.MIN_VALUE; } /** * null,undefined値の取り扱いを{Number.MAX_VALUE}とする * @memberof OrderByParam */ nullHandlingMax() { this.nullHandling = Number.MAX_VALUE; } private _changeOrderBy() { if (this.orderBy === 'ASC') { this.orderBy = 'DESC'; } else { this.orderBy = 'ASC'; } } private getOrderBy(val: string): ('ASC' | 'DESC') { if (val === 'ASC') { return 'ASC'; } return 'DESC'; } private _setOrderBy(val: string) { this.orderBy = this.getOrderBy(val); } } |
DI(依存性注入)
大体app.module.tsと言うファイル名になる。import文の追加とdeclarationsに宣言を追加する
1 2 3 4 5 6 7 8 9 10 11 |
import { FilterPipe } from './pipe/filter.pipe'; ・・・ @NgModule({ declarations: [ ・・・ FilterPipe ・・・ ], ・・・ |
使い方
- コンポーネントでOrderByParamの実装
- HTML部分にpipeの宣言
コンポーネントでOrderByParamの実装
OrderByParamクラスをグローバルエリアに実装する。OrderByParam.set(‘ソートするカラム名’,’並び順’)でソートを実行される。
click時にsort関数を発火させることによりOrderByParam.setを実装させる。引数となるカラム名はclickイベントから持ってくるとする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import { OrderByParam } from './order-by.pipe'; import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app'; order = new OrderByParam(); // ←ここで実装 tableData = [ { id: 1, name: '北海道', rand: 325 }, { id: 2, name: '青森県', rand: 671 }, { id: 3, name: '岩手県', rand: 69 }, ・・・ { id: 45, name: '宮崎県', rand: 651 }, { id: 46, name: '鹿児島県', rand: 722 }, { id: 47, name: '沖縄県', rand: 477 }, ]; constructor() { } sort(column: string, orderBy: string) { // ←イベント発火地点 this.order.set(column, orderBy); // ←ソートを行う } } |
HTML部分にpipeの宣言
order by の対象にパイプ宣言を行う
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<!--The content below is only a placeholder and can be replaced.--> <button (click)='sort("id","ASC")'>idのASC</button> <!-- イベント発火ポイント --> <button (click)='sort("id","DESC")'>idのDESC</button> <button (click)='sort("name","ASC")'>nameのASC</button> <button (click)='sort("name","DESC")'>nameのDESC</button> <button (click)='sort("rand","ASC")'>randのASC</button> <button (click)='sort("rand","DESC")'>randのDESC</button> <hr> <table border='1'> <tr> <th>id</th> <th>name</th> <th>rand</th> </tr> <ng-container *ngFor="let i of tableData | orderBy:order"> <!-- orderBy:"実装した変数名" --> <tr> <td>{{i.id}}</td> <td>{{i.name}}</td> <td>{{i.rand}}</td> </tr> </ng-container> </table> |
非同期の場合
サンプルではデータをハードコーディングしているが、もし非同期系(rx.js-observerなど)の場合、async
をorderByの前に宣言すればよい。
1 |
<ng-container *ngFor="let i of tableData | async | orderBy:order"> |
nullを最小値にしたい場合
nullは無限大数(すっごく大きい値)となり、降順ソートした場合は一番上に、昇順ソートした場合は一番下に来てしまう。
場合によって逆にしたい場合もあるだろうと思いnullHandlingMin
関数を作成してある。元に戻したいときはnullHandlingMax
関数を使用する。
1 2 3 4 |
sort(column: string, orderBy: string) { this.order.nullHandlingMin(); // nullの扱いをすっごく小さい値にする this.order.set(column, orderBy); } |