IgxGrid は ISortingStrategy を実装することでソート方法をカスタマイズすることができます。
この方法を応用して、欠損値として “-” や “*” や “” (空文字列) などが使われている場合に、欠損値を null や undefined と同じとみなしてソートする方法をご紹介します。
この記事で扱うデータ
下のコードはこの記事で使用するデータです。Col1 フィールドは数値型もしくは文字列型と定義されていて、欠損値として “-” が使用されています。
// src\app\sampleData.ts export interface SampleDataType { ID: number; Col1: number | string; } export const SampleData: SampleDataType[] = [ { ID: 1, Col1: 1 }, { ID: 2, Col1: 8 }, { ID: 3, Col1: 5 }, { ID: 4, Col1: '-' }, // 欠損値 { ID: 5, Col1: 3 }, { ID: 6, Col1: '-' }, // 欠損値 { ID: 7, Col1: 6 }, { ID: 7, Col1: 12 }, ]
このデータを IgxGrid にバインドして Col1 で並べ替えた場合に、昇順のときは ‘-‘ が一番最初に、降順のときは一番最後にさせてみましょう。
第一段階 : データを igx-grid に表示するまで
App コンポーネント側
// src\app\app.component.ts import { Component, OnInit } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { IgxGridModule, ISortingStrategy} from '@infragistics/igniteui-angular'; import { SampleDataType, SampleData } from './sampleData'; // 「この記事で扱うデータ」で提示しているデータを読み込みます @Component({ selector: 'app-root', imports: [RouterOutlet, IgxGridModule], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent implements OnInit { title = 'kb15005-app1'; public data: SampleDataType[] = SampleData; // 読み込んだデータを data に設定します。 constructor() {} ngOnInit() { } }
- 7 行目 : この一つ前のセクション「この記事で扱うデータ」で提示したデータを読み込みます。
- 17 行目 : 読み込んだデータを data に設定します。
App コンポーネント テンプレート側
<!-- src\app\app.component.html --> <div class="my_wrapper"> <igx-grid #grid1 [data]="data" [autoGenerate]="false" [primaryKey]="'ID'" height="600px"> <igx-column field="ID" header="ID" [dataType]="'number'" [sortable]="true"></igx-column> <igx-column field="Col1" header="Col1" [dataType]="'string'" [sortable]="true"> </igx-column> </igx-grid> </div>
- 4 行目 : igx-grid の data に読み込んだデータを設定します。
- 6 行目 : Col1 の dataType を string にしていることに注目です。SampleDataType の Col1 フィールドの定義を思い出してください。数値もしくは文字列でした。ここで dataType を number としてしまうと、数値に変換できない文字列が来たときにエラーが発生して表示できません。string であればどのようなものが来ても igx-grid は string に変換して表示させようとしますので、そのようなエラーを回避することができます。
- 5、6 行目の各 igx-column : sortable を trueにして並べ替え機能を有効にします。
実行結果
igx-grid にデータを表示させ、ソート機能を有効にするところまでできました。
第二段階 : ISortingStrategy を実装したクラスを実装する
今回は CustomSortingStrategy という名前のクラスを作成し、そこに ISortingStrategy を実装していきましょう。
まず src フォルダーの下に lib フォルダーを作成し、その下に CustomSortingStrategy.ts ファイルを作成します。
ISortingStrategy を実装したクラスをゼロから作成するのは大変ですので、igniteui-angular に組み込まれているデフォルトの ISortingStrategy の実装クラスをコピーしてきて必要な箇所だけ変更していくことにします。デフォルトの ISortingStrategy の実装クラスは GitHub にあり、こちらの DefaultSortingStrategy クラスがそれです。
コピーしてきたら、compareValue の null 値かもしくは undefined 値かを判定している部分に、’-‘ かどうかも追加します。
// src\lib\CustomSortingStrategy.ts public compareValues(a: any, b: any): number { const an = (a === null || a === undefined || a === '-'); // '-'もnullやundefined判定のところに加える const bn = (b === null || b === undefined || b === '-'); // '-'もnullやundefined判定のところに加える if (an) { if (bn) { return 0; } return -1; } else if (bn) { return 1; } return a > b ? 1 : a < b ? -1 : 0; }
また、本題とは離れますが、このままでは一部のコードでエラーが発生してしまうので、そこも修正しておきます。
// src\lib\CustomSortingStrategy.ts ... (中略) ... // コンストラクターをpublicに変更します。 public constructor() { } ... (中略) ... public sort( ... 中略 ... ) ) { const key = fieldName; const reverse = (dir === SortingDirection.Desc ? -1 : 1); // isDateおよびisTimeがnullのときはfalseを設定するように変更します。 const cmpFunc = (obj1: any, obj2: any) => this.compareObjects(obj1, obj2, key, reverse, ignoreCase, valueResolver, isDate ?? false, isTime ?? false); return this.arraySort(data, cmpFunc); }
これで ISortingStrategy を実装した CustomSortingStrategy クラスの実装は完成です。最終的にはこのようなコードになります。
// src\lib\CustomSortingStrategy.ts import { ISortingStrategy, SortingDirection } from '@infragistics/igniteui-angular'; export class CustomSortingStrategy implements ISortingStrategy { protected static _instance: CustomSortingStrategy | null = null; // コンストラクターをpublicに変更します。 public constructor() { } public static instance(): CustomSortingStrategy { return this._instance || (this._instance = new this()); } public sort( data: any[], fieldName: string, dir: SortingDirection, ignoreCase: boolean, valueResolver: (obj: any, key: string, isDate?: boolean) => any, isDate?: boolean, isTime?: boolean ) { const key = fieldName; const reverse = (dir === SortingDirection.Desc ? -1 : 1); // isDateおよびisTimeがnullのときはfalseを設定するように変更します。 const cmpFunc = (obj1: any, obj2: any) => this.compareObjects(obj1, obj2, key, reverse, ignoreCase, valueResolver, isDate ?? false, isTime ?? false); return this.arraySort(data, cmpFunc); } public compareValues(a: any, b: any): number { const an = (a === null || a === undefined || a === '-'); // '-'をnullやundefinedと同じとして扱う処理を追加します const bn = (b === null || b === undefined || b === '-'); // '-'をnullやundefinedと同じとして扱う処理を追加します if (an) { if (bn) { return 0; } return -1; } else if (bn) { return 1; } return a > b ? 1 : a < b ? -1 : 0; } protected compareObjects( obj1: any, obj2: any, key: string, reverse: number, ignoreCase: boolean, valueResolver: (obj: any, key: string, isDate?: boolean, isTime?: boolean) => any, isDate: boolean, isTime: boolean ) { let a = valueResolver.call(this, obj1, key, isDate, isTime); let b = valueResolver.call(this, obj2, key, isDate, isTime); if (ignoreCase) { a = a && a.toLowerCase ? a.toLowerCase() : a; b = b && b.toLowerCase ? b.toLowerCase() : b; } return reverse * this.compareValues(a, b); } protected arraySort(data: any[], compareFn?: (arg0: any, arg1: any) => number): any[] { return data.sort(compareFn); } }
第三段階 : CustomSortingStrategy を Col1 列に適用する
ここまでくればあともう一息です!
App コンポーネント側
作成した CustomSortingStrategy をインポートし、オブジェクトを作成します。
// src\app\app.component.ts import { Component, OnInit } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { IgxGridModule, ISortingStrategy} from '@infragistics/igniteui-angular'; import { SampleDataType, SampleData } from './sampleData'; import { CustomSortingStrategy } from '../lib/CustomSortingStrategy'; // ここ!CustomSortingStrategyをインポートします。 @Component({ selector: 'app-root', imports: [RouterOutlet, IgxGridModule], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent implements OnInit { title = 'kb15005-app1'; public data: SampleDataType[] = SampleData; public col1CustomSortingStrategy: ISortingStrategy = new CustomSortingStrategy(); // ここ!CustomSortingStrategyオブジェクトを作成し、col1CustomSortingStrategyに代入します。 constructor() {} ngOnInit() { } }
App コンポーネント テンプレート側
Col1 の igx-column の sortStrategy で CustomSortingStrategy オブジェクトを指定します。
<!-- src\app\app.component.html --> <div class="my_wrapper"> <igx-grid #grid1 [data]="data" [autoGenerate]="false" [primaryKey]="'ID'" height="600px"> <igx-column field="ID" header="ID" [dataType]="'number'" [sortable]="true"></igx-column> <!-- sortStrategyでCol1のカスタムソートを設定します。 --> <igx-column field="Col1" header="Col1" [dataType]="'string'" [sortable]="true" [sortStrategy]="col1CustomSortingStrategy"> </igx-column> </igx-grid> </div>
以上ですべて完了です!
実行結果
Col1 列を昇順で並べ替えた場合
“-” が先頭に来るようになりました! しかも 12 も数値として並べ替えられていて、ちゃんと最後に来るようになっています。
Col1 列を降順で並べ替えた場合
こちらも同様ですね! “-” が最後に来て、12 も数値として並べ替えられています。