Skip to content

Commit

Permalink
feat(ngrid): use intersection observer
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
`resize-observer-polyfill` is no longer a peerDependency and is not required by the library. If you want polyfill support please import the polyfill using polyfill.ts
  • Loading branch information
shlomiassaf committed Dec 3, 2020
1 parent db28ef2 commit 161371b
Show file tree
Hide file tree
Showing 48 changed files with 556 additions and 146 deletions.
2 changes: 2 additions & 0 deletions apps/ngrid-dev-app/src/polyfills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
// We use types from `@types/resize-observer-browser` so we import directly to prevent conflicts
import 'resize-observer-polyfill/dist/ResizeObserver';

/***************************************************************************************************
* APPLICATION IMPORTS
Expand Down
4 changes: 3 additions & 1 deletion apps/ngrid-dev-app/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": []
"types": [
"@types/resize-observer-browser"
]
},
"exclude": [
"./src/test.ts",
Expand Down
2 changes: 1 addition & 1 deletion apps/ngrid-dev-app/tsconfig.editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"extends": "./tsconfig.json",
"include": ["**/*.ts"],
"compilerOptions": {
"types": ["jasmine", "node"]
"types": ["jasmine", "node", "@types/resize-observer-browser"]
}
}
2 changes: 1 addition & 1 deletion apps/ngrid-dev-app/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["jasmine", "node"]
"types": ["jasmine", "node", "@types/resize-observer-browser"]
},
"files": ["src/test.ts", "src/polyfills.ts"],
"include": ["**/*.spec.ts", "**/*.d.ts"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: What is the Context
path: concepts/context/what-is-the-context
parent: concepts/context
tags: context, contextApi
tags: context, contextApi, IntersectionObserver
ordinal: 1
---
# What is the Context
Expand Down Expand Up @@ -47,6 +47,73 @@ I> If there is no unique column context support is available but limited.
For example, if we remove the `pIndex` from the example above, each click for sort/pagination will clear the cache since
there is no way for the context to identify and match exiting context to rows.

## The Data Row Context

The data row context is the object which holds the state of a data row (including the row's data item) and some methods to operate on the context.

Let's first focus on the properties:

```typescript
export interface PblNgridRowContext<T = any> {
/** Data for the row that this cell is located within. */
$implicit?: T;
/** Length of the number of total rows rendered rows. */
count?: number;
/** True if this cell is contained in the first row. */
first?: boolean;
/** True if this cell is contained in the last row. */
last?: boolean;
/** True if this cell is contained in a row with an even-numbered index. */
even?: boolean;
/** True if this cell is contained in a row with an odd-numbered index. */
odd?: boolean;

/** The identity of this row */
identity: number;

/**
* When true, it is the first time that the row is rendered.
* Once the row leaves the view this will be false and will not change.
*
* Note that rendered items might appear outside of the viewport if virtual scroll is not set and
* when set but the row is rendered as part of the buffer.
*
* This is relevant only when virtual scroll is set.
*/
firstRender: boolean;

/**
* When true, indicates that the row is rendered outside of the viewport.
*
* The indicator is updated when rows are rendered (i.e. not live, on scroll events).
* Understanding this behavior is important!!!
*
* Note that when virtual scroll is enabled `true` indicates a buffer row.
*/
outOfView: boolean;

/** The index at the datasource */
dsIndex: number;

readonly grid: PblNgridComponent<T>;

/**
* Returns the length of cells context stored in this row
*/
readonly length: number;
}
```

`$implicit` is the actual data row and the default property provided by angular.
All other properties are straight forwards, let's see them in action:


<div pbl-example-view="pbl-context-object-example"></div>

> When using [virtual scroll](../../../features/grid/virtual-scroll/what-is-virtual-scroll), `count` represents the rendered rows, not the total rows.
I> `identity` is populated based on the [identity column](../../columns/identity), if none define the data index is used.

## Context Pitfalls

Some key points:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import { PblNgridTargetEventsModule } from '@pebula/ngrid/target-events';
import { PblNgridPaginatorModule } from '@pebula/ngrid-material/paginator';
import { PblNgridMatSortModule } from '@pebula/ngrid-material/sort';
import { ContextExampleExample } from './context-example.component';
import { ContextObjectExample } from './context-object.component';

@NgModule({
declarations: [ ContextExampleExample ],
declarations: [ ContextExampleExample, ContextObjectExample ],
imports: [
ExampleCommonModule,
PblNgridModule, PblNgridTargetEventsModule, PblNgridPaginatorModule, PblNgridMatSortModule,
],
exports: [ ContextExampleExample ],
exports: [ ContextExampleExample, ContextObjectExample ],
})
@BindNgModule(ContextExampleExample)
@BindNgModule(ContextExampleExample, ContextObjectExample)
export class ContextExampleExampleModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<pbl-ngrid [dataSource]="ds" [columns]="columns">

<div *pblNgridCellDef="'dsIndex'; let ctx">
{{ctx.rowContext.dsIndex}}
</div>

<div *pblNgridCellDef="'index'; let ctx">
{{ctx.rowContext.index}}
</div>


<div *pblNgridCellDef="'firstRender'; let ctx">
{{!!ctx.rowContext.firstRender}}
</div>

<div *pblNgridCellDef="'outOfView'; let ctx">
{{!!ctx.rowContext.outOfView}}
</div>

<div *pblNgridCellDef="'count'; let ctx">
{{ctx.rowContext.count}}
</div>

<div *pblNgridCellDef="'even'; let ctx">
{{!!ctx.rowContext.even}}
</div>

<div *pblNgridCellDef="'odd'; let ctx">
{{!!ctx.rowContext.odd}}
</div>

<div *pblNgridCellDef="'first'; let ctx">
{{!!ctx.rowContext.first}}
</div>

<div *pblNgridCellDef="'last'; let ctx">
{{!!ctx.rowContext.last}}
</div>

<div *pblNgridCellDef="'identity'; let ctx">
{{ctx.rowContext.identity}}
</div>

</pbl-ngrid>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { createDS, columnFactory } from '@pebula/ngrid';

import { Person, DynamicClientApi } from '@pebula/apps/docs-app-lib/client-api';
import { Example } from '@pebula/apps/docs-app-lib';

@Component({
selector: 'pbl-context-object-example',
templateUrl: './context-object.component.html',
styleUrls: ['./context-object.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
@Example('pbl-context-object-example', { title: 'Context Object' })
export class ContextObjectExample {
columns = columnFactory()
.table(
{ prop: 'id', width: '50px' },
{ prop: 'name', width: '100px', pIndex: true },
{ prop: 'firstRender', width: '50px' },
{ prop: 'outOfView', width: '50px' },
{ prop: 'dsIndex', width: '50px' },
{ prop: 'index', width: '50px' },
{ prop: 'count', width: '50px' },
{ prop: 'odd', width: '50px' },
{ prop: 'even', width: '50px' },
{ prop: 'first', width: '50px' },
{ prop: 'last', width: '50px' },
{ prop: 'identity', width: '100px' },
)
.build();
ds = createDS<Person>().onTrigger( () => this.datasource.getPeople(100, 500) ).create();

constructor(private datasource: DynamicClientApi) { }
}
107 changes: 107 additions & 0 deletions apps/ngrid-docs-app/content/concepts/grid/browser-apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
title: Compatibility & Browser APIs
path: concepts/grid/browser-apis
parent: concepts/grid
ordinal: 4
---
# Compatibility & Browser APIs

## Compatibility

**nGrid** support all browsers that angular & the angular CDK support.

## Browser APIs

**nGrid** makes use of the following browsers APIs:

### ResizeObserver

The [ResizeObserver API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) is used to detect resize in column width's and react to them by re-calculating the new widths.

The **ResizeObserver API** is supported in all major browsers, however it is not supported by **Internet Explorer**
If you require full support or want a polyfill just in case you can use one of the following polyfills:

- [resize-observer-polyfill](https://github.com/que-etc/resize-observer-polyfill) - The most used polyfill (performs auto-detect)
- [@juggle/resize-observer](https://github.com/juggle/resize-observer) - A newer, more modern polyfill (does NOT auto-detect)

You can add them to your `polyfill.ts` file.

I> There might be more differences between the 2, please read the documentation of each one for more in-depth information

**resize-observer-polyfill** will automatically detect if the API is implemented and if not will add it so you can safely do:

```typescript
import 'resize-observer-polyfill';
```

In your `polyfill.ts`.

Note the polyfill has built-in type support (d.ts) auto-loaded when you import it, so if you already have types
loaded for `ResizeObserver` and want to keep them, load the polyfill directly without passing through the `package.json`

```typescript
import 'resize-observer-polyfill/dist/ResizeObserver';
```

**@juggle/resize-observer** does not auto-detect so doing the above will override the native implementation, if one exists.

So you should do something like this:

```typescript
import { ResizeObserver as Polyfill } from '@juggle/resize-observer';

if ('ResizeObserver' in window === false) {
window.ResizeObserver = Polyfill;
}
```

There are more options for this polyfill, including a demo how to load it async using dynamic imports, read more at the polyfill's site.

I> **nGrid** makes minimal use of the API so changes in the final spec should have no effect.
@types/resize-observer-brow

### ResizeObserver Types

If you enable library type checking and TypeScript is complaining about `ResizeObserver` types missing, install the following:

```bash
yarn add -D @types/resize-observer-browser

# OR

npm install -D @types/resize-observer-browser
```

This should not happen as both polyfills come with types build in...

> If you have specific `types` defined in your `tsconfig.json` add it there as well.
### IntersectionObserver

The [IntersectionObserver API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) is used for performance.

It allows **nGrid** to get notified when a row is visible and when it is not.
With this API **nGrid** can skip a lot of layout reflows and recalculations because it does not need to check if a row is out of view.
This is very important for performance, especially when using virtual scroll.

The **IntersectionObserver API** is a more mature API compared to the `ResizeObserver API`, it is shipped with all major browsers for a long time now
and comes as part of the DOM type library in TypeScript.

That said, it is not supported in Internet Explorer.

In this case we do not provide recommendation for a polyfill, you can use any good polyfill out there or you can **DISABLE** the use of the API
so **nGrid** will not use it to detect the changes and instead perform it's own magic for that. This will slightly degrade performance, how much
depends on the use case, **nGrid** instance configuration and features used together.

To disable the use of **IntersectionObserver** apply the following provider in your application root module:

```typescript
import { DISABLE_INTERSECTION_OBSERVABLE } from '@pebula/ngrid';

@NgModule({
providers: [
{ provide: DISABLE_INTERSECTION_OBSERVABLE, useValue: true },
]
})
class MyAppRootModule { }
```
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<pbl-ngrid #grid1 [dataSource]="ds" [columns]="columns" [hideColumns]="hideColumns">
<pbl-ngrid #nGrid [dataSource]="ds" [columns]="columns" [hideColumns]="hideColumns">
<div *pblNgridCellDef="'renderIndex'; let ctx">
{{ctx.rowContext.index}}
</div>
<div *pblNgridCellDef="'__isFirstRender'; let ctx">
{{!!ctx.rowContext.firstRender}}
</div>
Expand All @@ -12,5 +15,5 @@

<ul>
<li>Clicking on any cell in the column <b>name</b> will trigger an input box, leaving the input box (blur) will return to the read-only view.</li>
<li>Clicking on <button mat-button (click)="update(grid1)">this button</button> will trigger edit on the 4th <b>rendered</b> row and the 2nd cell (name).</li>
<li>Clicking on <button mat-button (click)="update(nGrid)">this button</button> will trigger edit on the 4th <b>rendered</b> row and the 2nd cell (name).</li>
</ul>
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class CellEditExample {
{ prop: 'name', editable: true },
{ prop: 'gender', width: '50px' },
{ prop: 'birthdate' },
{ prop: 'renderIndex', label: 'Render Index', width: '60px' },
{ prop: '__isFirstRender', label: 'First Render?', width: '60px' },
)
.build();
Expand Down
3 changes: 2 additions & 1 deletion apps/ngrid-docs-app/src/polyfills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.

// We use types from `@types/resize-observer-browser` so we import directly to prevent conflicts
import 'resize-observer-polyfill/dist/ResizeObserver';

/***************************************************************************************************
* APPLICATION IMPORTS
Expand Down
4 changes: 3 additions & 1 deletion apps/ngrid-docs-app/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": []
"types": [
"@types/resize-observer-browser"
]
},

"exclude": [
Expand Down
3 changes: 2 additions & 1 deletion apps/ngrid-docs-app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"compilerOptions": {
"types": [
"node",
"jest"
"jest",
"@types/resize-observer-browser"
]
},
"references": [
Expand Down
3 changes: 2 additions & 1 deletion apps/ngrid-docs-app/tsconfig.spec.jest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"module": "commonjs",
"types": [
"jest",
"node"
"node",
"@types/resize-observer-browser"
]
},
"files": [
Expand Down
Loading

0 comments on commit 161371b

Please sign in to comment.