Skip to content

Commit

Permalink
feat(components/dimension): provide signal to observe element size
Browse files Browse the repository at this point in the history
```ts
import {fromDimension} from '@scion/components/dimension';

const element: HTMLElement = ...;
const dimension = fromDimension(element);
```
  • Loading branch information
danielwiehl committed Oct 9, 2024
1 parent 31c5dd5 commit a160a1e
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 53 deletions.
2 changes: 1 addition & 1 deletion docs/site/scion-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Provides Angular-based components and directives with a focus on SCION requireme
You can choose between different presentations: `ellipsis`, `ripple`, `roller`, `spinner`.

- [**Dimension**][link-tool-dimension]\
Provides a directive for observing changes in the size of the host element.
Provides primitives for observing size changes of an element.

- [**SCION Design Tokens**][link-scion-design-tokens]\
SCION provides a set of design tokens to enable consistent design and theming of SCION components.
Expand Down
64 changes: 34 additions & 30 deletions docs/site/tools/dimension.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@

## [SCION Toolkit][menu-home] > [@scion/components][link-scion-components] > Dimension

The NPM sub-module `@scion/components/dimension` provides an Angular directive for observing the size of an HTML element. The directive emits the element's initial size, and then continuously emits when its size changes. It never completes.
The NPM sub-module `@scion/components/dimension` provides a signal and directive for observing size changes of an element.

Install the NPM module `@scion/components` as following:

```
npm install @scion/components @scion/toolkit @angular/cdk
```

<details>
<summary><strong>Installation and Usage</strong></summary>
<summary><strong>Dimension Directive</strong></summary>

1. Install `@scion/components` using the NPM command-line tool:
```
npm install @scion/components @scion/toolkit @angular/cdk
```
Observes the size of the host element.

1. Import `SciDimensionDirective` in your component.

```typescript
```ts
import {SciDimensionDirective} from '@scion/components/dimension';

@Component({
Expand All @@ -29,40 +32,41 @@ The NPM sub-module `@scion/components/dimension` provides an Angular directive f
}
```

Alternatively, import `SciDimensionModule` in the `NgModule` that declares your component.

```typescript
import {SciDimensionModule} from '@scion/components/dimension';

@NgModule({
imports: [SciDimensionModule]
})
export class AppModule {
}
```

1. Add the `sciDimension` directive to the HTML element for which you want to observe its size:
1. Add the `sciDimension` directive to the HTML element to observe.

```html
<div sciDimension (sciDimensionChange)="onDimensionChange($event)"></div>
```

1. Add the following method to the component:
```typescript
public onDimensionChange(dimension: Dimension): void {
1. Add method to be notified about size changes.
```ts
public onDimensionChange(dimension: SciDimension): void {
console.log(dimension);
}
```

The directive can be configured with `emitOutsideAngular` set to `true` to emit outside the Angular zone. Defaults to `false`.

</details>

<details>
<summary><strong>Control if to emit outside of the Angular zone</strong></summary>

You can control if to emit a dimension change inside or outside of the Angular zone by passing a `boolean` value to the input parameter `emitOutsideAngular`. If emitting outside of the Angular zone, the directive does not trigger an Angular change detection cycle. By default, dimension changes are emitted inside of the Angular zone.

```html
<div sciDimension (sciDimensionChange)="onDimensionChange($event)" [emitOutsideAngular]="false"></div>
```
<summary><strong>Dimension Signal</strong></summary>

Creates a signal to observe the size of an element.

```ts
import {fromDimension} from '@scion/components/dimension';

const element: HTMLElement = ...;
const dimension = fromDimension(element);
```

The signal subscribes to the native [`ResizeObserver`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) to monitor element size changes. The subscription is automatically disposed of when the current context is destroyed.

**Notes:**
- The function must be called within an injection context or with a `DestroyRef` passed for automatic unsubscription.
- The function must NOT be called within a reactive context to avoid creating a new subscription each time it is called.

</details>

[menu-home]: /README.md
Expand Down
4 changes: 2 additions & 2 deletions projects/scion/components/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The SCION Components is a collection of Angular components and directives primar
Provides a viewport component with scrollbars that sit on top of the viewport client.

- [**Dimension**][link-tool-dimension]\
Provides a directive for observing changes in the size of the host element.
Provides primitives for observing size changes of an element.

- [**Sashbox**][link-tool-sashbox]\
Provides a sashbox component for splitting content into multiple parts, which the user can resize by moving the splitter between the parts.
Expand Down Expand Up @@ -37,4 +37,4 @@ License: EPL-2.0
[link-tool-dimension]: https://github.com/SchweizerischeBundesbahnen/scion-toolkit/blob/master/docs/site/tools/dimension.md
[link-tool-sashbox]: https://github.com/SchweizerischeBundesbahnen/scion-toolkit/blob/master/docs/site/tools/sashbox.md
[link-tool-splitter]: https://github.com/SchweizerischeBundesbahnen/scion-toolkit/blob/master/docs/site/tools/splitter.md
[link-tool-throbber]: https://github.com/SchweizerischeBundesbahnen/scion-toolkit/blob/master/docs/site/tools/throbber.md
[link-tool-throbber]: https://github.com/SchweizerischeBundesbahnen/scion-toolkit/blob/master/docs/site/tools/throbber.md
20 changes: 4 additions & 16 deletions projects/scion/components/dimension/src/dimension.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ import {fromResize$} from '@scion/toolkit/observable';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {observeInside, subscribeInside} from '@scion/toolkit/operators';
import {animationFrameScheduler, observeOn, subscribeOn} from 'rxjs';
import {SciDimension} from './dimension';

/**
* Observes changes to the size of the host element.
*
* Upon subscription, emits the current size, and then continuously when the size changes. The Observable never completes.
* Observes the size of the host element.
*
* ---
* Usage:
Expand All @@ -33,12 +32,12 @@ import {animationFrameScheduler, observeOn, subscribeOn} from 'rxjs';
export class SciDimensionDirective {

/**
* Controls if to emit outside the Angular zone. Defaults to `false`.
* Controls if to output outside the Angular zone. Defaults to `false`.
*/
public emitOutsideAngular = input(false);

/**
* Upon subscription, emits the current size, and then continuously when the size changes. The Observable never completes.
* Outputs the size of the element.
*/
public dimensionChange = output<SciDimension>({alias: 'sciDimensionChange'});

Expand All @@ -65,14 +64,3 @@ export class SciDimensionDirective {
});
}
}

