Skip to content

Commit a71a0ff

Browse files
committed
feat(rx): add creation functions for intersection-, resize-, and mutation observer
1 parent 77b2d88 commit a71a0ff

9 files changed

+214
-0
lines changed

libs/rx/platform/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# @code-workers.io/angular-kit/rx/platform
2+
3+
A set of reactive helpers wrapping browser APIs.

libs/rx/platform/ng-package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"lib": {
3+
"entryFile": "src/index.ts"
4+
}
5+
}

libs/rx/platform/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export * from './lib/create-resize-observer';
2+
export * from './lib/create-intersection-observer';
3+
export * from './lib/create-mutation-observer';
4+
5+
export * from './lib/directives/observe-resize.directive';
6+
export * from './lib/directives/observe-intersection.directive';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {createElementRef, mockIntersectionObserver} from '@angular-kit/testing';
2+
import {createIntersectionObserver} from './create-intersection-observer';
3+
import {subscribeSpyTo} from '@hirez_io/observer-spy';
4+
import {fakeAsync, tick} from '@angular/core/testing';
5+
6+
describe('createIntersectionObserver', () => {
7+
describe('supported', () => {
8+
beforeEach(() => mockIntersectionObserver());
9+
it('should create', () => {
10+
const observer = createIntersectionObserver(createElementRef());
11+
expect(observer).toBeTruthy();
12+
});
13+
14+
it('should emit on intersect', fakeAsync(() => {
15+
const elementRef = createElementRef();
16+
const observer = createIntersectionObserver(elementRef);
17+
18+
const result = subscribeSpyTo(observer);
19+
elementRef.nativeElement.dispatchEvent(new Event('intersect'));
20+
21+
tick(1000);
22+
23+
expect(result.getValues().length).toEqual(1);
24+
}));
25+
});
26+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {debounceTime, Observable, ReplaySubject, SchedulerLike, share} from 'rxjs';
2+
import {ElementRef} from '@angular/core';
3+
4+
const DEFAULT_THROTTLE_TIME = 125;
5+
6+
export function supportsIntersectionObserver() {
7+
return typeof window.IntersectionObserver !== 'undefined';
8+
}
9+
10+
export function createIntersectionObserver(
11+
observeElement: ElementRef,
12+
options?: IntersectionObserverInit,
13+
cfg?: {
14+
throttleMs?: number;
15+
scheduler?: SchedulerLike;
16+
}
17+
): Observable<IntersectionObserverEntry[]> {
18+
if (!supportsIntersectionObserver()) {
19+
throw new Error('[AngularKit] IntersectionObserver is not supported in this browser');
20+
}
21+
const obs$ = new Observable<IntersectionObserverEntry[]>((subscriber) => {
22+
const intersectionObserver = new IntersectionObserver((entries) => {
23+
subscriber.next(entries);
24+
}, options ?? {});
25+
26+
intersectionObserver.observe(observeElement.nativeElement);
27+
28+
return () => intersectionObserver.disconnect();
29+
});
30+
31+
return obs$.pipe(
32+
cfg?.throttleMs ? debounceTime(cfg?.throttleMs, cfg?.scheduler) : debounceTime(DEFAULT_THROTTLE_TIME),
33+
share({
34+
connector: () => new ReplaySubject(1),
35+
resetOnComplete: false,
36+
resetOnError: false,
37+
resetOnRefCountZero: false,
38+
})
39+
);
40+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {createMutationObserver} from './create-mutation-observer';
2+
import {subscribeSpyTo} from '@hirez_io/observer-spy';
3+
import {createElementRef, mockMutationObserver} from '@angular-kit/testing';
4+
import {fakeAsync, tick} from '@angular/core/testing';
5+
6+
describe('createMutationObserver', () => {
7+
describe('supported', () => {
8+
beforeEach(() => mockMutationObserver());
9+
it('should create', () => {
10+
const observer = createMutationObserver(createElementRef());
11+
expect(observer).toBeTruthy();
12+
});
13+
14+
it('should emit on resize', fakeAsync(() => {
15+
const elementRef = createElementRef();
16+
const observer = createMutationObserver(elementRef);
17+
18+
const result = subscribeSpyTo(observer);
19+
elementRef.nativeElement.dispatchEvent(new Event('mutate'));
20+
21+
tick(1000);
22+
23+
expect(result.getValues().length).toEqual(1);
24+
}));
25+
});
26+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {debounceTime, Observable, ReplaySubject, SchedulerLike, share} from 'rxjs';
2+
import {ElementRef} from '@angular/core';
3+
4+
const DEFAULT_THROTTLE_TIME = 125;
5+
6+
export function supportsMutationObserver() {
7+
return typeof window.MutationObserver !== 'undefined';
8+
}
9+
10+
export function createMutationObserver(
11+
observeElement: ElementRef,
12+
options?: MutationObserverInit,
13+
cfg?: {
14+
throttleMs?: number;
15+
scheduler?: SchedulerLike;
16+
}
17+
): Observable<MutationRecord[]> {
18+
if (!supportsMutationObserver()) {
19+
throw new Error('[AngularKit] MutationObserver is not supported in this browser');
20+
}
21+
const obs$ = new Observable<MutationRecord[]>((subscriber) => {
22+
const mutationObserver = new MutationObserver((entries) => {
23+
subscriber.next(entries);
24+
});
25+
26+
mutationObserver.observe(observeElement.nativeElement, options ?? {});
27+
28+
return () => mutationObserver.disconnect();
29+
});
30+
31+
return obs$.pipe(
32+
cfg?.throttleMs ? debounceTime(cfg?.throttleMs, cfg?.scheduler) : debounceTime(DEFAULT_THROTTLE_TIME),
33+
share({
34+
connector: () => new ReplaySubject(1),
35+
resetOnComplete: false,
36+
resetOnError: false,
37+
resetOnRefCountZero: false,
38+
})
39+
);
40+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {createResizeObserver} from './create-resize-observer';
2+
import {fakeAsync, tick} from '@angular/core/testing';
3+
import {subscribeSpyTo} from '@hirez_io/observer-spy';
4+
import {createElementRef, mockResizeObserver} from '@angular-kit/testing';
5+
6+
describe('createResizeObserver', () => {
7+
describe('supported', () => {
8+
beforeEach(() => mockResizeObserver());
9+
it('should create', () => {
10+
const observer = createResizeObserver(createElementRef());
11+
expect(observer).toBeTruthy();
12+
});
13+
14+
it('should emit on resize', fakeAsync(() => {
15+
const elementRef = createElementRef();
16+
const observer = createResizeObserver(elementRef);
17+
18+
const result = subscribeSpyTo(observer);
19+
elementRef.nativeElement.dispatchEvent(new Event('resize'));
20+
21+
tick(1000);
22+
23+
expect(result.getValues().length).toEqual(1);
24+
}));
25+
});
26+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {ElementRef} from '@angular/core';
2+
import {debounceTime, distinctUntilChanged, Observable, ReplaySubject, SchedulerLike, share} from 'rxjs';
3+
4+
const DEFAULT_THROTTLE_TIME = 50;
5+
6+
export function supportsResizeObserver() {
7+
return typeof window.ResizeObserver !== 'undefined';
8+
}
9+
10+
export type ResizeObserverConfig = {
11+
throttleMs?: number;
12+
scheduler?: SchedulerLike;
13+
};
14+
15+
export function createResizeObserver(
16+
observeElement: ElementRef,
17+
cfg?: ResizeObserverConfig
18+
): Observable<ResizeObserverEntry[]> {
19+
if (!supportsResizeObserver()) {
20+
throw new Error('[AngularKit] ResizeObserver is not supported in this browser');
21+
}
22+
const obs$ = new Observable<ResizeObserverEntry[]>((subscriber) => {
23+
const resizeObserver = new ResizeObserver((entries) => {
24+
subscriber.next(entries);
25+
});
26+
27+
resizeObserver.observe(observeElement.nativeElement);
28+
29+
return () => resizeObserver.disconnect();
30+
});
31+
32+
return obs$.pipe(
33+
distinctUntilChanged(),
34+
cfg?.throttleMs ? debounceTime(cfg?.throttleMs, cfg?.scheduler) : debounceTime(DEFAULT_THROTTLE_TIME),
35+
share({
36+
connector: () => new ReplaySubject(1),
37+
resetOnComplete: false,
38+
resetOnError: false,
39+
resetOnRefCountZero: false,
40+
})
41+
);
42+
}

0 commit comments

Comments
 (0)