Skip to main content Skip to navigation Skip to search

Datagrid

A datagrid is a presentation of data in a table which enables the user to perform actions upon the entries which are organized in rows, with columns for each attribute.

Usage

Datagrids are for organizing large volumes of data that users can scan, compare, and perform actions on.

  • Do not use a datagrid when you need to present static data - this is what tables are for.
  • Do not use a datagrid when you require hierarchy within the data - tree tables a.k.a hierarchical datagrids are not supported by Clarity because of usability and accessibility issues.

Content

A datagrid is well-suited for presenting large volumes of data that don’t fit on one page. Users can filter and sort the data according to preference.

For smaller amounts of data (10 to 20 lines), datagrids are a relatively heavy component. Use datagrids if:

  • The data set will grow
  • Users need search, filter, or batch operations
  • For a smaller volume of data, use a table. Tables are a lighter-weight solution with a static view..

For data sets with a blend of text, images, and data visualizations, or content with mixed formatting, cards offer a better layout.

Code & Examples

Basic Structure

To use our Datagrid, you do not need to pass an array of data or a JSON configuration to a single element. Instead, we leverage a pure declarative API, just like any other Angular component. You write your HTML just as you would for a basic table, with a *ngFor (or *clrDgItems, see Smart iterator below) on the rows to iterate over your data.

basic structure

Custom Cell Rendering

The contents of datagrid cells or column headers can be as complex as you need them to be, with nested components and interpolation (define nested components and interpolation for designers)

Because we use a declarative API, simply projecting your HTML inside our components' templates, you have complete control over what we display. The contents of datagrid cells or column headers can be as complex as you need them to be, with nested components and interpolation.

Smart Iterator

If you want to let us handle all the data processing needed by our various features, you will need to use *clrDgItems instead of *ngFor. They have the exact syntax and behave the same way, but the former lets us have full control what is actually being displayed. As you can see in the following example it doesn't change anything for our simple case, but it will as soon as we start adding features like sorting, filtering, pagination, etc.,

Binding Properties

For an easy setup of datagrid column features, you can simply specify the property to bind it to in your model. When you do, the column will benefit from all built-in features for this case: sorting based on the natural comparison, filtering using either of the built-in filters, and anything else we might add in the future. You can bind to as deep a property as you want in your model, using a standard dot-separated syntax: [clrDgField]="'my.deep.property'"

You can also see in the following example how every feature we offer is always opt-in: we did not declare any binding on the "User ID" column, which means it is not sortable or filterable.

By default, bound columns are assumed to contain string-like contents and the user is presented with the normal string filter. If you know that the contents of the column will be numeric, you can instead use the built-in numeric range filter by adding [clrDgColType]="'number'". You can see an example of this in the "Wins" column.

In this example, the [clrDgField] input is a hard-coded string, so it needs to be quoted twice: [clrDgField]="'name'". Another way to write this would be clrDgField="name", without having the extra quotes, but we do not recommend this. In particular, this leaves a potentially unwanted attribute on the element, whereas the previous syntax only adds a property to the corresponding Javascript object.

Custom Sorting

Sometimes, the natural sort order for a property is not the relevant one. Sometimes, a column is not even a property on your model but is dynamically generated instead. In these cases, you might want to specify a custom comparator to sort the column according to your needs. This can be done by providing a comparator through the [clrDgSortBy] input, whether or not your column is declared as a clrDgField, and will always take precedence over it if it is.

