Data grids provide a SKY UX-themed layout for tabular data. Combine data grids with data managers to allow users to manipulate larger data sets.
Use data grids to display large amounts of data when users need to compare values between rows or scan for specific values or outliers.
Don't use data grids to display content that requires a complex layout. To display multiple templated columns, visual content such as graphs or images, or content that users are likely to view in small viewports such as phones, use repeaters instead.
To display actions that affect individual items in the data grid, use context-menu dropdowns.
When you need to supplement a data grid column header with additional information but don't require persistent inline help, you can place a help button beside the header to invoke contextual user assistance.
To let users narrow down their list of items to a set they are interested in using structured criteria, use filtering.
To let users select and perform actions on multiple rows, use data manager and enable multiselect.
Only use multiselect when the data grid container is a page, full-page modal, or flyout. Don't use multiselect with data grids in tiles, page sections, or other containers that can flow off the screen.
To let users select or clear all rows and view only selected items, include the multiselect toolbar. To display actions that users can perform on selected rows, display the summary action bar below the data grid.
Make column headers sortable. Sort by the leftmost column or the primary record column in the data grid. Always indicate a data grid’s sort order.
Only include the sort button in the toolbar if the list includes other views in addition to the data grid (e.g., repeater) or it contains templated columns where users need to sort by another property in that column.
To display composite information instead of single values, use templated columns.
The fit determines how to place data grids within their containers. If a container is wider than the data grid, the fit stretches the data grid to fill the container.
The scroll
width
scroll
width
To reorder columns, users can drag and drop them.
To resize columns, users can select and drag.
Data grids perform poorly in small viewports, such as mobile devices. Columns in fixed-width grids shrink to the point of being unreadable, and grids with horizontal scrollbars require a great deal of effort to view content. If you expect users to view content in small viewports, use repeaters or another pattern instead.
SkyAgGridModule
@skyux/ag-grid
npm install --save-exact @skyux/ag-grid
In addition to the @skyux/ag-grid
ag-grid-angular
ag-grid-community
To add the SKY UX styles for AG Grid to your SPA, run ng g @skyux/packages:add-ag-grid-styles --project my-app
"@skyux/ag-grid/css/sky-ag-grid.css"
styles
angular.json
The sky-ag-grid-wrapper
sky-ag-grid-wrapper
ag-grid-angular
If you use a data grid within a data manager component, the skyAgGridDataManagerAdapter
sky-data-view
Other properties of the data state, such as filters and applying the sort, still need to be implemented for each use.
For full control over a SKY UX component rendered in a cell, you can create your own cell renderer and place the component inside it. For example, to include a context menu in your grid, you create a cell renderer and place the context menu in the cell renderer. See the demo for an example.
[skyAgGridRowDelete]
Input | Description |
---|---|
rowDeleteIds?: string[] | The IDs of the data in the rows where the inline delete appears. Optional. |
Event | Description |
---|---|
rowDeleteCancel: EventEmitter<SkyAgGridRowDeleteCancelArgs> | Emits a |
rowDeleteConfirm: EventEmitter<SkyAgGridRowDeleteConfirmArgs> | Emits a |
rowDeleteIdsChange: EventEmitter<string[]> | Emits when the list of ids of the data in the rows where inline deletes are shown changes. |
To display a help button beside the column group header, create a component that includes the help button element,
such as sky-help-inline
, include a SkyAgGridHeaderGroupInfo
parameter in the constructor to access the column
group information, such as display name, and add the component to the headerComponentParams.inlineHelpComponent
property of the column definition.
Property | Description |
---|---|
columnGroup?: ColumnGroup | Optional. |
context?: any | Application context as set on Optional. |
displayName?: string | Optional. |
To display a help button beside the column header, create a component that includes the help button element,
such as sky-help-inline
, include a SkyAgGridHeaderInfo
parameter in the constructor to access the column
information, such as display name, and add the component to the headerComponentParams.inlineHelpComponent
property
of the column definition.
Property | Description |
---|---|
column?: Column<any> | Optional. |
context?: any | Application context as set on Optional. |
displayName?: string | Optional. |
SkyAgGridService
provides methods to get AG Grid gridOptions
to ensure grids match SKY UX functionality. The gridOptions
can be overridden, and include registered SKY UX column types.
Methods | Description |
---|---|
getEditableGridOptions() | Returns AG Grid
|
getGridOptions() | Returns AG Grid
|
| Deprecated. The
Returnsnumber |
Static methods | Description | ||||
---|---|---|---|---|---|
SkyAgGridCellEditorUtils.getEditorInitialAction() | Gets the initial action that a cell editor should take when initialized.
Parameters
Returns | ||||
SkyAgGridCellEditorUtils.subtractOrZero() |
Parameters
Returnsnumber |
interface SkyAgGridAutocompleteProperties {
data?: unknown[];
debounceTime?: number;
descriptorProperty?: string;
propertiesToSearch?: string[];
search?: (searchText: string, data?: unknown[]) => unknown[] | Promise<unknown[]>;
searchFilters?: ((searchText: string, item: unknown) => boolean)[];
searchResultTemplate?: TemplateRef<unknown>;
searchResultsLimit?: number;
searchTextMinimumCharacters?: number;
selectionChange?: (event: SkyAutocompleteSelectionChange) => void;
}
Property | Description | ||||
---|---|---|---|---|---|
data?: unknown[] | |||||
debounceTime?: number | |||||
descriptorProperty?: string | |||||
propertiesToSearch?: string[] | |||||
search?: (searchText: string, data?: unknown[]) => unknown[] | Promise<unknown[]> | Parameters
Returnsunknown[] | Promise<unknown[]> | ||||
searchFilters?: ((searchText: string, item: unknown) => boolean)[] | Parameters
Returnsboolean | ||||
searchResultTemplate?: TemplateRef<unknown> | |||||
searchResultsLimit?: number | |||||
searchTextMinimumCharacters?: number | |||||
selectionChange?: (event: SkyAutocompleteSelectionChange) => void |
interface SkyAgGridCurrencyProperties {
currencySymbol?: string;
decimalPlaces?: string | number;
negativeBracketsTypeOnBlur?: '(,)' | '[,]' | '<,>' | '{,}' | '〈,〉' | '「,」' | '⸤,⸥' | '⟦,⟧' | '‹,›' | '«,»';
}
Property | Description |
---|---|
currencySymbol?: string | |
decimalPlaces?: string | number | |
negativeBracketsTypeOnBlur?: '(,)' | '[,]' | '<,>' | '{,}' | '〈,〉' | '「,」' | '⸤,⸥' | '⟦,⟧' | '‹,›' | '«,»' |
interface SkyAgGridDatepickerProperties {
dateFormat?: string;
disabled?: boolean;
maxDate?: Date;
minDate?: Date;
skyDatepickerNoValidate?: boolean;
startingDay?: number;
}
Property | Description |
---|---|
dateFormat?: string | |
disabled?: boolean | |
maxDate?: Date | |
minDate?: Date | |
skyDatepickerNoValidate?: boolean | |
startingDay?: number |
Interface to use for the
headerGroupComponentParams
property on ColDef
.
interface SkyAgGridHeaderGroupParams {
inlineHelpComponent?: Type<unknown>;
}
Property | Description |
---|---|
inlineHelpComponent?: Type<unknown> |
Interface to use for the
headerComponentParams
property on ColDef
.
interface SkyAgGridHeaderParams {
inlineHelpComponent?: Type<unknown>;
}
Property | Description |
---|---|
inlineHelpComponent?: Type<unknown> |
interface SkyAgGridLookupProperties {
addClick?: (args: SkyLookupAddClickEventArgs) => void;
ariaLabel?: string;
ariaLabelledBy?: string;
autocompleteAttribute?: string;
data?: unknown[];
debounceTime?: number;
descriptorProperty?: string;
disabled?: boolean;
enableShowMore?: boolean;
idProperty?: string;
placeholderText?: string;
propertiesToSearch?: string[];
search?: SkyAutocompleteSearchFunction;
searchAsync?: (args: SkyAutocompleteSearchAsyncArgs) => void;
searchFilters?: SkyAutocompleteSearchFunctionFilter[];
searchResultTemplate?: TemplateRef<unknown>;
searchResultsLimit?: number;
searchTextMinimumCharacters?: number;
selectMode?: SkyLookupSelectModeType;
showAddButton?: boolean;
showMoreConfig?: SkyLookupShowMoreConfig;
wrapperClass?: string;
}
Property | Description |
---|---|
addClick?: (args: SkyLookupAddClickEventArgs) => void | |
ariaLabel?: string | |
ariaLabelledBy?: string | |
| Deprecated. |
data?: unknown[] | |
debounceTime?: number | |
descriptorProperty?: string | |
disabled?: boolean | |
enableShowMore?: boolean | |
idProperty?: string | |
placeholderText?: string | |
propertiesToSearch?: string[] | |
search?: SkyAutocompleteSearchFunction | |
searchAsync?: (args: SkyAutocompleteSearchAsyncArgs) => void | |
searchFilters?: SkyAutocompleteSearchFunctionFilter[] | |
searchResultTemplate?: TemplateRef<unknown> | |
searchResultsLimit?: number | |
searchTextMinimumCharacters?: number | |
selectMode?: SkyLookupSelectModeType | |
showAddButton?: boolean | |
showMoreConfig?: SkyLookupShowMoreConfig | |
wrapperClass?: string |
interface SkyAgGridValidatorProperties {
validator?: (value: unknown, data?: unknown, rowIndex?: number) => boolean;
validatorMessage?: string | (value: unknown, data?: unknown, rowIndex?: number) => string;
}
Property | Description | ||||||
---|---|---|---|---|---|---|---|
validator?: (value: unknown, data?: unknown, rowIndex?: number) => boolean | Parameters
Returnsboolean | ||||||
validatorMessage?: string | (value: unknown, data?: unknown, rowIndex?: number) => string |
interface SkyAutocompleteProperties {
data?: unknown[];
debounceTime?: number;
descriptorProperty?: string;
propertiesToSearch?: string[];
search?: (searchText: string, data?: unknown[]) => unknown[] | Promise<unknown[]>;
searchFilters?: ((searchText: string, item: unknown) => boolean)[];
searchResultTemplate?: TemplateRef<unknown>;
searchResultsLimit?: number;
searchTextMinimumCharacters?: number;
selectionChange?: (event: SkyAutocompleteSelectionChange) => void;
}
Property | Description | ||||
---|---|---|---|---|---|
data?: unknown[] | |||||
debounceTime?: number | |||||
descriptorProperty?: string | |||||
propertiesToSearch?: string[] | |||||
search?: (searchText: string, data?: unknown[]) => unknown[] | Promise<unknown[]> | Parameters
Returnsunknown[] | Promise<unknown[]> | ||||
searchFilters?: ((searchText: string, item: unknown) => boolean)[] | Parameters
Returnsboolean | ||||
searchResultTemplate?: TemplateRef<unknown> | |||||
searchResultsLimit?: number | |||||
searchTextMinimumCharacters?: number | |||||
selectionChange?: (event: SkyAutocompleteSelectionChange) => void |
interface SkyDatepickerProperties {
dateFormat?: string;
disabled?: boolean;
maxDate?: Date;
minDate?: Date;
skyDatepickerNoValidate?: boolean;
startingDay?: number;
}
Property | Description |
---|---|
dateFormat?: string | |
disabled?: boolean | |
maxDate?: Date | |
minDate?: Date | |
skyDatepickerNoValidate?: boolean | |
startingDay?: number |
interface SkyGetGridOptionsArgs {
gridOptions: GridOptions<any>;
dateFormat?: string;
locale?: string;
}
Property | Description |
---|---|
gridOptions: GridOptions<any> | The AG Grid |
dateFormat?: string | The format to use for formatting date strings in the |
locale?: string | The locale for location-specific formatting, such as date values for the |
The initial action that a cell editor should take when initialized.
Member | Description |
---|---|
Delete | The cell should be cleared. |
Highlighted | The cell value should be highlighted. |
Replace | The cell value should be replaced with the initializing value. |
Untouched | The cell should not be modified and the cursor is placed at the end of the value. |
These column types can be used by setting the AG Grid column definition type
to one of the following values.
Any SKY UX component can be made into a cell editor or cell renderer component. If you would like to use a component that does not have a column definition yet, please consider contributing it to the SKY UX data entry grid module, or file an issue in the @skyux/ag-grid
repo.
Member | Description |
---|---|
Autocomplete | Edit mode
|
Currency | Edit mode
|
CurrencyValidator | Edit and read-only modes
|
Date | Edit mode
|
Lookup | Edit mode
|
Number | Edit mode
|
NumberValidator | Edit and read-only modes
|
RowSelector | Edit and read-only modes
|
Template | Read-only mode
|
Text | Edit mode
|
Validator | Edit and read-only modes
|
<sky-ag-grid-wrapper>
<ag-grid-angular [gridOptions]="gridOptions" [rowData]="gridData" />
</sky-ag-grid-wrapper>
export interface AutocompleteOption {
id: string;
name: string;
}
export const DEPARTMENTS = [
{
id: '1',
name: 'Marketing',
},
{
id: '2',
name: 'Sales',
},
{
id: '3',
name: 'Engineering',
},
{
id: '4',
name: 'Customer Support',
},
];
export const JOB_TITLES: Record<string, AutocompleteOption[]> = {
Marketing: [
{
id: '1',
name: 'Social Media Coordinator',
},
{
id: '2',
name: 'Blog Manager',
},
{
id: '3',
name: 'Events Manager',
},
],
Sales: [
{
id: '4',
name: 'Business Development Representative',
},
{
id: '5',
name: 'Account Executive',
},
],
Engineering: [
{
id: '6',
name: 'Software Engineer',
},
{
id: '7',
name: 'Senior Software Engineer',
},
{
id: '8',
name: 'Principal Software Engineer',
},
{
id: '9',
name: 'UX Designer',
},
{
id: '10',
name: 'Product Manager',
},
],
'Customer Support': [
{
id: '11',
name: 'Customer Support Representative',
},
{
id: '12',
name: 'Account Manager',
},
{
id: '13',
name: 'Customer Support Specialist',
},
],
};
export interface AgGridDemoRow {
selected?: boolean;
name: string;
age: number;
startDate: Date;
endDate?: Date;
department: AutocompleteOption;
jobTitle?: AutocompleteOption;
}
export const AG_GRID_DEMO_DATA = [
{
name: 'Billy Bob',
age: 55,
startDate: new Date('12/1/1994'),
department: DEPARTMENTS[3],
jobTitle: JOB_TITLES['Customer Support'][1],
},
{
name: 'Jane Deere',
age: 33,
startDate: new Date('7/15/2009'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][2],
},
{
name: 'John Doe',
age: 38,
startDate: new Date('9/1/2017'),
endDate: new Date('9/30/2017'),
department: DEPARTMENTS[1],
},
{
name: 'David Smith',
age: 51,
startDate: new Date('1/1/2012'),
endDate: new Date('6/15/2018'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][4],
},
{
name: 'Emily Johnson',
age: 41,
startDate: new Date('1/15/2014'),
department: DEPARTMENTS[0],
jobTitle: JOB_TITLES['Marketing'][2],
},
{
name: 'Nicole Davidson',
age: 22,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][0],
},
{
name: 'Carl Roberts',
age: 23,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][3],
},
];
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { AgGridDemoRow } from './data';
@Component({
standalone: true,
selector: 'app-context-menu',
templateUrl: './context-menu.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SkyDropdownModule],
})
export class ContextMenuComponent implements ICellRendererAngularComp {
public contextMenuAriaLabel = '';
public deleteAriaLabel = '';
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
#name: string | undefined;
public agInit(params: ICellRendererParams<AgGridDemoRow>): void {
this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
this.moreInfoAriaLabel = `More info for ${this.#name}`;
}
public refresh(): boolean {
return false;
}
protected actionClicked(action: string): void {
alert(`${action} clicked for ${this.#name}`);
}
}
<sky-dropdown buttonType="context-menu" [label]="contextMenuAriaLabel">
<sky-dropdown-menu>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="deleteAriaLabel"
(click)="actionClicked('Delete')"
>
Delete
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="markInactiveAriaLabel"
(click)="actionClicked('Mark inactive')"
>
Mark inactive
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="moreInfoAriaLabel"
(click)="actionClicked('More info')"
>
More info
</button>
</sky-dropdown-item>
</sky-dropdown-menu>
</sky-dropdown>
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { SkyAgGridModule, SkyAgGridService, SkyCellType } from '@skyux/ag-grid';
import { SkyDataManagerService } from '@skyux/data-manager';
import { AgGridModule } from 'ag-grid-angular';
import {
ColDef,
GridApi,
GridOptions,
GridReadyEvent,
ValueFormatterParams,
} from 'ag-grid-community';
import { ContextMenuComponent } from './context-menu.component';
import { AG_GRID_DEMO_DATA, AgGridDemoRow } from './data';
@Component({
standalone: true,
selector: 'app-demo',
templateUrl: './demo.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [SkyDataManagerService],
imports: [AgGridModule, SkyAgGridModule],
})
export class DemoComponent {
protected gridData = AG_GRID_DEMO_DATA;
protected gridOptions: GridOptions;
#columnDefs: ColDef[] = [
{
colId: 'context',
maxWidth: 50,
sortable: false,
cellRenderer: ContextMenuComponent,
},
{
field: 'name',
headerName: 'Name',
},
{
field: 'age',
headerName: 'Age',
type: SkyCellType.Number,
maxWidth: 60,
},
{
field: 'startDate',
headerName: 'Start date',
type: SkyCellType.Date,
sort: 'asc',
},
{
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
valueFormatter: (params: ValueFormatterParams<AgGridDemoRow, Date>) =>
this.#endDateFormatter(params),
},
{
field: 'department',
headerName: 'Department',
type: SkyCellType.Autocomplete,
},
{
field: 'jobTitle',
headerName: 'Title',
type: SkyCellType.Autocomplete,
},
];
#gridApi: GridApi | undefined;
readonly #agGridSvc = inject(SkyAgGridService);
constructor() {
const gridOptions: GridOptions = {
columnDefs: this.#columnDefs,
onGridReady: (gridReadyEvent): void => {
this.onGridReady(gridReadyEvent);
},
rowSelection: 'single',
};
this.gridOptions = this.#agGridSvc.getGridOptions({
gridOptions,
});
}
public onGridReady(gridReadyEvent: GridReadyEvent): void {
this.#gridApi = gridReadyEvent.api;
this.#gridApi.sizeColumnsToFit();
}
#endDateFormatter(params: ValueFormatterParams<AgGridDemoRow, Date>): string {
return params.value
? params.value.toLocaleDateString('en-us', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
: 'N/A';
}
}
<sky-ag-grid-wrapper>
<ag-grid-angular [gridOptions]="gridOptions" [rowData]="gridData" />
</sky-ag-grid-wrapper>
export interface AutocompleteOption {
id: string;
name: string;
}
export const DEPARTMENTS = [
{
id: '1',
name: 'Marketing',
},
{
id: '2',
name: 'Sales',
},
{
id: '3',
name: 'Engineering',
},
{
id: '4',
name: 'Customer Support',
},
];
export const JOB_TITLES: Record<string, AutocompleteOption[]> = {
Marketing: [
{
id: '1',
name: 'Social Media Coordinator',
},
{
id: '2',
name: 'Blog Manager',
},
{
id: '3',
name: 'Events Manager',
},
],
Sales: [
{
id: '4',
name: 'Business Development Representative',
},
{
id: '5',
name: 'Account Executive',
},
],
Engineering: [
{
id: '6',
name: 'Software Engineer',
},
{
id: '7',
name: 'Senior Software Engineer',
},
{
id: '8',
name: 'Principal Software Engineer',
},
{
id: '9',
name: 'UX Designer',
},
{
id: '10',
name: 'Product Manager',
},
],
'Customer Support': [
{
id: '11',
name: 'Customer Support Representative',
},
{
id: '12',
name: 'Account Manager',
},
{
id: '13',
name: 'Customer Support Specialist',
},
],
};
export interface AgGridDemoRow {
selected?: boolean;
name: string;
age: number;
startDate: Date;
endDate?: Date;
department: AutocompleteOption;
jobTitle?: AutocompleteOption;
}
export const AG_GRID_DEMO_DATA = [
{
selected: false,
name: 'Billy Bob',
age: 55,
startDate: new Date('12/1/1994'),
department: DEPARTMENTS[3],
jobTitle: JOB_TITLES['Customer Support'][1],
},
{
selected: false,
name: 'Jane Deere',
age: 33,
startDate: new Date('7/15/2009'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][2],
},
{
selected: false,
name: 'John Doe',
age: 38,
startDate: new Date('9/1/2017'),
endDate: new Date('9/30/2017'),
department: DEPARTMENTS[1],
},
{
selected: false,
name: 'David Smith',
age: 51,
startDate: new Date('1/1/2012'),
endDate: new Date('6/15/2018'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][4],
},
{
selected: true,
name: 'Emily Johnson',
age: 41,
startDate: new Date('1/15/2014'),
department: DEPARTMENTS[0],
jobTitle: JOB_TITLES['Marketing'][2],
},
{
selected: false,
name: 'Nicole Davidson',
age: 22,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][0],
},
{
selected: false,
name: 'Carl Roberts',
age: 23,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][3],
},
];
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { AgGridDemoRow } from './data';
@Component({
standalone: true,
selector: 'app-context-menu',
templateUrl: './context-menu.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SkyDropdownModule],
})
export class ContextMenuComponent implements ICellRendererAngularComp {
public contextMenuAriaLabel = '';
public deleteAriaLabel = '';
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
#name: string | undefined;
public agInit(params: ICellRendererParams<AgGridDemoRow>): void {
this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
this.moreInfoAriaLabel = `More info for ${this.#name}`;
}
public refresh(): boolean {
return false;
}
protected actionClicked(action: string): void {
alert(`${action} clicked for ${this.#name}`);
}
}
<sky-dropdown buttonType="context-menu" [label]="contextMenuAriaLabel">
<sky-dropdown-menu>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="deleteAriaLabel"
(click)="actionClicked('Delete')"
>
Delete
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="markInactiveAriaLabel"
(click)="actionClicked('Mark inactive')"
>
Mark inactive
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="moreInfoAriaLabel"
(click)="actionClicked('More info')"
>
More info
</button>
</sky-dropdown-item>
</sky-dropdown-menu>
</sky-dropdown>
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { SkyAgGridModule, SkyAgGridService, SkyCellType } from '@skyux/ag-grid';
import { SkyDataManagerService } from '@skyux/data-manager';
import { AgGridModule } from 'ag-grid-angular';
import {
ColDef,
GridApi,
GridOptions,
GridReadyEvent,
ValueFormatterParams,
} from 'ag-grid-community';
import { of } from 'rxjs';
import { ContextMenuComponent } from './context-menu.component';
import { AG_GRID_DEMO_DATA, AgGridDemoRow } from './data';
@Component({
standalone: true,
selector: 'app-demo',
templateUrl: './demo.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [SkyDataManagerService],
imports: [AgGridModule, SkyAgGridModule],
})
export class DemoComponent {
protected gridData = AG_GRID_DEMO_DATA;
protected gridOptions: GridOptions;
#columnDefs: ColDef[] = [
{
field: 'selected',
type: SkyCellType.RowSelector,
cellRendererParams: {
// Could be a SkyAppResourcesService.getString call that returns an observable.
label: (data: AgGridDemoRow) => of(`Select ${data.name}`),
},
},
{
colId: 'context',
maxWidth: 50,
sortable: false,
cellRenderer: ContextMenuComponent,
},
{
field: 'name',
headerName: 'Name',
},
{
field: 'age',
headerName: 'Age',
type: SkyCellType.Number,
maxWidth: 60,
},
{
field: 'startDate',
headerName: 'Start date',
type: SkyCellType.Date,
sort: 'asc',
},
{
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
valueFormatter: (params: ValueFormatterParams<AgGridDemoRow, Date>) =>
this.#endDateFormatter(params),
},
{
field: 'department',
headerName: 'Department',
type: SkyCellType.Autocomplete,
},
{
field: 'jobTitle',
headerName: 'Title',
type: SkyCellType.Autocomplete,
},
];
#gridApi: GridApi | undefined;
readonly #agGridSvc = inject(SkyAgGridService);
constructor() {
const gridOptions: GridOptions = {
columnDefs: this.#columnDefs,
onGridReady: (gridReadyEvent): void => {
this.onGridReady(gridReadyEvent);
},
rowSelection: 'multiple',
};
this.gridOptions = this.#agGridSvc.getGridOptions({
gridOptions,
});
}
public onGridReady(gridReadyEvent: GridReadyEvent): void {
this.#gridApi = gridReadyEvent.api;
this.#gridApi.sizeColumnsToFit();
}
#endDateFormatter(params: ValueFormatterParams<AgGridDemoRow, Date>): string {
return params.value
? params.value.toLocaleDateString('en-us', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
: 'N/A';
}
}
<sky-help-inline
class="sky-control-help"
ariaLabel="Information about {{ displayName }}"
(actionClick)="onHelpClick()"
/>
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
inject,
} from '@angular/core';
import { SkyAgGridModule, SkyAgGridService, SkyCellType } from '@skyux/ag-grid';
import { SkyDataManagerService } from '@skyux/data-manager';
import { SkyToolbarModule } from '@skyux/layout';
import { SkySearchModule } from '@skyux/lookup';
import { AgGridModule } from 'ag-grid-angular';
import {
ColDef,
GridApi,
GridOptions,
GridReadyEvent,
ValueFormatterParams,
} from 'ag-grid-community';
import { of } from 'rxjs';
import { ContextMenuComponent } from './context-menu.component';
import { AG_GRID_DEMO_DATA, AgGridDemoRow } from './data';
import { InlineHelpComponent } from './inline-help.component';
@Component({
standalone: true,
selector: 'app-demo',
templateUrl: './demo.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [SkyDataManagerService],
imports: [AgGridModule, SkyAgGridModule, SkySearchModule, SkyToolbarModule],
})
export class DemoComponent {
protected gridData = AG_GRID_DEMO_DATA;
protected gridOptions: GridOptions;
protected searchText = '';
protected noRowsTemplate: string;
#columnDefs: ColDef[] = [
{
field: 'selected',
type: SkyCellType.RowSelector,
cellRendererParams: {
// Could be a SkyAppResourcesService.getString call that returns an observable.
label: (data: AgGridDemoRow) => of(`Select ${data.name}`),
},
},
{
colId: 'context',
maxWidth: 50,
sortable: false,
cellRenderer: ContextMenuComponent,
},
{
field: 'name',
headerName: 'Name',
headerComponentParams: {
inlineHelpComponent: InlineHelpComponent,
},
},
{
field: 'age',
headerName: 'Age',
type: SkyCellType.Number,
maxWidth: 60,
headerComponentParams: {
inlineHelpComponent: InlineHelpComponent,
},
},
{
field: 'startDate',
headerName: 'Start date',
type: SkyCellType.Date,
sort: 'asc',
headerComponentParams: {
inlineHelpComponent: InlineHelpComponent,
},
},
{
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
valueFormatter: (params: ValueFormatterParams<AgGridDemoRow, Date>) =>
this.#endDateFormatter(params),
headerComponentParams: {
inlineHelpComponent: InlineHelpComponent,
},
},
{
field: 'department',
headerName: 'Department',
type: SkyCellType.Autocomplete,
headerComponentParams: {
inlineHelpComponent: InlineHelpComponent,
},
},
{
field: 'jobTitle',
headerName: 'Title',
type: SkyCellType.Autocomplete,
headerComponentParams: {
inlineHelpComponent: InlineHelpComponent,
},
},
];
#gridApi: GridApi | undefined;
readonly #agGridSvc = inject(SkyAgGridService);
readonly #changeDetectorRef = inject(ChangeDetectorRef);
constructor() {
this.noRowsTemplate = `<div class="sky-font-deemphasized">No results found.</div>`;
this.gridOptions = this.#agGridSvc.getGridOptions({
gridOptions: {
columnDefs: this.#columnDefs,
onGridReady: this.onGridReady.bind(this),
},
});
this.#changeDetectorRef.markForCheck();
}
public onGridReady(gridReadyEvent: GridReadyEvent): void {
this.#gridApi = gridReadyEvent.api;
this.#gridApi.sizeColumnsToFit();
this.#changeDetectorRef.markForCheck();
}
protected searchApplied(searchText: string | void): void {
if (searchText) {
this.searchText = searchText;
} else {
this.searchText = '';
}
if (this.#gridApi) {
this.#gridApi.setQuickFilter(this.searchText);
const displayedRowCount = this.#gridApi.getDisplayedRowCount();
if (displayedRowCount > 0) {
this.#gridApi.hideOverlay();
} else {
this.#gridApi.showNoRowsOverlay();
}
}
}
#endDateFormatter(params: ValueFormatterParams<AgGridDemoRow, Date>): string {
return params.value
? params.value.toLocaleDateString('en-us', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
: 'N/A';
}
}
<sky-toolbar>
<sky-toolbar-item>
<sky-search
[debounceTime]="250"
[searchText]="searchText"
(searchApply)="searchApplied($event)"
(searchChange)="searchApplied($event)"
(searchClear)="searchApplied($event)"
/>
</sky-toolbar-item>
</sky-toolbar>
<sky-ag-grid-wrapper>
<ag-grid-angular
[gridOptions]="gridOptions"
[overlayNoRowsTemplate]="noRowsTemplate"
[rowData]="gridData"
/>
</sky-ag-grid-wrapper>
export interface AutocompleteOption {
id: string;
name: string;
}
export const DEPARTMENTS = [
{
id: '1',
name: 'Marketing',
},
{
id: '2',
name: 'Sales',
},
{
id: '3',
name: 'Engineering',
},
{
id: '4',
name: 'Customer Support',
},
];
export const JOB_TITLES: Record<string, AutocompleteOption[]> = {
Marketing: [
{
id: '1',
name: 'Social Media Coordinator',
},
{
id: '2',
name: 'Blog Manager',
},
{
id: '3',
name: 'Events Manager',
},
],
Sales: [
{
id: '4',
name: 'Business Development Representative',
},
{
id: '5',
name: 'Account Executive',
},
],
Engineering: [
{
id: '6',
name: 'Software Engineer',
},
{
id: '7',
name: 'Senior Software Engineer',
},
{
id: '8',
name: 'Principal Software Engineer',
},
{
id: '9',
name: 'UX Designer',
},
{
id: '10',
name: 'Product Manager',
},
],
'Customer Support': [
{
id: '11',
name: 'Customer Support Representative',
},
{
id: '12',
name: 'Account Manager',
},
{
id: '13',
name: 'Customer Support Specialist',
},
],
};
export interface AgGridDemoRow {
selected?: boolean;
name: string;
age: number;
startDate: Date;
endDate?: Date;
department: AutocompleteOption;
jobTitle?: AutocompleteOption;
}
export const AG_GRID_DEMO_DATA = [
{
name: 'Billy Bob',
age: 55,
startDate: new Date('12/1/1994'),
department: DEPARTMENTS[3],
jobTitle: JOB_TITLES['Customer Support'][1],
},
{
name: 'Jane Deere',
age: 33,
startDate: new Date('7/15/2009'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][2],
},
{
name: 'John Doe',
age: 38,
startDate: new Date('9/1/2017'),
endDate: new Date('9/30/2017'),
department: DEPARTMENTS[1],
},
{
name: 'David Smith',
age: 51,
startDate: new Date('1/1/2012'),
endDate: new Date('6/15/2018'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][4],
},
{
name: 'Emily Johnson',
age: 41,
startDate: new Date('1/15/2014'),
department: DEPARTMENTS[0],
jobTitle: JOB_TITLES['Marketing'][2],
},
{
name: 'Nicole Davidson',
age: 22,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][0],
},
{
name: 'Carl Roberts',
age: 23,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][3],
},
];
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { AgGridDemoRow } from './data';
@Component({
standalone: true,
selector: 'app-context-menu',
templateUrl: './context-menu.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SkyDropdownModule],
})
export class ContextMenuComponent implements ICellRendererAngularComp {
public contextMenuAriaLabel = '';
public deleteAriaLabel = '';
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
#name: string | undefined;
public agInit(params: ICellRendererParams<AgGridDemoRow>): void {
this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
this.moreInfoAriaLabel = `More info for ${this.#name}`;
}
public refresh(): boolean {
return false;
}
protected actionClicked(action: string): void {
alert(`${action} clicked for ${this.#name}`);
}
}
<sky-dropdown buttonType="context-menu" [label]="contextMenuAriaLabel">
<sky-dropdown-menu>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="deleteAriaLabel"
(click)="actionClicked('Delete')"
>
Delete
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="markInactiveAriaLabel"
(click)="actionClicked('Mark inactive')"
>
Mark inactive
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="moreInfoAriaLabel"
(click)="actionClicked('More info')"
>
More info
</button>
</sky-dropdown-item>
</sky-dropdown-menu>
</sky-dropdown>
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { SkyAgGridHeaderInfo } from '@skyux/ag-grid';
import { SkyHelpInlineModule } from '@skyux/indicators';
@Component({
standalone: true,
selector: 'app-inline-help',
templateUrl: './inline-help.component.html',
styles: [
`
:host {
display: block;
}
`,
],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SkyHelpInlineModule],
})
export class InlineHelpComponent {
protected displayName: string | undefined;
readonly #headerInfo = inject(SkyAgGridHeaderInfo);
constructor() {
this.displayName = this.#headerInfo.displayName;
}
protected onHelpClick(): void {
alert(`Help was clicked for ${this.displayName}.`);
}
}
<sky-ag-grid-wrapper>
<ag-grid-angular [gridOptions]="gridOptions" [rowData]="gridData" />
</sky-ag-grid-wrapper>
<sky-paging
[currentPage]="currentPage"
[itemCount]="gridData.length"
[pageSize]="pageSize"
(currentPageChange)="onPageChange($event)"
/>
export interface AutocompleteOption {
id: string;
name: string;
}
export const DEPARTMENTS = [
{
id: '1',
name: 'Marketing',
},
{
id: '2',
name: 'Sales',
},
{
id: '3',
name: 'Engineering',
},
{
id: '4',
name: 'Customer Support',
},
];
export const JOB_TITLES: Record<string, AutocompleteOption[]> = {
Marketing: [
{
id: '1',
name: 'Social Media Coordinator',
},
{
id: '2',
name: 'Blog Manager',
},
{
id: '3',
name: 'Events Manager',
},
],
Sales: [
{
id: '4',
name: 'Business Development Representative',
},
{
id: '5',
name: 'Account Executive',
},
],
Engineering: [
{
id: '6',
name: 'Software Engineer',
},
{
id: '7',
name: 'Senior Software Engineer',
},
{
id: '8',
name: 'Principal Software Engineer',
},
{
id: '9',
name: 'UX Designer',
},
{
id: '10',
name: 'Product Manager',
},
],
'Customer Support': [
{
id: '11',
name: 'Customer Support Representative',
},
{
id: '12',
name: 'Account Manager',
},
{
id: '13',
name: 'Customer Support Specialist',
},
],
};
export interface AgGridDemoRow {
selected?: boolean;
name: string;
age: number;
startDate: Date;
endDate?: Date;
department: AutocompleteOption;
jobTitle?: AutocompleteOption;
}
export const AG_GRID_DEMO_DATA = [
{
name: 'Billy Bob',
age: 55,
startDate: new Date('12/1/1994'),
department: DEPARTMENTS[3],
jobTitle: JOB_TITLES['Customer Support'][1],
},
{
name: 'Jane Deere',
age: 33,
startDate: new Date('7/15/2009'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][2],
},
{
name: 'John Doe',
age: 38,
startDate: new Date('9/1/2017'),
endDate: new Date('9/30/2017'),
department: DEPARTMENTS[1],
},
{
name: 'David Smith',
age: 51,
startDate: new Date('1/1/2012'),
endDate: new Date('6/15/2018'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][4],
},
{
name: 'Emily Johnson',
age: 41,
startDate: new Date('1/15/2014'),
department: DEPARTMENTS[0],
jobTitle: JOB_TITLES['Marketing'][2],
},
{
name: 'Nicole Davidson',
age: 22,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][0],
},
{
name: 'Carl Roberts',
age: 23,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][3],
},
];
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { AgGridDemoRow } from './data';
@Component({
standalone: true,
selector: 'app-context-menu',
templateUrl: './context-menu.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SkyDropdownModule],
})
export class ContextMenuComponent implements ICellRendererAngularComp {
public contextMenuAriaLabel = '';
public deleteAriaLabel = '';
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
#name: string | undefined;
public agInit(params: ICellRendererParams<AgGridDemoRow>): void {
this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
this.moreInfoAriaLabel = `More info for ${this.#name}`;
}
public refresh(): boolean {
return false;
}
protected actionClicked(action: string): void {
alert(`${action} clicked for ${this.#name}`);
}
}
<sky-dropdown buttonType="context-menu" [label]="contextMenuAriaLabel">
<sky-dropdown-menu>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="deleteAriaLabel"
(click)="actionClicked('Delete')"
>
Delete
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="markInactiveAriaLabel"
(click)="actionClicked('Mark inactive')"
>
Mark inactive
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="moreInfoAriaLabel"
(click)="actionClicked('More info')"
>
More info
</button>
</sky-dropdown-item>
</sky-dropdown-menu>
</sky-dropdown>
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnDestroy,
OnInit,
inject,
} from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { SkyAgGridModule, SkyAgGridService, SkyCellType } from '@skyux/ag-grid';
import { SkyDataManagerService } from '@skyux/data-manager';
import { SkyPagingModule } from '@skyux/lists';
import { AgGridModule } from 'ag-grid-angular';
import {
ColDef,
GridApi,
GridOptions,
GridReadyEvent,
ValueFormatterParams,
} from 'ag-grid-community';
import { Subscription } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { ContextMenuComponent } from './context-menu.component';
import { AG_GRID_DEMO_DATA, AgGridDemoRow } from './data';
@Component({
standalone: true,
selector: 'app-demo',
templateUrl: './demo.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [SkyDataManagerService],
imports: [AgGridModule, SkyAgGridModule, SkyPagingModule],
})
export class DemoComponent implements OnInit, OnDestroy {
protected currentPage = 1;
protected readonly pageSize = 3;
#columnDefs: ColDef[] = [
{
colId: 'context',
maxWidth: 50,
sortable: false,
cellRenderer: ContextMenuComponent,
},
{
field: 'name',
headerName: 'Name',
},
{
field: 'age',
headerName: 'Age',
type: SkyCellType.Number,
maxWidth: 60,
},
{
field: 'startDate',
headerName: 'Start date',
type: SkyCellType.Date,
sort: 'asc',
},
{
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
valueFormatter: (params: ValueFormatterParams<AgGridDemoRow, Date>) =>
this.#endDateFormatter(params),
},
{
field: 'department',
headerName: 'Department',
type: SkyCellType.Autocomplete,
},
{
field: 'jobTitle',
headerName: 'Title',
type: SkyCellType.Autocomplete,
},
];
protected gridData = AG_GRID_DEMO_DATA;
protected gridOptions: GridOptions;
#gridApi: GridApi | undefined;
#subscriptions = new Subscription();
readonly #activatedRoute = inject(ActivatedRoute);
readonly #agGridSvc = inject(SkyAgGridService);
readonly #changeDetectorRef = inject(ChangeDetectorRef);
readonly #router = inject(Router);
constructor() {
const gridOptions: GridOptions = {
columnDefs: this.#columnDefs,
onGridReady: (gridReadyEvent): void => {
this.onGridReady(gridReadyEvent);
},
rowSelection: 'single',
pagination: true,
suppressPaginationPanel: true,
paginationPageSize: this.pageSize,
};
this.gridOptions = this.#agGridSvc.getGridOptions({
gridOptions,
});
}
public ngOnInit(): void {
this.#subscriptions.add(
this.#activatedRoute.queryParamMap
.pipe(map((params) => params.get('page') ?? '1'))
.subscribe((page) => {
this.currentPage = Number(page);
this.#gridApi?.paginationGoToPage(this.currentPage - 1);
this.#changeDetectorRef.detectChanges();
}),
);
this.#subscriptions.add(
this.#router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe(() => {
const page = this.#activatedRoute.snapshot.paramMap.get('page');
if (page) {
this.currentPage = Number(page);
}
this.#gridApi?.paginationGoToPage(this.currentPage - 1);
this.#changeDetectorRef.detectChanges();
}),
);
}
public ngOnDestroy(): void {
this.#subscriptions.unsubscribe();
}
public onGridReady(gridReadyEvent: GridReadyEvent): void {
this.#gridApi = gridReadyEvent.api;
this.#gridApi.sizeColumnsToFit();
this.#gridApi.paginationGoToPage(this.currentPage - 1);
}
protected async onPageChange(page: number): Promise<void> {
await this.#router.navigate(['.'], {
relativeTo: this.#activatedRoute,
queryParams: { page: page.toString(10) },
queryParamsHandling: 'merge',
});
}
#endDateFormatter(params: ValueFormatterParams<AgGridDemoRow, Date>): string {
return params.value
? params.value.toLocaleDateString('en-us', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
: 'N/A';
}
}
<sky-toolbar>
<sky-toolbar-item>
<sky-search
[debounceTime]="250"
[searchText]="searchText"
(searchApply)="searchApplied($event)"
(searchChange)="searchApplied($event)"
(searchClear)="searchApplied($event)"
/>
</sky-toolbar-item>
</sky-toolbar>
<sky-ag-grid-wrapper>
<ag-grid-angular
[gridOptions]="gridOptions"
[overlayNoRowsTemplate]="noRowsTemplate"
[rowData]="gridData"
/>
</sky-ag-grid-wrapper>
export interface AutocompleteOption {
id: string;
name: string;
}
export const DEPARTMENTS = [
{
id: '1',
name: 'Marketing',
},
{
id: '2',
name: 'Sales',
},
{
id: '3',
name: 'Engineering',
},
{
id: '4',
name: 'Customer Support',
},
];
export const JOB_TITLES: Record<string, AutocompleteOption[]> = {
Marketing: [
{
id: '1',
name: 'Social Media Coordinator',
},
{
id: '2',
name: 'Blog Manager',
},
{
id: '3',
name: 'Events Manager',
},
],
Sales: [
{
id: '4',
name: 'Business Development Representative',
},
{
id: '5',
name: 'Account Executive',
},
],
Engineering: [
{
id: '6',
name: 'Software Engineer',
},
{
id: '7',
name: 'Senior Software Engineer',
},
{
id: '8',
name: 'Principal Software Engineer',
},
{
id: '9',
name: 'UX Designer',
},
{
id: '10',
name: 'Product Manager',
},
],
'Customer Support': [
{
id: '11',
name: 'Customer Support Representative',
},
{
id: '12',
name: 'Account Manager',
},
{
id: '13',
name: 'Customer Support Specialist',
},
],
};
export interface AgGridDemoRow {
selected?: boolean;
name: string;
age: number;
startDate: Date;
endDate?: Date;
department: AutocompleteOption;
jobTitle?: AutocompleteOption;
}
export const AG_GRID_DEMO_DATA = [
{
selected: true,
name: 'Billy Bob',
age: 55,
startDate: new Date('12/1/1994'),
department: DEPARTMENTS[3],
jobTitle: JOB_TITLES['Customer Support'][1],
},
{
selected: false,
name: 'Jane Deere',
age: 33,
startDate: new Date('7/15/2009'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][2],
},
{
selected: false,
name: 'John Doe',
age: 38,
startDate: new Date('9/1/2017'),
endDate: new Date('9/30/2017'),
department: DEPARTMENTS[1],
},
{
selected: false,
name: 'David Smith',
age: 51,
startDate: new Date('1/1/2012'),
endDate: new Date('6/15/2018'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][4],
},
{
selected: true,
name: 'Emily Johnson',
age: 41,
startDate: new Date('1/15/2014'),
department: DEPARTMENTS[0],
jobTitle: JOB_TITLES['Marketing'][2],
},
{
selected: false,
name: 'Nicole Davidson',
age: 22,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][0],
},
{
selected: false,
name: 'Carl Roberts',
age: 23,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][3],
},
];
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { AgGridDemoRow } from './data';
@Component({
standalone: true,
selector: 'app-context-menu',
templateUrl: './context-menu.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SkyDropdownModule],
})
export class ContextMenuComponent implements ICellRendererAngularComp {
public contextMenuAriaLabel = '';
public deleteAriaLabel = '';
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
#name: string | undefined;
public agInit(params: ICellRendererParams<AgGridDemoRow>): void {
this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
this.moreInfoAriaLabel = `More info for ${this.#name}`;
}
public refresh(): boolean {
return false;
}
protected actionClicked(action: string): void {
alert(`${action} clicked for ${this.#name}`);
}
}
<sky-dropdown buttonType="context-menu" [label]="contextMenuAriaLabel">
<sky-dropdown-menu>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="deleteAriaLabel"
(click)="actionClicked('Delete')"
>
Delete
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="markInactiveAriaLabel"
(click)="actionClicked('Mark inactive')"
>
Mark inactive
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="moreInfoAriaLabel"
(click)="actionClicked('More info')"
>
More info
</button>
</sky-dropdown-item>
</sky-dropdown-menu>
</sky-dropdown>
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { SkyAgGridModule, SkyAgGridService, SkyCellType } from '@skyux/ag-grid';
import { SkyDataManagerService } from '@skyux/data-manager';
import { SkyToolbarModule } from '@skyux/layout';
import { SkySearchModule } from '@skyux/lookup';
import { AgGridModule } from 'ag-grid-angular';
import {
ColDef,
GridApi,
GridOptions,
GridReadyEvent,
ValueFormatterParams,
} from 'ag-grid-community';
import { of } from 'rxjs';
import { ContextMenuComponent } from './context-menu.component';
import { AG_GRID_DEMO_DATA, AgGridDemoRow } from './data';
@Component({
standalone: true,
selector: 'app-demo',
templateUrl: './demo.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [SkyDataManagerService],
imports: [AgGridModule, SkyAgGridModule, SkySearchModule, SkyToolbarModule],
})
export class DemoComponent {
protected gridData = AG_GRID_DEMO_DATA;
protected gridOptions: GridOptions;
protected searchText = '';
protected noRowsTemplate = `<div class="sky-font-deemphasized">No results found.</div>`;
#columnDefs: ColDef[] = [
{
colId: 'context',
maxWidth: 50,
sortable: false,
cellRenderer: ContextMenuComponent,
},
{
field: 'selected',
type: SkyCellType.RowSelector,
cellRendererParams: {
// Could be a SkyAppResourcesService.getString call that returns an observable.
label: (data: AgGridDemoRow) => of(`Select ${data.name}`),
},
},
{
field: 'name',
headerName: 'Name',
},
{
field: 'age',
headerName: 'Age',
type: SkyCellType.Number,
maxWidth: 60,
},
{
field: 'startDate',
headerName: 'Start date',
type: SkyCellType.Date,
sort: 'asc',
},
{
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
valueFormatter: (params: ValueFormatterParams<AgGridDemoRow, Date>) =>
this.#endDateFormatter(params),
},
{
field: 'department',
headerName: 'Department',
type: SkyCellType.Autocomplete,
},
{
field: 'jobTitle',
headerName: 'Title',
type: SkyCellType.Autocomplete,
},
];
#gridApi: GridApi | undefined;
readonly #agGridSvc = inject(SkyAgGridService);
constructor() {
this.gridOptions = this.#agGridSvc.getGridOptions({
gridOptions: {
columnDefs: this.#columnDefs,
onGridReady: (gridReadyEvent): void => {
this.onGridReady(gridReadyEvent);
},
context: {
enableTopScroll: true,
},
},
});
}
public onGridReady(gridReadyEvent: GridReadyEvent): void {
this.#gridApi = gridReadyEvent.api;
this.#gridApi.sizeColumnsToFit();
}
protected searchApplied(searchText: string | void): void {
this.searchText = searchText ?? '';
if (this.#gridApi) {
this.#gridApi.setQuickFilter(this.searchText);
const displayedRowCount = this.#gridApi.getDisplayedRowCount();
if (displayedRowCount > 0) {
this.#gridApi.hideOverlay();
} else {
this.#gridApi.showNoRowsOverlay();
}
}
}
#endDateFormatter(params: ValueFormatterParams<AgGridDemoRow, Date>): string {
return params.value
? params.value.toLocaleDateString('en-us', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
: 'N/A';
}
}
<ng-template #boldColumn let-value="value">
<strong>{{ value }}</strong>
</ng-template>
<ng-template #emphasizedColumn let-value="value" let-row="row">
<span
*ngIf="row['jobLevel']"
class="sky-margin-inline-default sky-pull-right"
>{{ row['jobLevel'] }}</span
>
<em>{{ value }}</em>
</ng-template>
<sky-ag-grid-wrapper>
<ag-grid-angular [gridOptions]="gridOptions" [rowData]="gridData" />
</sky-ag-grid-wrapper>
export interface AgGridDemoRow {
name: string;
age: number;
department: string;
jobTitle: string;
}
export const AG_GRID_DEMO_DATA = [
{
name: 'Billy Bob',
age: 55,
department: 'Customer Support',
jobTitle: 'Customer Support Representative',
jobLevel: '🥈',
},
{
name: 'Jane Deere',
age: 33,
department: 'Engineering',
jobTitle: 'Software Engineer',
jobLevel: '🥇',
},
{
name: 'John Doe',
age: 38,
department: 'Engineering',
jobTitle: 'UX Designer',
},
{
name: 'David Smith',
age: 51,
department: 'Engineering',
jobTitle: 'Software Engineer',
jobLevel: '🥉',
},
{
name: 'Emily Johnson',
age: 41,
department: 'Marketing',
jobTitle: 'Customer Support Representative',
},
{
name: 'Nicole Davidson',
age: 22,
startDate: new Date('11/1/2019'),
department: 'Marketing',
jobTitle: 'Account Manager',
jobLevel: '🥈',
},
{
name: 'Carl Roberts',
age: 23,
startDate: new Date('11/1/2019'),
department: 'Marketing',
jobTitle: 'Social Media Coordinator',
jobLevel: '🥇',
},
];
import { NgIf } from '@angular/common';
import {
Component,
OnInit,
TemplateRef,
ViewChild,
inject,
} from '@angular/core';
import { SkyAgGridModule, SkyAgGridService, SkyCellType } from '@skyux/ag-grid';
import { AgGridModule } from 'ag-grid-angular';
import { GridOptions } from 'ag-grid-community';
import { AG_GRID_DEMO_DATA } from './data';
@Component({
standalone: true,
selector: 'app-demo',
templateUrl: './demo.component.html',
imports: [AgGridModule, SkyAgGridModule, NgIf],
})
export class DemoComponent implements OnInit {
@ViewChild('boldColumn', { static: true })
protected boldColumn: TemplateRef<unknown> | undefined;
@ViewChild('emphasizedColumn', { static: true })
protected emphasizedColumn: TemplateRef<unknown> | undefined;
protected gridData = AG_GRID_DEMO_DATA;
protected gridOptions: GridOptions | undefined;
readonly #agGridSvc = inject(SkyAgGridService);
public ngOnInit(): void {
this.gridOptions = this.#agGridSvc.getGridOptions({
gridOptions: {
columnDefs: [
{
field: 'name',
headerName: 'Name',
initialWidth: 150,
},
{
field: 'age',
headerName: 'Age',
type: SkyCellType.Number,
maxWidth: 80,
resizable: false,
},
{
field: 'department',
headerName: 'Department',
type: SkyCellType.Template,
cellRendererParams: {
template: this.boldColumn,
},
initialWidth: 220,
},
{
field: 'jobTitle',
headerName: 'Title',
type: SkyCellType.Template,
cellRendererParams: {
template: this.emphasizedColumn,
},
initialWidth: 220,
},
],
},
});
}
}
<sky-data-view skyAgGridDataManagerAdapter [viewId]="viewConfig.id">
<sky-ag-grid-wrapper *ngIf="isViewActive && isGridReadyForInitialization">
<ag-grid-angular
[gridOptions]="gridOptions"
[rowData]="displayedItems"
[overlayNoRowsTemplate]="noRowsTemplate"
(rowSelected)="onRowSelected($event)"
/>
</sky-ag-grid-wrapper>
</sky-data-view>
export interface Filters {
jobTitle?: string;
hideSales?: boolean;
}
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
inject,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SkyIdModule } from '@skyux/core';
import {
SkyDataManagerFilterData,
SkyDataManagerFilterModalContext,
} from '@skyux/data-manager';
import { SkyCheckboxModule } from '@skyux/forms';
import { SkyModalInstance, SkyModalModule } from '@skyux/modals';
import { Filters } from './filters';
@Component({
standalone: true,
selector: 'app-filter-modal',
templateUrl: './filter-modal.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [FormsModule, SkyCheckboxModule, SkyIdModule, SkyModalModule],
})
export class FilterModalComponent {
protected hideSales = false;
protected jobTitle = '';
readonly #changeDetectorRef = inject(ChangeDetectorRef);
readonly #context = inject(SkyDataManagerFilterModalContext);
readonly #instance = inject(SkyModalInstance);
constructor() {
if (this.#context.filterData && this.#context.filterData.filters) {
const filters = this.#context.filterData.filters as Filters;
this.jobTitle = filters.jobTitle ?? 'any';
this.hideSales = filters.hideSales ?? false;
}
this.#changeDetectorRef.markForCheck();
}
protected applyFilters(): void {
const result: SkyDataManagerFilterData = {};
result.filtersApplied = this.jobTitle !== 'any' || this.hideSales;
result.filters = {
jobTitle: this.jobTitle,
hideSales: this.hideSales,
} satisfies Filters;
this.#changeDetectorRef.markForCheck();
this.#instance.save(result);
}
protected clearAllFilters(): void {
this.hideSales = false;
this.jobTitle = 'any';
this.#changeDetectorRef.markForCheck();
}
protected cancel(): void {
this.#instance.cancel();
}
}
<sky-modal>
<sky-modal-header> Filter employees </sky-modal-header>
<sky-modal-content>
<label [for]="jobTitleInput.id">Job Title</label>
<select
class="sky-form-control"
skyId="jobTitleInput"
[(ngModel)]="jobTitle"
#jobTitleInput
>
<option value="any">Any job title</option>
<option value="Product Manager">Product Manager</option>
<option value="Software Engineer">Software Engineer</option>
</select>
<div class="sky-margin-stacked-lg">
<sky-checkbox [(ngModel)]="hideSales">
<sky-checkbox-label> Hide Sales employees </sky-checkbox-label>
</sky-checkbox>
</div>
</sky-modal-content>
<sky-modal-footer>
<button
class="sky-btn sky-btn-primary"
type="button"
(click)="applyFilters()"
>
Apply filters
</button>
<button
class="sky-btn sky-btn-link"
type="button"
(click)="clearAllFilters()"
>
Clear all filters
</button>
<button class="sky-btn sky-btn-link" type="button" (click)="cancel()">
Cancel
</button>
</sky-modal-footer>
</sky-modal>
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnDestroy,
OnInit,
inject,
} from '@angular/core';
import {
SkyDataManagerModule,
SkyDataManagerService,
SkyDataManagerState,
} from '@skyux/data-manager';
import { Subject, takeUntil } from 'rxjs';
import { AG_GRID_DEMO_DATA } from './data';
import { FilterModalComponent } from './filter-modal.component';
import { Filters } from './filters';
import { ViewGridComponent } from './view-grid.component';
@Component({
standalone: true,
selector: 'app-demo',
templateUrl: './demo.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [SkyDataManagerService],
imports: [SkyDataManagerModule, ViewGridComponent],
})
export class DemoComponent implements OnInit, OnDestroy {
protected items = AG_GRID_DEMO_DATA;
#activeViewId = 'dataGridWithDataManagerView';
#dataManagerConfig = {
filterModalComponent: FilterModalComponent,
sortOptions: [
{
id: 'az',
label: 'Name (A - Z)',
descending: false,
propertyName: 'name',
},
{
id: 'za',
label: 'Name (Z - A)',
descending: true,
propertyName: 'name',
},
],
};
#defaultDataState = new SkyDataManagerState({
filterData: {
filtersApplied: false,
filters: {
hideSales: false,
jobTitle: 'any',
} satisfies Filters,
},
views: [
{
viewId: 'dataGridWithDataManagerView',
displayedColumnIds: [
'context',
'name',
'age',
'startDate',
'endDate',
'department',
'jobTitle',
],
},
],
});
#ngUnsubscribe = new Subject<void>();
readonly #changeDetectorRef = inject(ChangeDetectorRef);
readonly #dataManagerSvc = inject(SkyDataManagerService);
constructor() {
this.#dataManagerSvc
.getActiveViewIdUpdates()
.pipe(takeUntil(this.#ngUnsubscribe))
.subscribe((activeViewId) => {
this.#activeViewId = activeViewId;
this.#changeDetectorRef.detectChanges();
});
}
public ngOnInit(): void {
this.#dataManagerSvc.initDataManager({
activeViewId: this.#activeViewId,
dataManagerConfig: this.#dataManagerConfig,
defaultDataState: this.#defaultDataState,
});
}
public ngOnDestroy(): void {
this.#ngUnsubscribe.next();
this.#ngUnsubscribe.complete();
}
}
<sky-data-manager>
<sky-data-manager-toolbar />
<app-view-grid [items]="items" />
</sky-data-manager>
export interface AutocompleteOption {
id: string;
name: string;
}
export const DEPARTMENTS = [
{
id: '1',
name: 'Marketing',
},
{
id: '2',
name: 'Sales',
},
{
id: '3',
name: 'Engineering',
},
{
id: '4',
name: 'Customer Support',
},
];
export const JOB_TITLES: Record<string, AutocompleteOption[]> = {
Marketing: [
{
id: '1',
name: 'Social Media Coordinator',
},
{
id: '2',
name: 'Blog Manager',
},
{
id: '3',
name: 'Events Manager',
},
],
Sales: [
{
id: '4',
name: 'Business Development Representative',
},
{
id: '5',
name: 'Account Executive',
},
],
Engineering: [
{
id: '6',
name: 'Software Engineer',
},
{
id: '7',
name: 'Senior Software Engineer',
},
{
id: '8',
name: 'Principal Software Engineer',
},
{
id: '9',
name: 'UX Designer',
},
{
id: '10',
name: 'Product Manager',
},
],
'Customer Support': [
{
id: '11',
name: 'Customer Support Representative',
},
{
id: '12',
name: 'Account Manager',
},
{
id: '13',
name: 'Customer Support Specialist',
},
],
};
export interface AgGridDemoRow {
selected?: boolean;
name: string;
age: number;
startDate: Date;
endDate?: Date;
department: AutocompleteOption;
jobTitle?: AutocompleteOption;
}
export const AG_GRID_DEMO_DATA = [
{
name: 'Billy Bob',
age: 55,
startDate: new Date('12/1/1994'),
department: DEPARTMENTS[3],
jobTitle: JOB_TITLES['Customer Support'][1],
},
{
name: 'Jane Deere',
age: 33,
startDate: new Date('7/15/2009'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][2],
},
{
name: 'John Doe',
age: 38,
startDate: new Date('9/1/2017'),
endDate: new Date('9/30/2017'),
department: DEPARTMENTS[1],
jobTitle: JOB_TITLES['Sales'][1],
},
{
name: 'David Smith',
age: 51,
startDate: new Date('1/1/2012'),
endDate: new Date('6/15/2018'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][4],
},
{
name: 'Emily Johnson',
age: 41,
startDate: new Date('1/15/2014'),
department: DEPARTMENTS[0],
jobTitle: JOB_TITLES['Marketing'][2],
},
{
name: 'Nicole Davidson',
age: 22,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][0],
},
{
name: 'Carl Roberts',
age: 23,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][3],
},
];
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { AgGridDemoRow } from './data';
@Component({
standalone: true,
selector: 'app-context-menu',
templateUrl: './context-menu.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SkyDropdownModule],
})
export class ContextMenuComponent implements ICellRendererAngularComp {
public contextMenuAriaLabel = '';
public deleteAriaLabel = '';
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
#name: string | undefined;
public agInit(params: ICellRendererParams<AgGridDemoRow>): void {
this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
this.moreInfoAriaLabel = `More info for ${this.#name}`;
}
public refresh(): boolean {
return false;
}
protected actionClicked(action: string): void {
alert(`${action} clicked for ${this.#name}`);
}
}
<sky-dropdown buttonType="context-menu" [label]="contextMenuAriaLabel">
<sky-dropdown-menu>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="deleteAriaLabel"
(click)="actionClicked('Delete')"
>
Delete
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="markInactiveAriaLabel"
(click)="actionClicked('Mark inactive')"
>
Mark inactive
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="moreInfoAriaLabel"
(click)="actionClicked('More info')"
>
More info
</button>
</sky-dropdown-item>
</sky-dropdown-menu>
</sky-dropdown>
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnDestroy,
OnInit,
inject,
} from '@angular/core';
import { SkyAgGridModule, SkyAgGridService, SkyCellType } from '@skyux/ag-grid';
import {
SkyDataManagerColumnPickerOption,
SkyDataManagerService,
SkyDataManagerState,
SkyDataViewConfig,
SkyDataViewState,
} from '@skyux/data-manager';
import { AgGridModule } from 'ag-grid-angular';
import {
ColDef,
GridApi,
GridOptions,
GridReadyEvent,
RowSelectedEvent,
ValueFormatterParams,
} from 'ag-grid-community';
import { Subject, takeUntil } from 'rxjs';
import { ContextMenuComponent } from './context-menu.component';
import { AgGridDemoRow } from './data';
import { Filters } from './filters';
@Component({
standalone: true,
selector: 'app-view-grid',
templateUrl: './view-grid.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [AgGridModule, CommonModule, SkyAgGridModule],
})
export class ViewGridComponent implements OnInit, OnDestroy {
@Input()
public items: AgGridDemoRow[] = [];
protected displayedItems: AgGridDemoRow[] = [];
protected gridOptions!: GridOptions;
protected isGridReadyForInitialization = false;
protected isViewActive = false;
protected noRowsTemplate = `<div class="sky-deemphasized">No results found.</div>`;
protected viewConfig: SkyDataViewConfig;
#columnDefs: ColDef[] = [
{
colId: 'context',
maxWidth: 50,
sortable: false,
cellRenderer: ContextMenuComponent,
},
{
field: 'name',
headerName: 'Name',
},
{
field: 'age',
headerName: 'Age',
type: SkyCellType.Number,
maxWidth: 60,
},
{
field: 'startDate',
headerName: 'Start date',
type: SkyCellType.Date,
sort: 'asc',
},
{
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
valueFormatter: (params: ValueFormatterParams<AgGridDemoRow, Date>) =>
this.#endDateFormatter(params),
},
{
field: 'department',
headerName: 'Department',
type: SkyCellType.Autocomplete,
},
{
field: 'jobTitle',
headerName: 'Title',
type: SkyCellType.Autocomplete,
},
];
#columnPickerOptions: SkyDataManagerColumnPickerOption[] = [
{
id: 'context',
label: '',
alwaysDisplayed: true,
},
{
id: 'name',
label: 'Name',
description: 'The name of the employee.',
},
{
id: 'age',
label: 'Age',
description: 'The age of the employee.',
},
{
id: 'startDate',
label: 'Start date',
description: 'The start date of the employee.',
},
{
id: 'endDate',
label: 'End date',
description: 'The end date of the employee.',
},
{
id: 'department',
label: 'Department',
description: 'The department of the employee',
},
{
id: 'jobTitle',
label: 'Title',
description: 'The job title of the employee.',
},
];
#dataState = new SkyDataManagerState({});
#gridApi: GridApi | undefined;
#ngUnsubscribe = new Subject<void>();
#viewId = 'dataGridWithDataManagerView';
readonly #agGridSvc = inject(SkyAgGridService);
readonly #changeDetectorRef = inject(ChangeDetectorRef);
readonly #dataManagerSvc = inject(SkyDataManagerService);
constructor() {
this.viewConfig = {
id: this.#viewId,
name: 'Data Grid View',
icon: 'table',
searchEnabled: true,
columnPickerEnabled: true,
filterButtonEnabled: true,
columnOptions: this.#columnPickerOptions,
};
}
public ngOnInit(): void {
this.displayedItems = this.items;
this.#dataManagerSvc.initDataView(this.viewConfig);
this.gridOptions = this.#agGridSvc.getGridOptions({
gridOptions: {
columnDefs: this.#columnDefs,
rowSelection: 'single',
onGridReady: this.onGridReady.bind(this),
},
});
this.#dataManagerSvc
.getDataStateUpdates(this.#viewId)
.pipe(takeUntil(this.#ngUnsubscribe))
.subscribe((state) => {
this.#dataState = state;
this.#updateColumnOrder();
this.isGridReadyForInitialization = true;
this.#updateDisplayedItems();
this.#changeDetectorRef.detectChanges();
});
this.#dataManagerSvc
.getActiveViewIdUpdates()
.pipe(takeUntil(this.#ngUnsubscribe))
.subscribe((id) => {
this.isViewActive = id === this.#viewId;
this.#changeDetectorRef.detectChanges();
});
}
public ngOnDestroy(): void {
this.#ngUnsubscribe.next();
this.#ngUnsubscribe.complete();
}
public onGridReady(gridReadyEvent: GridReadyEvent): void {
this.#gridApi = gridReadyEvent.api;
this.#gridApi.sizeColumnsToFit();
this.#updateDisplayedItems();
}
protected onRowSelected(
rowSelectedEvent: RowSelectedEvent<AgGridDemoRow>,
): void {
if (!rowSelectedEvent.data?.selected) {
this.#updateDisplayedItems();
}
}
#searchItems(items: AgGridDemoRow[]): AgGridDemoRow[] {
let searchedItems = items;
const searchText = this.#dataState && this.#dataState.searchText;
if (searchText) {
searchedItems = items.filter((item: AgGridDemoRow) => {
let property: keyof typeof item;
for (property in item) {
if (
Object.prototype.hasOwnProperty.call(item, property) &&
property === 'name'
) {
const propertyText = item[property].toLowerCase();
if (propertyText.includes(searchText)) {
return true;
}
}
}
return false;
});
}
return searchedItems;
}
#filterItems(items: AgGridDemoRow[]): AgGridDemoRow[] {
let filteredItems = items;
const filterData = this.#dataState && this.#dataState.filterData;
if (filterData?.filters) {
const filters = filterData.filters as Filters;
filteredItems = items.filter((item: AgGridDemoRow) => {
return (
(!!(filters.hideSales && item.department.name !== 'Sales') ||
!filters.hideSales) &&
((filters.jobTitle !== 'any' &&
item.jobTitle?.name === filters.jobTitle) ||
!filters.jobTitle ||
filters.jobTitle === 'any')
);
});
}
return filteredItems;
}
#endDateFormatter(params: ValueFormatterParams<AgGridDemoRow, Date>): string {
return params.value
? params.value.toLocaleDateString('en-us', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
: 'N/A';
}
#updateColumnOrder(): void {
const viewState: SkyDataViewState | undefined =
this.#dataState.getViewStateById(this.#viewId);
if (viewState) {
this.#columnDefs.sort((columnDefinition1, columnDefinition2) => {
const displayedColumnIdIndex1: number =
viewState.displayedColumnIds.findIndex(
(aDisplayedColumnId: string) =>
aDisplayedColumnId === columnDefinition1.field,
);
const displayedColumnIdIndex2: number =
viewState.displayedColumnIds.findIndex(
(aDisplayedColumnId: string) =>
aDisplayedColumnId === columnDefinition2.field,
);
if (displayedColumnIdIndex1 === -1) {
return 0;
} else if (displayedColumnIdIndex2 === -1) {
return 0;
} else {
return displayedColumnIdIndex1 - displayedColumnIdIndex2;
}
});
this.#changeDetectorRef.markForCheck();
}
}
#updateDisplayedItems(): void {
this.displayedItems = this.#filterItems(this.#searchItems(this.items));
if (this.#dataState.onlyShowSelected) {
this.displayedItems = this.displayedItems.filter((item) => item.selected);
}
if (this.displayedItems.length > 0) {
this.#gridApi?.hideOverlay();
} else {
this.#gridApi?.showNoRowsOverlay();
}
this.#dataManagerSvc.updateDataSummary(
{
totalItems: this.items.length,
itemsMatching: this.displayedItems.length,
},
this.#viewId,
);
}
}
<sky-data-view skyAgGridDataManagerAdapter [viewId]="viewConfig.id">
<sky-ag-grid-wrapper *ngIf="isViewActive && isGridReadyForInitialization">
<ag-grid-angular
[gridOptions]="gridOptions"
[rowData]="displayedItems"
[overlayNoRowsTemplate]="noRowsTemplate"
(rowSelected)="onRowSelected($event)"
/>
</sky-ag-grid-wrapper>
</sky-data-view>
export interface Filters {
jobTitle?: string;
hideSales?: boolean;
}
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
inject,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SkyIdModule } from '@skyux/core';
import {
SkyDataManagerFilterData,
SkyDataManagerFilterModalContext,
} from '@skyux/data-manager';
import { SkyCheckboxModule } from '@skyux/forms';
import { SkyModalInstance, SkyModalModule } from '@skyux/modals';
import { Filters } from './filters';
@Component({
standalone: true,
selector: 'app-filter-modal',
templateUrl: './filter-modal.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [FormsModule, SkyCheckboxModule, SkyIdModule, SkyModalModule],
})
export class FilterModalComponent {
protected hideSales = false;
protected jobTitle = '';
readonly #changeDetectorRef = inject(ChangeDetectorRef);
readonly #context = inject(SkyDataManagerFilterModalContext);
readonly #instance = inject(SkyModalInstance);
constructor() {
if (this.#context.filterData && this.#context.filterData.filters) {
const filters = this.#context.filterData.filters as Filters;
this.jobTitle = filters.jobTitle ?? 'any';
this.hideSales = filters.hideSales ?? false;
}
this.#changeDetectorRef.markForCheck();
}
protected applyFilters(): void {
const result: SkyDataManagerFilterData = {};
result.filtersApplied = this.jobTitle !== 'any' || this.hideSales;
result.filters = {
jobTitle: this.jobTitle,
hideSales: this.hideSales,
} satisfies Filters;
this.#changeDetectorRef.markForCheck();
this.#instance.save(result);
}
protected clearAllFilters(): void {
this.hideSales = false;
this.jobTitle = 'any';
this.#changeDetectorRef.markForCheck();
}
protected cancel(): void {
this.#instance.cancel();
}
}
<sky-modal>
<sky-modal-header> Filter employees </sky-modal-header>
<sky-modal-content>
<label [for]="jobTitleInput.id">Job Title</label>
<select
class="sky-form-control"
skyId="jobTitleInput"
[(ngModel)]="jobTitle"
#jobTitleInput
>
<option value="any">Any job title</option>
<option value="Product Manager">Product Manager</option>
<option value="Software Engineer">Software Engineer</option>
</select>
<div class="sky-margin-stacked-lg">
<sky-checkbox [(ngModel)]="hideSales">
<sky-checkbox-label> Hide Sales employees </sky-checkbox-label>
</sky-checkbox>
</div>
</sky-modal-content>
<sky-modal-footer>
<button
class="sky-btn sky-btn-primary"
type="button"
(click)="applyFilters()"
>
Apply filters
</button>
<button
class="sky-btn sky-btn-link"
type="button"
(click)="clearAllFilters()"
>
Clear all filters
</button>
<button class="sky-btn sky-btn-link" type="button" (click)="cancel()">
Cancel
</button>
</sky-modal-footer>
</sky-modal>
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnDestroy,
OnInit,
inject,
} from '@angular/core';
import {
SkyDataManagerConfig,
SkyDataManagerModule,
SkyDataManagerService,
SkyDataManagerState,
} from '@skyux/data-manager';
import { Subject, takeUntil } from 'rxjs';
import { AG_GRID_DEMO_DATA } from './data';
import { FilterModalComponent } from './filter-modal.component';
import { Filters } from './filters';
import { ViewGridComponent } from './view-grid.component';
@Component({
standalone: true,
selector: 'app-demo',
templateUrl: './demo.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [SkyDataManagerService],
imports: [SkyDataManagerModule, ViewGridComponent],
})
export class DemoComponent implements OnInit, OnDestroy {
protected items = AG_GRID_DEMO_DATA;
#dataManagerConfig: SkyDataManagerConfig = {
filterModalComponent: FilterModalComponent,
sortOptions: [
{
id: 'az',
label: 'Name (A - Z)',
descending: false,
propertyName: 'name',
},
{
id: 'za',
label: 'Name (Z - A)',
descending: true,
propertyName: 'name',
},
],
};
#defaultDataState = new SkyDataManagerState({
filterData: {
filtersApplied: false,
filters: {
hideSales: false,
jobTitle: 'any',
} satisfies Filters,
},
views: [
{
viewId: 'dataGridMultiselectWithDataManagerView',
displayedColumnIds: [
'selected',
'context',
'name',
'age',
'startDate',
'endDate',
'department',
'jobTitle',
],
},
],
});
#activeViewId = 'dataGridMultiselectWithDataManagerView';
#ngUnsubscribe = new Subject<void>();
readonly #changeDetectorRef = inject(ChangeDetectorRef);
readonly #dataManagerSvc = inject(SkyDataManagerService);
constructor() {
this.#dataManagerSvc
.getActiveViewIdUpdates()
.pipe(takeUntil(this.#ngUnsubscribe))
.subscribe((activeViewId) => {
this.#activeViewId = activeViewId;
this.#changeDetectorRef.detectChanges();
});
}
public ngOnInit(): void {
this.#dataManagerSvc.initDataManager({
activeViewId: this.#activeViewId,
dataManagerConfig: this.#dataManagerConfig,
defaultDataState: this.#defaultDataState,
});
}
public ngOnDestroy(): void {
this.#ngUnsubscribe.next();
this.#ngUnsubscribe.complete();
}
}
<sky-data-manager>
<sky-data-manager-toolbar />
<app-view-grid [items]="items" />
</sky-data-manager>
export interface AutocompleteOption {
id: string;
name: string;
}
export const DEPARTMENTS = [
{
id: '1',
name: 'Marketing',
},
{
id: '2',
name: 'Sales',
},
{
id: '3',
name: 'Engineering',
},
{
id: '4',
name: 'Customer Support',
},
];
export const JOB_TITLES: Record<string, AutocompleteOption[]> = {
Marketing: [
{
id: '1',
name: 'Social Media Coordinator',
},
{
id: '2',
name: 'Blog Manager',
},
{
id: '3',
name: 'Events Manager',
},
],
Sales: [
{
id: '4',
name: 'Business Development Representative',
},
{
id: '5',
name: 'Account Executive',
},
],
Engineering: [
{
id: '6',
name: 'Software Engineer',
},
{
id: '7',
name: 'Senior Software Engineer',
},
{
id: '8',
name: 'Principal Software Engineer',
},
{
id: '9',
name: 'UX Designer',
},
{
id: '10',
name: 'Product Manager',
},
],
'Customer Support': [
{
id: '11',
name: 'Customer Support Representative',
},
{
id: '12',
name: 'Account Manager',
},
{
id: '13',
name: 'Customer Support Specialist',
},
],
};
export interface AgGridDemoRow {
selected?: boolean;
name: string;
age: number;
startDate: Date;
endDate?: Date;
department: AutocompleteOption;
jobTitle?: AutocompleteOption;
}
export const AG_GRID_DEMO_DATA = [
{
selected: false,
name: 'Billy Bob',
age: 55,
startDate: new Date('12/1/1994'),
department: DEPARTMENTS[3],
jobTitle: JOB_TITLES['Customer Support'][1],
},
{
selected: false,
name: 'Jane Deere',
age: 33,
startDate: new Date('7/15/2009'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][2],
},
{
selected: false,
name: 'John Doe',
age: 38,
startDate: new Date('9/1/2017'),
endDate: new Date('9/30/2017'),
department: DEPARTMENTS[1],
jobTitle: JOB_TITLES['Sales'][1],
},
{
selected: false,
name: 'David Smith',
age: 51,
startDate: new Date('1/1/2012'),
endDate: new Date('6/15/2018'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][4],
},
{
selected: true,
name: 'Emily Johnson',
age: 41,
startDate: new Date('1/15/2014'),
department: DEPARTMENTS[0],
jobTitle: JOB_TITLES['Marketing'][2],
},
{
selected: false,
name: 'Nicole Davidson',
age: 22,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][0],
},
{
selected: false,
name: 'Carl Roberts',
age: 23,
startDate: new Date('11/1/2019'),
department: DEPARTMENTS[2],
jobTitle: JOB_TITLES['Engineering'][3],
},
];
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { AgGridDemoRow } from './data';
@Component({
standalone: true,
selector: 'app-context-menu',
templateUrl: './context-menu.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SkyDropdownModule],
})
export class ContextMenuComponent implements ICellRendererAngularComp {
public contextMenuAriaLabel = '';
public deleteAriaLabel = '';
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
#name: string | undefined;
public agInit(params: ICellRendererParams<AgGridDemoRow>): void {
this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
this.moreInfoAriaLabel = `More info for ${this.#name}`;
}
public refresh(): boolean {
return false;
}
protected actionClicked(action: string): void {
alert(`${action} clicked for ${this.#name}`);
}
}
<sky-dropdown buttonType="context-menu" [label]="contextMenuAriaLabel">
<sky-dropdown-menu>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="deleteAriaLabel"
(click)="actionClicked('Delete')"
>
Delete
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="markInactiveAriaLabel"
(click)="actionClicked('Mark inactive')"
>
Mark inactive
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button
type="button"
[attr.aria-label]="moreInfoAriaLabel"
(click)="actionClicked('More info')"
>
More info
</button>
</sky-dropdown-item>
</sky-dropdown-menu>
</sky-dropdown>
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnDestroy,
OnInit,
inject,
} from '@angular/core';
import { SkyAgGridModule, SkyAgGridService, SkyCellType } from '@skyux/ag-grid';
import {
SkyDataManagerColumnPickerOption,
SkyDataManagerService,
SkyDataManagerState,
SkyDataViewConfig,
SkyDataViewState,
} from '@skyux/data-manager';
import { AgGridModule } from 'ag-grid-angular';
import {
ColDef,
ColumnApi,
GridApi,
GridOptions,
GridReadyEvent,
RowSelectedEvent,
ValueFormatterParams,
} from 'ag-grid-community';
import { Subject, of, takeUntil } from 'rxjs';
import { ContextMenuComponent } from './context-menu.component';
import { AgGridDemoRow } from './data';
import { Filters } from './filters';
@Component({
standalone: true,
selector: 'app-view-grid',
templateUrl: './view-grid.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [AgGridModule, CommonModule, SkyAgGridModule],
})
export class ViewGridComponent implements OnInit, OnDestroy {
@Input()
public items: AgGridDemoRow[] = [];
protected displayedItems: AgGridDemoRow[] = [];
protected gridOptions!: GridOptions;
protected isGridReadyForInitialization = false;
protected isViewActive = false;
protected noRowsTemplate = `<div class="sky-deemphasized">No results found.</div>`;
protected viewConfig: SkyDataViewConfig;
#columnDefs: ColDef[] = [
{
field: 'selected',
type: SkyCellType.RowSelector,
cellRendererParams: {
// Could be a SkyAppResourcesService.getString call that returns an observable.
label: (data: AgGridDemoRow) => of(`Select ${data.name}`),
},
},
{
colId: 'context',
maxWidth: 50,
sortable: false,
cellRenderer: ContextMenuComponent,
},
{
field: 'name',
headerName: 'Name',
},
{
field: 'age',
headerName: 'Age',
type: SkyCellType.Number,
maxWidth: 60,
},
{
field: 'startDate',
headerName: 'Start date',
type: SkyCellType.Date,
sort: 'asc',
},
{
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
valueFormatter: (params: ValueFormatterParams<AgGridDemoRow, Date>) =>
this.#endDateFormatter(params),
},
{
field: 'department',
headerName: 'Department',
type: SkyCellType.Autocomplete,
},
{
field: 'jobTitle',
headerName: 'Title',
type: SkyCellType.Autocomplete,
},
];
#columnPickerOptions: SkyDataManagerColumnPickerOption[] = [
{
id: 'selected',
label: '',
alwaysDisplayed: true,
},
{
id: 'context',
label: '',
alwaysDisplayed: true,
},
{
id: 'name',
label: 'Name',
description: 'The name of the employee.',
},
{
id: 'age',
label: 'Age',
description: 'The age of the employee.',
},
{
id: 'startDate',
label: 'Start date',
description: 'The start date of the employee.',
},
{
id: 'endDate',
label: 'End date',
description: 'The end date of the employee.',
},
{
id: 'department',
label: 'Department',
description: 'The department of the employee',
},
{
id: 'jobTitle',
label: 'Title',
description: 'The job title of the employee.',
},
];
#columnApi?: ColumnApi;
#dataState = new SkyDataManagerState({});
#gridApi?: GridApi;
#ngUnsubscribe = new Subject<void>();
#viewId = 'dataGridMultiselectWithDataManagerView';
readonly #agGridSvc = inject(SkyAgGridService);
readonly #changeDetectorRef = inject(ChangeDetectorRef);
readonly #dataManagerSvc = inject(SkyDataManagerService);
constructor() {
this.viewConfig = {
id: this.#viewId,
name: 'Data Grid View',
icon: 'table',
searchEnabled: true,
multiselectToolbarEnabled: true,
columnPickerEnabled: true,
filterButtonEnabled: true,
columnOptions: this.#columnPickerOptions,
};
}
public ngOnInit(): void {
this.displayedItems = this.items;
this.#dataManagerSvc.initDataView(this.viewConfig);
this.gridOptions = this.#agGridSvc.getGridOptions({
gridOptions: {
columnDefs: this.#columnDefs,
rowSelection: 'multiple',
onGridReady: this.onGridReady.bind(this),
},
});
this.#dataManagerSvc
.getDataStateUpdates(this.#viewId)
.pipe(takeUntil(this.#ngUnsubscribe))
.subscribe((state) => {
this.#dataState = state;
this.#updateColumnOrder();
this.isGridReadyForInitialization = true;
this.#updateDisplayedItems();
this.#changeDetectorRef.detectChanges();
});
this.#dataManagerSvc
.getActiveViewIdUpdates()
.pipe(takeUntil(this.#ngUnsubscribe))
.subscribe((id) => {
this.isViewActive = id === this.#viewId;
this.#changeDetectorRef.detectChanges();
});
}
public ngOnDestroy(): void {
this.#ngUnsubscribe.next();
this.#ngUnsubscribe.complete();
}
public onGridReady(gridReadyEvent: GridReadyEvent): void {
this.#gridApi = gridReadyEvent.api;
this.#gridApi.sizeColumnsToFit();
this.#columnApi = gridReadyEvent.columnApi;
this.#updateDisplayedItems();
}
protected onRowSelected(
rowSelectedEvent: RowSelectedEvent<AgGridDemoRow>,
): void {
if (!rowSelectedEvent.data?.selected) {
this.#updateDisplayedItems();
}
}
#searchItems(items: AgGridDemoRow[]): AgGridDemoRow[] {
let searchedItems = items;
const searchText = this.#dataState && this.#dataState.searchText;
if (searchText) {
searchedItems = items.filter((item) => {
let property: keyof typeof item;
for (property in item) {
if (
Object.prototype.hasOwnProperty.call(item, property) &&
property === 'name'
) {
const propertyText = item[property].toLowerCase();
if (propertyText.includes(searchText)) {
return true;
}
}
}
return false;
});
}
return searchedItems;
}
#filterItems(items: AgGridDemoRow[]): AgGridDemoRow[] {
let filteredItems = items;
const filterData = this.#dataState && this.#dataState.filterData;
if (filterData?.filters) {
const filters = filterData.filters as Filters;
filteredItems = items.filter((item) => {
return (
(!!(filters.hideSales && item.department.name !== 'Sales') ||
!filters.hideSales) &&
((filters.jobTitle !== 'any' &&
item.jobTitle?.name === filters.jobTitle) ||
!filters.jobTitle ||
filters.jobTitle === 'any')
);
});
}
return filteredItems;
}
#endDateFormatter(params: ValueFormatterParams<AgGridDemoRow, Date>): string {
return params.value
? params.value.toLocaleDateString('en-us', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
: 'N/A';
}
#updateColumnOrder(): void {
const viewState: SkyDataViewState | undefined =
this.#dataState.getViewStateById(this.#viewId);
if (viewState) {
this.#columnDefs.sort((columnDefinition1, columnDefinition2) => {
const displayedColumnIdIndex1: number =
viewState.displayedColumnIds.findIndex(
(aDisplayedColumnId: string) =>
aDisplayedColumnId === columnDefinition1.field,
);
const displayedColumnIdIndex2: number =
viewState.displayedColumnIds.findIndex(
(aDisplayedColumnId: string) =>
aDisplayedColumnId === columnDefinition2.field,
);
if (displayedColumnIdIndex1 === -1) {
return 0;
} else if (displayedColumnIdIndex2 === -1) {
return 0;
} else {
return displayedColumnIdIndex1 - displayedColumnIdIndex2;
}
});
this.#changeDetectorRef.markForCheck();
}
}
#updateDisplayedItems(): void {
const sortOption = this.#dataState.activeSortOption;
if (this.#columnApi && sortOption) {
this.#columnApi.applyColumnState({
state: [
{
colId: sortOption.propertyName,
sort: sortOption.descending ? 'desc' : 'asc',
},
],
});
}
this.displayedItems = this.#filterItems(this.#searchItems(this.items));
if (this.#dataState.onlyShowSelected) {
this.displayedItems = this.displayedItems.filter((item) => item.selected);
}
if (this.displayedItems.length > 0) {
this.#gridApi?.hideOverlay();
} else {
this.#gridApi?.showNoRowsOverlay();
}
this.#dataManagerSvc.updateDataSummary(
{
totalItems: this.items.length,
itemsMatching: this.displayedItems.length,
},
this.#viewId,
);
}
}
<sky-data-manager>
<div class="sky-margin-stacked-xs">
<sky-data-manager-toolbar>
<sky-data-manager-toolbar-primary-item>
<button
aria-label="New dashboard"
class="sky-btn sky-btn-default"
type="button"
>
<sky-icon icon="add" />
New
</button>
</sky-data-manager-toolbar-primary-item>
</sky-data-manager-toolbar>
</div>
<div class="sky-margin-stacked-sm">
<sky-key-info class="sky-padding-horizontal-sm" layout="horizontal">
<sky-key-info-value class="sky-font-display-4">
{{ items.length }}
</sky-key-info-value>
<sky-key-info-label> Dashboards </sky-key-info-label>
</sky-key-info>
</div>
<sky-data-view skyAgGridDataManagerAdapter viewId="gridView">
<sky-ag-grid-wrapper>
<ag-grid-angular [gridOptions]="gridOptions" [rowData]="items" />
</sky-ag-grid-wrapper>
</sky-data-view>
</sky-data-manager>
export interface Item {
dashboard: string;
name: string;
lastUpdated: string;
}
import { Component } from '@angular/core';
import { SkyPageModule } from '@skyux/pages';
import { ListPageContentComponent } from './list-page-content.component';
@Component({
standalone: true,
selector: 'app-demo',
templateUrl: './demo.component.html',
imports: [ListPageContentComponent, SkyPageModule],
})
export class DemoComponent {}
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { SkyPageHarness } from '@skyux/pages/testing';
import { DemoComponent } from './demo.component';
describe('List page list layout demo', () => {
async function setupTest(): Promise<{
pageHarness: SkyPageHarness;
fixture: ComponentFixture<DemoComponent>;
}> {
const fixture = TestBed.createComponent(DemoComponent);
const loader = TestbedHarnessEnvironment.documentRootLoader(fixture);
const pageHarness = await loader.getHarness(SkyPageHarness);
return { pageHarness, fixture };
}
beforeEach(() => {
TestBed.configureTestingModule({
imports: [DemoComponent, NoopAnimationsModule],
});
});
it('should have a list layout', async () => {
const { pageHarness } = await setupTest();
await expectAsync(pageHarness.getLayout()).toBeResolvedTo('list');
});
it('should have the correct page header text', async () => {
const { pageHarness } = await setupTest();
const pageHeaderHarness = await pageHarness.getPageHeader();
await expectAsync(pageHeaderHarness.getPageTitle()).toBeResolvedTo(
'Dashboards',
);
});
});
<sky-page layout="list">
<sky-page-header pageTitle="Dashboards" />
<sky-page-content>
<app-list-page-content />
</sky-page-content>
</sky-page>
import { Component } from '@angular/core';
import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { Item } from './item';
@Component({
standalone: true,
selector: 'app-dashboards-grid-context-menu',
templateUrl: './dashboards-grid-context-menu.component.html',
imports: [SkyDropdownModule],
})
export class DashboardGridContextMenuComponent
implements ICellRendererAngularComp
{
protected dashboardName = '';
public agInit(params: ICellRendererParams<Item>): void {
this.dashboardName = params.data?.dashboard ?? '';
}
public refresh(): boolean {
return false;
}
}
<sky-dropdown
buttonType="context-menu"
[label]="'context menu for ' + dashboardName"
>
<sky-dropdown-menu>
<sky-dropdown-item>
<button type="button" [attr.aria-label]="'Edit ' + dashboardName">
Edit
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button type="button" [attr.aria-label]="'Copy ' + dashboardName">
Copy
</button>
</sky-dropdown-item>
<sky-dropdown-item>
<button type="button" [attr.aria-label]="'Delete ' + dashboardName">
Delete
</button>
</sky-dropdown-item>
</sky-dropdown-menu>
</sky-dropdown>
import { Component, OnInit, inject } from '@angular/core';
import { SkyAgGridModule, SkyAgGridService } from '@skyux/ag-grid';
import {
SkyDataManagerModule,
SkyDataManagerService,
SkyDataManagerState,
SkyDataViewConfig,
} from '@skyux/data-manager';
import { SkyIconModule, SkyKeyInfoModule } from '@skyux/indicators';
import { AgGridModule } from 'ag-grid-angular';
import { ColDef, GridOptions, ICellRendererParams } from 'ag-grid-community';
import { DashboardGridContextMenuComponent } from './dashboards-grid-context-menu.component';
import { Item } from './item';
@Component({
standalone: true,
selector: 'app-list-page-content',
templateUrl: './list-page-content.component.html',
providers: [SkyDataManagerService],
imports: [
AgGridModule,
SkyAgGridModule,
SkyDataManagerModule,
SkyIconModule,
SkyKeyInfoModule,
],
})
export class ListPageContentComponent implements OnInit {
protected items: Item[] = [
{
dashboard: 'Cash Flow Tracker',
name: 'Kanesha Hutto',
lastUpdated: '06/21/2023',
},
{
dashboard: 'Accounts Receivable Dashboard',
name: 'Kristeen Lunsford',
lastUpdated: '06/30/2023',
},
{
dashboard: 'Accounts Payable Dashboard',
name: 'Darcel Lenz',
lastUpdated: '04/20/2023',
},
{
dashboard: 'Budget vs. Actual',
name: 'Barbara Durr',
lastUpdated: '12/04/2023',
},
{
dashboard: 'Balance Sheet - New',
name: 'Ilene Woo',
lastUpdated: '12/20/2023',
},
{
dashboard: 'Debt Management',
name: 'Tonja Sanderson',
lastUpdated: '09/10/2023',
},
];
protected gridOptions: GridOptions;
#columnDefs: ColDef[] = [
{
colId: 'contextMenu',
headerName: '',
sortable: false,
cellRenderer: DashboardGridContextMenuComponent,
maxWidth: 55,
},
{
colId: 'dashboard',
field: 'dashboard',
headerName: 'Name',
width: 150,
cellRenderer: (params: ICellRendererParams): string => {
return `<a href="/">${params.value}</a>`;
},
},
{
colId: 'name',
field: 'name',
headerName: 'Created By',
},
{
colId: 'lastUpdated',
field: 'lastUpdated',
headerName: 'Last Updated',
},
];
#viewConfig: SkyDataViewConfig = {
id: 'gridView',
name: 'Grid View',
searchEnabled: true,
};
readonly #dataManagerService = inject(SkyDataManagerService);
readonly #agGridSvc = inject(SkyAgGridService);
constructor() {
this.gridOptions = this.#agGridSvc.getGridOptions({
gridOptions: {
columnDefs: this.#columnDefs,
onGridReady: (args) => {
args.api.sizeColumnsToFit();
},
},
});
}
public ngOnInit(): void {
this.#dataManagerService.initDataManager({
activeViewId: 'gridView',
dataManagerConfig: {},
defaultDataState: new SkyDataManagerState({
views: [
{
viewId: 'gridView',
displayedColumnIds: [
'contextMenu',
'dashboard',
'name',
'lastUpdated',
],
},
],
}),
});
this.#dataManagerService.initDataView(this.#viewConfig);
}
}