Angular 그리드 그룹화

    A Group By behavior in an Ignite UI for Angular Table or UI Grid creates grouped data rows based on the column values. The Group By in igxGrid allows for visualizing the groups in a hierarchical structure. The grouped data rows can be expanded or collapsed and the order of grouping may be changed through the UI or API. When Row Selection is enabled, a Group By row selector is rendered in the left-most area of the group row. In case the rowSelection property is set to single, checkboxes are disabled and only serve as an indication for the group where selection is placed. If the rowSelection property is set to multiple, clicking over the Group By row selector selects all records belonging to this group.

    Angular Grid Group By Example

    이 예에서는 대량의 데이터를 그룹화하는 기능을 보여줍니다. 열 헤더를 상단(그룹화 영역)으로 드래그하면 사용자는 선택한 열에 대한 데이터를 계층 구조로 볼 수 있습니다. 더 많은 열 헤더를 맨 위로 드래그하여 여러 필드에서 그룹화할 수 있습니다. 이러한 그룹화 옵션은 사용자가 훨씬 빠르고 시각적으로 허용되는 방식으로 데이터를 표시하려는 수많은 행과 열이 있는 테이블이 있는 경우 유용합니다.

    Initial Grouping State

    It is possible to define initial grouping of the grid by assigning an array of expressions to the groupingExpressions property of the grid.

    public ngOnInit() {
        grid.groupingExpressions = [
            { fieldName: 'ProductName', dir: SortingDirection.Desc },
            { fieldName: 'Released', dir: SortingDirection.Desc }
        ];
    }
    

    Grouping expressions implement the ISortingExpression interface.

    Group By API

    Grouping API

    Grouping is available through the UI and through a robust API exposed by the grid component. Developers can allow end-users to group the grid data by certain columns, by setting each column's groupable property to true.

    <igx-grid [data]="data">
        <igx-column *ngFor="let c of columns" [field]="c.field" [groupable]="true">
        </igx-column>
    </igx-grid>
    
    public ngOnInit() {
        grid.columns.forEach((column) => {
            column.groupable = true;
        });
    }
    

    During runtime the expressions are gettable and settable from the groupingExpressions property. If you need to add or change an existing expression you may also use the groupBy method with either a single or an array of ISortingExpression.

    grid.groupBy({ fieldName: 'ProductName', dir: SortingDirection.Desc, ignoreCase: true });
    
    Note

    지금까지는 그룹화/정렬이 서로 함께 작동했습니다. 13.2 버전에서는 정렬에서 그룹화를 분리하는 새로운 동작이 도입되었습니다. 예를 들어 그룹화를 지우면 그리드의 정렬 표현식이 지워지지 않으며 그 반대의 경우도 마찬가지입니다. 그러나 열이 정렬되고 그룹화된 경우 그룹화된 식이 우선합니다.

    Expand/Collapse API

    In addition to grouping expressions you can also control the expansion states for group rows. They are stored in a separate property of the igxGrid component groupingExpansionState. A group row is uniquely identified based on the field name it is created for and the value it represents for each level of grouping. This means that the signature of an expansion state interface is the following:

    export interface IGroupByKey {
        fieldName: string;
        value: any;
    }
    
    export interface IGroupByExpandState {
        hierarchy: Array<IGroupByKey>;
        expanded: boolean;
    }
    

    As with groupingExpressions, setting a list of IGroupByExpandState directly to the groupingExpansionState will change the expansion accordingly. Additionally igxGrid exposes a method that toggles a group by the group record instance.

        const groupRow = this.grid.groupsRecords.find(r => r.value === "France");
        const groupRow = this.grid.getRowByIndex(0).groupRow;
        grid.toggleGroup(groupRow);
        groupRow.expanded = false;
    

    Groups can be created expanded (default) or collapsed and the expansion states would generally only contain the state opposite to the default behavior. You can control whether groups should be created expanded or not through the groupsExpanded property.

    Select/Deselect all rows in a group API

    Selecting/Deselecting all rows in a group is available through the selectRowsInGroup and deselectRowsInGroup API methods.

    The code snippet below can be used to select all rows within a group using the group record instance selectRowsInGroup method. Additionally, the second parameter of this method is a boolean property through which you may choose whether the previous row selection will be cleared or not. The previous selection is preserved by default.

        const groupRow = this.grid.groupsRecords.find(r => r.value === "France");
        const groupRow = this.grid.getRowByIndex(0).groupRow;
        grid.selectRowsInGroup(groupRow);
    

    If you need to deselect all rows within a group programmatically, you can use the deselectRowsInGroup method.

        const groupRow = this.grid.groupsRecords.find(r => r.value === "France");
        const groupRow = this.grid.getRowByIndex(0).groupRow;
        grid.deselectRowsInGroup(groupRow);
    

    Templating

    Group Row Templates

    확장/축소 UI를 제외한 그룹 행은 완전히 템플릿화 가능합니다. 기본적으로 그룹화 아이콘을 렌더링하고 해당 아이콘이 나타내는 필드 이름과 값을 표시합니다. 렌더링되는 그룹화 레코드 템플릿에는 다음과 같은 서명이 있습니다.

    export interface IGroupByRecord {
        expression: ISortingExpression;
        level: number;
        records: GroupedRecords;
        value: any;
        groupParent: IGroupByRecord;
        groups?: IGroupByRecord[];
    }
    

    예를 들어, 다음 템플릿은 그룹 행 요약을 더욱 자세하게 만듭니다.

    <ng-template igxGroupByRow let-groupRow>
        <span>Total items with value: {{ groupRow.value }} are {{ groupRow.records.length }}</span>
    </ng-template>
    

    Group Row Selector Templates

    As mentioned above the group row except for the expand/collapse UI is fully templatable. To create a custom Group By row selector template within the Grid, declare an <ng-template> with igxGroupByRowSelector directive. From the template, you can access the implicitly provided context variable, with properties that give you information about the Group By row's state.

    The selectedCount property shows how many of the group records are currently selected while totalCount shows how many records belong to the group.

    <ng-template igxGroupByRowSelector let-groupByRowContext>
        {{ groupByRowContext.selectedCount }} / {{ groupByRowContext.totalCount  }}
    </ng-template>
    

    The groupRow property returns a reference to the group row.

    <ng-template igxGroupByRowSelector let-groupByRowContext>
        <div (click)="handleGroupByRowSelectorClick($event, groupByRowContext.groupRow)">Handle groupRow</div>
    </ng-template>
    

    The selectedCount and totalCount properties can be used to determine if the Group By row selector should be checked or indeterminate (partially selected).

    <igx-grid #grid [data]="gridData" primaryKey="ProductID" rowSelection="multiple">
        <!-- ... -->
        <ng-template igxGroupByRowSelector let-context>
            <igx-checkbox
                [checked]=" context.selectedCount > 0 && context.selectedCount === context.totalCount"
                [indeterminate]="context.selectedCount > 0 && context.selectedCount !== context.totalCount">
            </igx-checkbox>
        </ng-template>
    </igx-grid>
    

    Angular Grid Group By with Paging

    그룹 행은 데이터 행과 함께 페이징 프로세스에 참여합니다. 각 페이지의 페이지 크기에 포함됩니다. 축소된 행은 페이징 프로세스에 포함되지 않습니다. 확장 또는 축소 작업을 수행하면 Paging이 페이지 수를 다시 계산하고 필요한 경우 페이지 인덱스를 조정합니다. 여러 페이지에 걸쳐 있는 그룹은 페이지 간에 분할됩니다. 그룹 행은 시작하는 페이지에만 표시되며 후속 페이지에서는 반복되지 않습니다. 그룹 행의 요약 정보는 전체 그룹을 기준으로 계산되며 Paging의 영향을 받지 않습니다.

    Angular group by with paging example

    Group By with Summaries

    Group By와 요약 간의 통합은 요약 항목에 설명되어 있습니다.

    Keyboard Navigation

    그룹화 UI는 다음과 같은 키보드 상호 작용을 지원합니다.

    • 그룹 행의 경우(포커스는 행 또는 확장/축소 셀에 있어야 함)

      • ALT + RIGHT- 그룹 확장
      • ALT + LEFT- 그룹 축소
      • SPACE-rowSelection 속성이 다중으로 설정된 경우 그룹의 모든 행을 선택합니다.
    • For group igxChip components in the group by area (focus should be on the chip)

      • SHIFT + LEFT- 가능한 경우 그룹화 순서를 변경하여 초점을 맞춘 칩을 왼쪽으로 이동합니다.
      • SHIFT + 오른쪽- 가능한 경우 그룹화 순서를 변경하여 초점을 맞춘 칩을 오른쪽으로 이동합니다.
      • SPACE- 정렬 방향을 변경합니다.
      • DELETE- 필드 그룹을 해제합니다.
      • 칩의 개별 요소도 초점을 맞출 수 있으며 키를 사용하여 상호 작용할 수 있습니다 ENTER.

    Angular Grid Custom Group By

    igxGrid를 사용하면 열별 또는 그룹화 표현식별로 사용자 정의 그룹화를 정의할 수 있으며, 이는 사용자 정의 조건을 기반으로 그룹화를 제공합니다. 이는 복잡한 개체별로 그룹화해야 하거나 기타 애플리케이션별 시나리오에 유용합니다.

    Note

    In order to implement custom grouping the data first needs to be sorted appropriately. Due to this you may also need to apply a custom sorting strategy that extends the base DefaultSortingStrategy. After the data is sorted the custom groups can be determined by specifying a groupingComparer for the column or for the specific grouping expression.

    The sample below demonstrates custom grouping by Date, where the date values are sorted and grouped by Day, Week, Month or Year based on user-selected grouping mode.

    Angular custom group by example

    The sample defines custom sorting strategies for the different date conditions. Each custom strategy extends the base DefaultSortingStrategy and defines the compareValues method, which is the custom compare function used when sorting the values. Additionally it extracts the values from the date needed for the comparison.

    class BaseSortingStrategy extends DefaultSortingStrategy {
    
        public getParsedDate(date: any) {
            return {
                day: date.getDay(),
                month: date.getMonth() + 1,
                year: date.getFullYear()
            };
        }
    
        compareValues(a: any, b: any) {
            const dateA = this.getParsedDate(a);
            const dateB = this.getParsedDate(b);
            return dateA.year < dateB.year ?
                -1 : dateA.year > dateB.year ?
                1 : dateA.month  < dateB.month ?
                -1 : dateA.month > dateB.month ?
                1 : 0;
        }
    }
    
    class DaySortingStrategy extends BaseSortingStrategy {
        compareValues(a: any, b: any) {
            const dateA = this.getParsedDate(a);
            const dateB = this.getParsedDate(b);
            return dateA.year < dateB.year ?
                -1 : dateA.year > dateB.year ?
                1 : dateA.month  < dateB.month ?
                -1 : dateA.month > dateB.month ?
                1 : dateA.day < dateB.day ?
                -1 : dateA.day > dateB.day ?
                1 : 0;
        }
    }
    
    class WeekSortingStrategy extends BaseSortingStrategy {
    
        public getWeekOfDate(a: any) {
           return parseInt(new DatePipe("en-US").transform(a, 'w'), 10);
        }
    
        compareValues(a: any, b: any) {
            const dateA = this.getParsedDate(a);
            const dateB = this.getParsedDate(b);
            const weekA = this.getWeekOfDate(a);
            const weekB = this.getWeekOfDate(b);
            return dateA.year < dateB.year ?
                -1 : dateA.year > dateB.year ?
                1 : weekA < weekB ?
                -1 : weekA > weekB ?
                1 : 0;
        }
    }
    

    A groupingComparer function is defined for the grouping expressions, which determines the items belonging to the same group based on the selected grouping mode. Values in the sorted data for which this function returns 0 are marked as part of the same group.

     groupingComparer: (a, b) => {
        const dateA = this.sortingStrategy.getParsedDate(a);
        const dateB = this.sortingStrategy.getParsedDate(b);
        if (this.groupByMode === 'Month') {
            return dateA.month === dateB.month ? 0 : -1;
        } else if (this.groupByMode === "Year") {
            return dateA.year === dateB.year ? 0 : -1;
        } else if (this.groupByMode === "Week") {
            return this.sortingStrategy.getWeekOfDate(a) === this.sortingStrategy.getWeekOfDate(b) ? 0 : -1;
        }
        return dateA.day === dateB.day && dateA.month === dateB.month ? 0 : -1;
    }
    

    From version 15.1.0, you can also use the built-in sorting strategy GroupMemberCountSortingStrategy to sort items based on members count.

    public sortByGroup() {
            const expressions = this.grid1.groupingExpressions;
            if (expressions.length) {
                const fieldName = expressions[0].fieldName;
                const dir = expressions[0].dir === SortingDirection.Asc ? SortingDirection.Desc : SortingDirection.Asc;
                this.grid1.groupBy({ fieldName, dir, ignoreCase: false, strategy: GroupMemberCountSortingStrategy.instance() });
            }
        }
    

    스타일링

    The igxGrid allows styling through the Ignite UI for Angular Theme Library. The grid's grid-theme exposes a wide variety of properties, which allow the customization of all the features of the grid.

    아래 단계에서는 그리드의 Group By 스타일을 사용자 정의하는 단계를 진행합니다.

    Importing global theme

    To begin the customization of the Group By feature, you need to import the index file, where all styling functions and mixins are located.

    @use "igniteui-angular/theming" as *;
    
    // IMPORTANT: Prior to Ignite UI for Angular version 13 use:
    // @import '~igniteui-angular/lib/core/styles/themes/index';
    

    Defining custom theme

    Next, create a new theme, that extends the grid-theme and accepts the parameters, required to customize the Group By as desired. You also need to extend the chip-theme, because it's used in the Group By feature.

    
    $custom-theme: grid-theme(
      $group-row-background: #494949,
      $group-row-selected-background: #383838,
      $group-label-column-name-text: #f8f8f8,
      $group-label-icon: #ffcd0f,
      $group-label-text: #f8f8f8,
      $group-count-background: #ffcd0f,
      $group-count-text-color: #000,
      $expand-icon-color: #ffcd0f,
      $expand-icon-hover-color: rgb(223, 181, 13),
      $cell-active-border-color: #ffcd0f,
      $row-selected-background: #fff6d3,
      $row-selected-text-color: #000,
      $drop-indicator-color: #ffcd0f
    );
    
    /* Chip theme will style the chips in the Group By area */
    $custom-chips-theme: chip-theme(
      $background: #494949,
      $text-color: #f8f8f8,
      $hover-text-color: #e7e7e7
    );
    

    Defining a custom color palette

    In the approach that we described above, the color values were hardcoded. Alternatively, you can achieve greater flexibility, using the palette and color functions. palette generates a color palette, based on provided primary, secondary and surface colors.

    $black-color: #292826;
    $yellow-color: #ffcd0f;
    $grey-color: #efefef;
    
    $custom-palette: palette(
      $primary: $black-color, 
      $secondary: $yellow-color, 
      $surface: $grey-color
    );
    

    After a custom palette has been generated, the color function can be used to obtain different varieties of the primary and the secondary colors.

    $custom-theme: grid-theme(
      $group-row-background: color($custom-palette, "primary", 300),
      $group-row-selected-background: color($custom-palette, "primary", 400),
      $group-label-column-name-text:contrast-color($custom-palette, "primary", 500),
      $group-label-icon: color($custom-palette, "secondary", 600),
      $group-label-text:contrast-color($custom-palette, "primary", 500),
      $group-count-background: color($custom-palette, "secondary", 600),
      $group-count-text-color: color($custom-palette, "primary", 400),
      $expand-icon-color: color($custom-palette, "secondary", 600),
      $expand-icon-hover-color: color($custom-palette, "secondary", 300),
      $cell-active-border-color: color($custom-palette, "secondary", 600)
    );
    
    $custom-chips-theme: chip-theme(
      $background: color($custom-palette, "primary", 300),
      $text-color:contrast-color($custom-palette, "primary", 500),
      $hover-text-color:contrast-color($custom-palette, "primary", 600)
    );
    

    Defining custom schemas

    You can go even further and build flexible structure that has all the benefits of a schema. The schema is the recipe of a theme. Extend one of the two predefined schemas, that are provided for every component. In our case, we would use light-grid.

    $custom-grid-schema: extend(
      map.get('grid', $light-material-schema),
      (
        group-row-background: (color:('secondary', 100)),
        group-row-selected-background: (color:('primary', 400)),
        group-label-column-name-text: (color:('primary', 600)),
        group-label-icon: (color:('primary', 600)),
        group-label-text: (color:('secondary', 700)),
        group-count-background: (color:('primary', 600)),
        group-count-text-color: (color:('secondary', 400)),
        expand-icon-color: (color:('primary', 600)),
        expand-icon-hover-color: (color:('primary', 400))
      )
    );
    

    In order for the custom schema to be applied, either (light or dark) globals has to be extended. The whole process is actually supplying a component with a custom schema and adding it to the respective component theme afterwards.

    $my-custom-schema: extend(
      $light-material-schema, 
      ( 
        grid: $custom-grid-schema
      )
    );
    
    $custom-theme: grid-theme(
      $palette: $custom-palette,
      $schema: $my-custom-schema
    );
    

    Applying the custom theme

    The easiest way to apply your theme is with a sass @include statement in the global styles file:

    @include css-vars($custom-theme);
    @include css-vars($custom-chips-theme);
    

    Scoped component theme

    In order for the custom theme to affect only specific component, you can move all of the styles you just defined from the global styles file to the custom component's style file (including the import of the index file).

    이렇게 하면 Angular의 ViewEncapsulation 덕분에 사용자 정의 구성 요소에만 스타일이 적용됩니다.

    Note

    If the component is using an Emulated ViewEncapsulation, it is necessary to penetrate this encapsulation using ::ng-deep in order to style the components which are inside the grid.

    In our example, we need to use ::ng-deep for our chip theme:

    @include css-vars($custom-theme);
    
    :host {
      ::ng-deep {
        @include chip($custom-chips-theme);
      }
    }
    

    Demo

    Note

    샘플은 선택된 글로벌 테마Change Theme의 영향을 받지 않습니다.

    Known Limitations

    한정 설명
    그룹화된 열의 최대 개수는 10개입니다. 10개 이상의 열이 그룹화되면 오류가 발생합니다.

    API References

    Additional Resources

    우리 커뮤니티는 활동적이며 항상 새로운 아이디어를 환영합니다.