Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PORT Angular hierarchical grid topics #466

Open
wants to merge 3 commits into
base: vnext
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 315 additions & 0 deletions doc/en/components/grids/hierarchical-grid/load-on-demand.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
---
title: {Platform} Hierarchical Data Grid | Material Table | {ProductName} | Infragistics
_description: The Ignite UI for {Platform} Hierarchical Grid provides the necessary tools to load data on demand for each child grid that is expanded. That way the volume of data would be greatly reduced and can be retrieved only when the user needs it.
_keywords: {Platform} data grid, igniteui for {Platform}, infragistics
---

# Hierarchical Grid Load On Demand

The Ignite UI for {Platform} `HierarchicalGrid` allows fast rendering by requesting the minimum amount of data to be retrieved from the server so that the user can see the result in view and interact with the visible data as quickly as possible. Initially only the root grid’s data is retrieved and rendered, only after the user expands a row containing a child grid, he will receive the data for that particular child grid. This mechanism, also known as Load on Demand, can be easily configured to work with any remote data.


This topic demonstrates how to configure Load on Demand by creating a Remote Service Provider that communicates with an already available remote oData v4 Service. Here's the working demo and later we will go through it step by step and describe the process of creating it.


## {Platform} Hierarchical Grid Load On Demand Example


<code-view style="height:620px"
data-demos-base-url="{environment:demosBaseUrl}"
iframe-src="{environment:demosBaseUrl}/hierarchical-grid/hierarchical-grid-lod" alt="{Platform} Hierarchical Grid Load On Demand Example">
</code-view>

<div class="divider--half"></div>

### Remote Service Provider

First we will prepare our service provider so we will be ready to get the data we would need for the hierarchical grid.

#### Getting basic data

We will be communicating with our backend service over HTTP protocol using the XMLHttpRequest interface the browsers provide. In order to achieve this more easily we will use {Platform}'s `HttpClient` module that offers a simplified client HTTP API. That way in order to get our data we will need this simple method in our service:


```typescript
public getData(dataState): Observable<any[]> {
return this.http.get(this.buildUrl(dataState)).pipe(
map(response => response['value']),
);
}
```

As you can see `this.http` will be a reference to our `HttpCLient` module, and `buildUrl()` will be the method that will generate our url based on the data that we have received. We map our response so we get only the value of our result and return an Observable, since this is executed asynchronously. That way we can later subscribe to it, process it further in our application and pass it to our grid.

#### Building our request url