/**
* Represents the dimension of an element.
*/
export interface SciDimension {
offsetWidth: number;
offsetHeight: number;
clientWidth: number;
clientHeight: number;
element: HTMLElement;
}
50 changes: 50 additions & 0 deletions projects/scion/components/dimension/src/dimension.signal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2018-2024 Swiss Federal Railways
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/

import {assertInInjectionContext, assertNotInReactiveContext, DestroyRef, ElementRef, inject, signal, Signal} from '@angular/core';
import {coerceElement} from '@angular/cdk/coercion';
import {SciDimension} from './dimension';

/**
* Creates a signal to observe the size of an element.
*
* The signal subscribes to the native {@link ResizeObserver} to monitor element size changes.
* The subscription is automatically disposed of when the current context is destroyed.
*
* The function:
* - must be called within an injection context or with a `DestroyRef` passed for automatic unsubscription.
* - must NOT be called within a reactive context to avoid repeated subscriptions to the native {@link ResizeObserver}.
*/
export function fromDimension(elementRef: HTMLElement | ElementRef<HTMLElement>, options?: {destroyRef?: DestroyRef}): Signal<SciDimension> {
assertNotInReactiveContext(fromDimension, 'Invoking `fromDimension` causes new subscriptions every time. Move `fromDimension` outside of the reactive context and read the signal value where needed.');
if (!options?.destroyRef) {
assertInInjectionContext(fromDimension);
}

const element = coerceElement(elementRef);
const dimension = signal(getDimension(element));
// Update signal in animation frame to not block the resize callback (ResizeObserver loop completed with undelivered notifications).
const resizeObserver = new ResizeObserver(() => requestAnimationFrame(() => dimension.set(getDimension(element))));
resizeObserver.observe(element);

const destroyRef = options?.destroyRef ?? inject(DestroyRef);
destroyRef.onDestroy(() => resizeObserver.disconnect());
return dimension;
}

function getDimension(element: HTMLElement): SciDimension {
return {
clientWidth: element.clientWidth,
offsetWidth: element.offsetWidth,
clientHeight: element.clientHeight,
offsetHeight: element.offsetHeight,
element: element,
};
}
Loading

0 comments on commit a160a1e

Please sign in to comment.