A comparator is just an object that implements a compare method that could be given as parameter to Javascript's native Array.sort() function. In other words, if a and b are two elements being compared, then:

  • if compare(a, b) is less than 0, a comes first,
  • if compare(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all other items,
  • if compare(a, b) is greater than 0, b comes first. The safest way to check that your types comply with our API is to have your comparator be an instance of a class that implement the ClrDatagridComparatorInterface interface provided by Clarity.

Why use an object instead of the function directly? Using an object implementing an interface allows strong type-checking, which is safer for your application.If your sorting function does not comply with our API, you will get a clear error during typescript compilation, instead of an obscure one during runtime. Admittedly, we could achieve strong typing by exporting a function signature instead of a whole interface, but not only do interfaces leave room for future features without forcing breaking changes, they also encourage you to write your business logic outside of the controller, naturally creating pure Typescript or Javascript "logic" classes, which are far more reusable.

In our example, everyone knows pokemon should not be sorted lexicographically, but according to Pokédex number.

Pre-Sorted Columns

Columns can be pre-sorted ascending or descending by declaring the clrSortOrder input on clr-dg-column. You must also provide the [clrDgField] so it knows what field in the provided object to sort on. Clarity provides an enum for such a scenario: ClrDatagridSortOrder

Here is an example that presorts the Name column for descending sort order.

Custom Filtering

Similarly to the advanced sorting features, sometimes the default filter on a string property is not what you need. When this is the case you can write your own filter, with fully custom template and controller, and both wrap it in and pass it to a <clr-dg-filter> component in your column declaration. This can be done whether or not your column is declared as a clrDgField, and will always take precedence over it if it is.

The filter you provide to the <clr-dg-filter> component needs to implement the ClrDatagridFilterInterface interface provided by Clarity:

There are several ways to pass your filter to the <clr-dg-filter> component:

  • The simplest, but less reusable way, is to simply inline your filter's template in the column and use the [clrDgFilter] input to pass your filter instance:
  • A more reusable way is to write an actual component for your custom filter, and inject its DatagridFilter parent in its constructor so that it can register itself:
  • Finally, if you want to have a completely reusable filter independently of our Datagrid, you can write a component for it and use a template reference variable to declare the filter to its container:

In our example, we can create "color picker" filter, rather than have to search by color name.

By default, filtering searches the original model value for matches. In cases where you format the text for display (such as using a pipe), you may want to create a custom filter to handle searching the formatted text. Otherwise, the results you see may not be filtered in the way you expect.

In the example above you will need to go with custom filters that will take into account the data that the user sees is the same that he is searching into.

Preset Column Filters

You can use a preset filter on columns to initialize the data grid to a specific filtered state. [(clrFilterValue)] can be pre-set.

Built-in Filters

Before reading this, you should make sure you read the previous section on custom filters. Done? Then you might be a bit overwhelmed by the complexity of custom filters, understandably. What if you want just a bit more than default string value filters, but phenomenal cosmic filter power turns out to be slightly overkill? This is where our built-in custom filters come handy. They let you customize specific parts of the filter like the filter matching function, without having to rewrite the whole thing yourself from two-way binding inputs to integration in the datagrid.

String Filter

The first and default filter is the "string" filter one, meaning the user is offered a text input, and the rows will be filtered based on a string-matching function you provide. You should now be familiar with our use of interfaces for this, so here is the interface your string matcher should implement:

Once you have it, you simply need to pass it to a <clr-dg-string-filter> component:

In our example, we can allow the user to filter not only by pokemon name, but also by entering the exact number of the pokemon they are interested in.

Numeric filter

Another built-in filter is the numeric filter, which allows you to filter a column by a minimum and/or maximum numeric value. The "Wins" column demonstrates the numeric filter. You provide the function logic and the user can optionally enter high and low limits for elements in the column. In this case, use a <clr-dg-numeric-filter> component and pass the filter to the [clrDgNumericFilter] property.

In the example below, we are implementing the string filter on pokemons' names.

Filters with preset values

You can use a preset filter with either of the built-in filters to initialize the data grid to a specific state. [(clrFilterValue)] can be pre-set to a string for a string filter or a range of numbers for a numeric filter. With numeric filters you can pass null for either of the limits to not set it. The example below sets a lower limit of 10 and no upper limit.

We are planning on writing more of these semi-customisable filters in future releases, including a filter where the user selects values among the ones that are actually present in the data. If the one you are looking for isn't implemented yet, you can absolutely write it yourself using the fully customisable filters. And if you think it's good, feel free to contribute back to Clarity and add it for everyone!

Pagination

The Clarity datagrid supports pagination because the point of a datagrid is to display large amounts of data, and it often can't be displayed all at the same time on the page.

To activate pagination on your datagrid, you simply need to add a <clr-dg-pagination> component in your datagrid's footer. This component exposes many of properties, most of them are bindable, to help you interact with it. You can optionally include a clr-dg-page-size component to bind a list of options to display for toggling the number of items per page.

Here is an example of how to use pagination, and attach a template reference variable to it to display information on the current page.

Selection

To allow actions on multiple items at once, we provide the ability for the user to select rows in the datagrid. To make rows selectable in your datagrid, you need to do the following:

  • Add a [clrDgItem] input on each <clr-dg-row> component to tell us what model the user is actually selecting. Most of the time, this will simply be the current data object in the iteration.
  • Add a [(clrDgSelected)] two-way binding on the datagrid itself, to have access to the list of currently selected items. Note that by adding items to this list yourself, you can dynamically select elements if you need to.

In addition to a checkbox for each row to select individual rows, there will be a checkbox in the header row that will select all of the currently visible rows.

In the following example, we simply display the names of the selected users, but since we have access to the full objects, we could perform any operation we want on them.

If you need an easier access to the selected state of a row, without having to go through the entire array, we also provide a boolean [(clrDgSelected)] two-way binding on the <clr-dg-row> itself. For instance, when your model itself tracks if items are selected, you can simply write:

If you need to listen to when the selection changes, you can use Angular's two way binding to listen to the (clrDgSelectedChange) event:

Mark a row with clrDgSelectable, this way the state of the row could not be changed by user interactions. This property works only when using single or multi-selection modes.

Preserving Selection

By default, when a filter is applied to the datagrid the selection is cleared. This is done to ensure that all selected items are always visible in the datagrid. In certain instances, this might not be desirable, therefore we provide the [clrDgPreserveSelection] input. Setting this to true will retain the current selection on the datagrid, even if filters are applied and selected items are not visible.

Note: If you do enable [clrDgPreserveSelection], before performing any action on the selected items, a confirmation should be shown to ensure the end-user is aware of which items they are operating on, since the filters may hide some of the selected items from the user causing a discovery issue.

Single Selection

Depending on the use case, you might want to restrict the user to only allow single selection in a datagrid. If you haven't done so, please read the previous section on general selection first.

  • For single select, instead of [(clrDgSelected)], add a [(clrDgSingleSelected)] two-way binding on the datagrid itself, to have access to the currently selected item. Note that by setting this value yourself, you can dynamically select an element if you need to.

In the following example, we simply display the name of the selected user, but since we have access to the full objects, we could perform any operation we want on it.

If you need to listen to when the selection changes, you can use Angular's two way binding to listen to the (clrDgSingleSelectedChange) event:

In order to conditionally disable selection on a row, use the clrDgSelectable input to disable selection state changes. This has to be done on each row you wish to disable, and works with single and multi selection.

Batch Action

You can allow batch actions to be performed on selected rows in selectable datagrids. You can make the action choices contextual to the selection by showing certain actions only if the selection meets the criteria. Add a clr-dg-action-bar inside a clr-datagrid. The content inside of it will be projected when one or more items is selected. We recommend that that you use a button bar with small buttons as in the example.

In the following example, we simply display the names of the selected users, but since we have access to the full objects, we could perform any operation we want on them.

Depending on the role of certain batch actions, you can choose to break button bars up into separate button groups. To increase the visibility of the most important batch actions within each button group, we recommend organizing batch actions in priority order from left to right.

Single Action

You can allow actions on an item in a single row, in the cases where batch operation is not applicable. You can use this pattern in both selectable and non-selectable datagrids. Add a clr-dg-action-overflow inside a clr-dg-row. The content inside of it will be projected as an action menu which will toggle when the user clicks on the ellipsis icon as shown below. We recommend that the menu items be buttons with a class .action-item as in the example.

In the following example, we simply display the names of the selected users, but since we have access to the full objects, we could perform any operation we want on them.

Server-Driven datagrid

When dealing with large amounts of data or heavy processing, a datagrid often has access the currently displayed data only, requesting only the necessary pieces from the server. This is a very common case that we fully support.

We expose events and hooks on all parts of the datagrid to make sure you can trigger any requests you need based on precise user actions. But an important thing to note is that when the server does handle pagination, it needs to also deal with sorting and filtering, because all three are tightly coupled. In light of this, we decided to expose a single global output (clrDgRefresh) that emits the current "state" of the datagrid whenever it changes due to a user action or an external one. This state has the following format:

It contains all the information you need to send to the server in order to get the slice of data currently displayed. It even contains redundant information ( page.to and page.size for instance), to make sure you have access to the most practical for you without extra processing.

The filters array contains either a custom state object returned by the state method of the filter or the filter instance itself if the optional state method is not implemented.

One important thing to note is that since you don't have all the data available at once, you cannot use the smart iterator *clrDgItems: it would sort, filter and paginate a subset of the data that has already gone through all that. So all you need to do is to go back to a simple *ngFor, which we support.

Finally, since server calls are involved, we need some way of notifying the user that his action has been acknowledged and that we are currently working on it. To this effect, we provide an input [clrDgLoading] that you can use to display the datagrid in a loading state, while fetching data.

Placeholder

Your datagrid can be empty for any number of reasons: you are still fetching the data from the server, the filters selected by the user are too strict, or simply you didn't find any data to display in it. In these cases, we display a simple placeholder image, but it can be useful to display a message to the user explaining what is happening. To do so, simply add a <clr-dg-placeholder> element next to your rows.

basic structure

Detail Pane

The Detail Pane is a pattern to show additional details for a record. The Detail Pane condenses the datagrid to show a primary column and a panel to the right to display more details about your record. The Detail Paine allows you to show the full content of the record in a larger scrollable space. The Detail Pane is also fully accessible for keyboard and screen reader users.

The Detail Pane adds a new toggle icon on the left-hand side of the Datagrid. When the pane is open, it takes 2/3 of the width of the Datagrid and hides all columns, except for the first column and any built-in columns that facilitate features like selection and row actions. The pagination also updates to a condensed format that scales well to small sizes. Only one row can be open at a time; selecting another row changes the content to the newly selected row.

The Detail Pane is not compatible with Expandable Rows; when both are enabled, the Detail Pane takes precedence. Hide and show columns are disabled while the Detail Pane is open, but still works properly when closed. The rest of the Datagrid behaviors work as expected, even while the Detail Pane is open.

Basic use of Detail Pane

To use the Detail Pane, add a new element with the following syntax inside of the Datagrid

Reacting to changes in Detail Pane state

It is possible to listen for changes to the Detail Pane state, by desugaring the *clrIfDetail directive and listening for the (clrIfDetailChange) event. It is important to use a local template variable like let-detail to reference the row object.

Controlling the Detail Pane programmatically

In some cases, you might want to programmatically control the toggling of the Detail Pane, which you can do by desugaring *clrIfDetail directive and using the two way binding syntax. To open the detail pane, set the detailState value to the row object, or to null to close it. Be sure to include the local template variable like let-detail to get access to the row object.

Expandable Rows

Use expandable rows when you have additional information for a row, or row cells that do not need to be shown at all times. This helps minimize visual clutter. It also frees up the need of a second datagrid that gets updated with details. This is sometimes called a master-detail or master-child pattern. Another use is replacing original row data with a custom view or layout which includes most or all of the original row data. The expanded area can be loaded with other components as well to fit your needs.

To make a row expandable, you need to put a <clr-dg-row-detail> component inside your row, and add a *clrIfExpanded structural directive on it. This directive doesn't take any input, it is here for 2 reasons: make sure the details are only instantiated once they are needed, and make it very clear in your application templates that this part of the DOM is not present at all times, but only when the row is expanded. This component can contain anything: text, images, graphs, ... It can ignore the overall table layout. If you wish to display details for each cell of the row individually and respect the table layout, all you need to do is use our usual <clr-dg-cell> component in the detail. Make sure you have exactly as many cells in the detail as you have in the row, or they will not align properly.

If you want the details to replace the original row rather than expand under it, we offer a [clrDgReplace] input that receives a boolean on the <clr-dg-row-detail> component. In other words, to make details replace the row they're in, just write:

Sometimes you want to conditionally display the expandable row, depending on if the given row has any content to expand. In order to handle this, you'll need to wrap your expandable row in a conditional *ngIf directive to handle this, but since you can't put two structural directives on the same element you'll need to use NgContainer and ngProjectAs like you see here in the following snippet.

Finally, you might need to make a server call to get the details for a row before you can display them. This is a very common lazy loading pattern. In this case, you need to add a [clrLoading] directive receiving a boolean anywhere in the row. Yes, it can be absolutely anywhere, as long as it's in or on the row itself. The easiest way to make the server call lazily is simply to create a component that will make the call on initialization (typically in the ngOnInit() method), and to use that component inside the *clrIfExpanded structural directive. Here is an example of what this solution typically looks like.

Note the ngProjectAs attribute on our custom detail component. This is needed to make sure it is projected in the same place an actual clr-dg-row-detail would be.

Hide/Show Columns

Datagrid columns may be shown / hidden by the user. A UI control on the left side of the footer surfaces a popup with the available columns listed. Activating the checkbox gives the user access to this function.

Datagrid columns are hideable with the *clrDgHideableColumn directive. Because this is a structural directive it cannot be used on the clr-dg-column component directly. Instead, you use *clrDgHideableColumn on an ng-container inside your clr-dg-column. It defaults to showing the column but you can use the hidden property to pre-configure it.

Compact Datagrid

To increase the information density of your Datagrid or to decrease the amount of space it takes up, add the datagrid-compact class to it. This class decreases the amount of whitespace paddings in the default Datagrid style.

basic structure