Next we will define how we should build our URL for the GET request. This is where we will be able to get the data for our main grid but also for any child grid inside it. We will use the `Customers` data from [here](https://services.odata.org/V4/Northwind/Northwind.svc/) for our root level and use `Order` and `Order_Details` for the lower levels. The model will differ per application but we will use the following one:

<img class="responsive-img" src="../../images/hgrid-database.jpg" />


What we first need is the `key` of our table to determine from where to get the data for the desired grid, the primary key of the parent row and its unique ID. We will define all this in an interface called `IDataState`. An example:

```typescript
export interface IDataState {
key: string;
parentID: any;
parentKey: string;
rootLevel: boolean;
}

//...
public buildUrl(dataState: IDataState): string {
let qS = "";
if (dataState) {
qS += `${dataState.key}?`;

if (!dataState.rootLevel) {
if (typeof dataState.parentID === "string") {
qS += `$filter=${dataState.parentKey} eq '${dataState.parentID}'`;
} else {
qS += `$filter=${dataState.parentKey} eq ${dataState.parentID}`;
}
}
}
return `${this.url}${qS}`;
}
//...
```

#### Result

Finally, this is how our `remote-lod.service.ts` would look like:


```typescript
import { HttpClient } from '@{Platform}/common/http';
import { Injectable } from '@{Platform}/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface IDataState {
key: string;
parentID: any;
parentKey: string;
rootLevel: boolean;
}

@Injectable()
export class RemoteLoDService {
url = `https://services.odata.org/V4/Northwind/Northwind.svc/`;

constructor(private http: HttpClient) { }

public getData(dataState: IDataState): Observable<any[]> {
return this.http.get(this.buildUrl(dataState)).pipe(
map((response) => response['value'])
);
}

public buildUrl(dataState: IDataState): string {
let qS = "";
if (dataState) {
qS += `${dataState.key}?`;

if (!dataState.rootLevel) {
if (typeof dataState.parentID === "string") {
qS += `$filter=${dataState.parentKey} eq '${dataState.parentID}'`;
} else {
qS += `$filter=${dataState.parentKey} eq ${dataState.parentID}`;
}
}
}
return `${this.url}${qS}`;
}
}
```

### Hierarchical Grid Setup

Next we will setup our hierarchical grid and connect it to our remote service provider.

#### Template defining

First we will define our hierarchical grid template with the levels of hierarchy that we expect to have. We know that our root grid `primaryKey` for the customers is their `CustomerID`, for their orders on the first level - `OrderID` and respectively for order details - `ProductID`. Knowing each database table and their keys allows us to define our initial template:

```html
<-hierarchical-grid #hGrid [primaryKey]="'CustomerID'" [autoGenerate]="false" [height]="'600px'" [width]="'100%'">
<-column field="CustomerID" [hidden]="true"></-column>
<-column field="CompanyName"></-column>
<-column field="ContactName"></-column>
<-column field="ContactTitle"></-column>
<-column field="Country"></-column>
<-column field="Phone"></-column>
<-row-island [key]="'Orders'" [primaryKey]="'OrderID'" [autoGenerate]="false" >
<-column field="OrderID" [hidden]="true"></-column>
<-column field="ShipCountry"></-column>
<-column field="ShipCity"></-column>
<-column field="ShipAddress"></-column>
<-column field="OrderDate"></-column>
<-row-island [key]="'Order_Details'" [primaryKey]="'ProductID'" [autoGenerate]="false" >
<-column field="ProductID" [hidden]="true"></-column>
<-column field="Quantity"></-column>
<-column field="UnitPrice"></-column>
<-column field="Discount"></-column>
</-row-island>
</-row-island>
</-hierarchical-grid>
```

There is one thing missing in our template though, and that is the data for our root level hierarchical grid, and eventually its children. We will easily set the data of the root grid after getting its data from the service in our code later, since we can use the `#hGrid` reference. Setting the data for any child that has been expanded is a bit different.

When a row is expanded for the first time, a new child `HierarchicalGrid` is rendered for it and we need to get the reference for the newly created grid to set its data. That is why each `RowIsland` component provides the `gridCreated` event that is fired when a new child grid is created for that specific row island. We can use that to get the reference we need for the new grid, request its data from the service, and apply it.

We can use one method for all row islands since we built our service so that it needs only information if it is the root level, the key of the row island, the primary key of the parent row, and its unique identifier. All this information can be accessed either directly from the event arguments, or from the row island responsible for triggering the event.

Let's name the method that we will use `gridCreated`. Since the event `gridCreated` provides the `parentID` property, a reference to the row island as `owner` and the new child `grid` property, it will be passed as the first argument. We are only missing information about the parent row's `primaryKey`, but we can easily pass that as a second argument, depending on which row island we bind.

The template file `hierarchical-grid-lod.component.html`, with these changes added, would look like this:

```html
<-hierarchical-grid #hGrid [primaryKey]="'CustomerID'" [autoGenerate]="false" [height]="'600px'" [width]="'100%'">
<-column field="CustomerID" [hidden]="true"></-column>
<-column field="CompanyName"></-column>
<-column field="ContactName"></-column>
<-column field="ContactTitle"></-column>
<-column field="Country"></-column>
<-column field="Phone"></-column>
<-row-island [key]="'Orders'" [primaryKey]="'OrderID'" [autoGenerate]="false" (gridCreated)="gridCreated($event, 'CustomerID')">
<-column field="OrderID" [hidden]="true"></-column>
<-column field="ShipCountry"></-column>
<-column field="ShipCity"></-column>
<-column field="ShipAddress"></-column>
<-column field="OrderDate"></-column>
<-row-island [key]="'Order_Details'" [primaryKey]="'ProductID'" [autoGenerate]="false" (gridCreated)="gridCreated($event, 'OrderID')">
<-column field="ProductID" [hidden]="true"></-column>
<-column field="Quantity"></-column>
<-column field="UnitPrice"></-column>
<-column field="Discount"></-column>
</-row-island>
</-row-island>
</-hierarchical-grid>
```

#### Connecting our service

One of our final steps now will be to connect our previously created service to our hierarchical grid. Since we defined it as an `Injectable`, we can pass it as a provider to our application. We will get a reference to our root grid as well, by using `ViewChild` query to set its data:

````TypeScript
@Component({
providers: [RemoteLoDService],
selector: "app-hierarchical-grid-lod",
styleUrls: ["./hierarchical-grid-lod.component.scss"],
templateUrl: "./hierarchical-grid-lod.component.html"
})
export class HierarchicalGridLoDSampleComponent {
@ViewChild("hGrid")
public hGrid: HierarchicalGridComponent;

constructor(private remoteService: RemoteLoDService) { }
}
````

In order to make sure that out grid is rendered before we request its data from the service and assign it, we will use the `AfterViewInit` lifecycle hook. As it doesn't have any parents we can only pass that `rootLevel` is `true`, and the key for it, to the `getData` of our service. Since it returns an observable we will need to subscribe to it:

````TypeScript
public ngAfterViewInit() {
this.remoteService.getData({ parentID: null, rootLevel: true, key: "Customers" }).subscribe((data) => {
this.hGrid.data = data;
this.hGrid.cdr.detectChanges();
});
}
````

Next, we only need to create our `gridCreated` method that will request data for any new child grid created. It will be similar to getting the root level grid data, just this time we will need to pass more information, like `parentID` and `parentKey`. `rootLevel` will be `false` for any child:

````TypeScript
public gridCreated(event: IGridCreatedEventArgs, _parentKey: string) {
const dataState = {
key: event.owner.key,
parentID: event.parentID,
parentKey: _parentKey,
rootLevel: false
};
this.remoteService.getData(dataState).subscribe(
(data) => {
event.grid.data = data;
event.grid.cdr.detectChanges();
}
);
}
````

With this, the setup of our application is almost done. This last step aims to improve the user experience by informing the user that the data is still loading so he doesn't have to look at an empty grid in the meantime. That's why the `HierarchicalGrid` supports a loading indicator that can be displayed while the grid is empty. If new data is received, the loading indicator will hide and the data will be rendered.

#### Setup of loading indication

The `HierarchicalGrid` can display a loading indicator by setting the `isLoading` property to `true` while there is no data. We need to set it initially for the root grid and also when creating new child grids, until their data is loaded. We could always set it to `true` in our template, but we want to hide it and display that the grid has no data if the service returns an empty array by setting it to `false`.

In this case the final version of our `hierarchical-grid-lod.component.ts` would look like this:

````TypeScript
import { AfterViewInit, Component, ViewChild } from "@{Platform}/core";
import {
IGridCreatedEventArgs,
HierarchicalGridComponent,
RowIslandComponent
} from "igniteui-{Platform}";
import { RemoteLoDService } from "../services/remote-lod.service";

@Component({
providers: [RemoteLoDService],
selector: "app-hierarchical-grid-lod",
styleUrls: ["./hierarchical-grid-lod.component.scss"],
templateUrl: "./hierarchical-grid-lod.component.html"
})
export class HierarchicalGridLoDSampleComponent implements AfterViewInit {
@ViewChild("hGrid")
public hGrid: HierarchicalGridComponent;

constructor(private remoteService: RemoteLoDService) { }

public ngAfterViewInit() {
this.hGrid.isLoading = true;
this.remoteService.getData({ parentID: null, rootLevel: true, key: "Customers" }).subscribe((data) => {
this.hGrid.isLoading = false;
this.hGrid.data = data;
this.hGrid.cdr.detectChanges();
});
}

public gridCreated(event: IGridCreatedEventArgs, _parentKey: string) {
const dataState = {
key: event.owner.key,
parentID: event.parentID,
parentKey: _parentKey,
rootLevel: false
};
event.grid.isLoading = true;
this.remoteService.getData(dataState).subscribe(
(data) => {
event.grid.isLoading = false;
event.grid.data = data;
event.grid.cdr.detectChanges();
}
);
}
}
````

### API References

* `HierarchicalGridComponent`
* `RowIslandComponent`

### Additional Resources

<div class="divider--half"></div>

* [Hierarchical Grid Component](hierarchical-grid.md)

<div class="divider--half"></div>
Our community is active and always welcoming to new ideas.

* [Ignite UI for {Platform} **Forums**](https://www.infragistics.com/community/forums/f/ignite-ui-for-{Platform})
* [Ignite UI for {Platform} **GitHub**](https://github.com/IgniteUI/igniteui-{Platform})
Loading