Skip to content

Commit

Permalink
feat(rx): add creation functions for intersection-, resize-, and muta…
Browse files Browse the repository at this point in the history
…tion observer
  • Loading branch information
michaelbe812 committed Jan 15, 2023
1 parent 77b2d88 commit a71a0ff
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 0 deletions.
3 changes: 3 additions & 0 deletions libs/rx/platform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @code-workers.io/angular-kit/rx/platform

A set of reactive helpers wrapping browser APIs.
5 changes: 5 additions & 0 deletions libs/rx/platform/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"lib": {
"entryFile": "src/index.ts"
}
}
6 changes: 6 additions & 0 deletions libs/rx/platform/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './lib/create-resize-observer';
export * from './lib/create-intersection-observer';
export * from './lib/create-mutation-observer';

export * from './lib/directives/observe-resize.directive';
export * from './lib/directives/observe-intersection.directive';
26 changes: 26 additions & 0 deletions libs/rx/platform/src/lib/create-intersection-observer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {createElementRef, mockIntersectionObserver} from '@angular-kit/testing';
import {createIntersectionObserver} from './create-intersection-observer';
import {subscribeSpyTo} from '@hirez_io/observer-spy';
import {fakeAsync, tick} from '@angular/core/testing';

describe('createIntersectionObserver', () => {
describe('supported', () => {
beforeEach(() => mockIntersectionObserver());
it('should create', () => {
const observer = createIntersectionObserver(createElementRef());
expect(observer).toBeTruthy();
});

it('should emit on intersect', fakeAsync(() => {
const elementRef = createElementRef();
const observer = createIntersectionObserver(elementRef);

const result = subscribeSpyTo(observer);
elementRef.nativeElement.dispatchEvent(new Event('intersect'));

tick(1000);

expect(result.getValues().length).toEqual(1);
}));
});
});
40 changes: 40 additions & 0 deletions libs/rx/platform/src/lib/create-intersection-observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {debounceTime, Observable, ReplaySubject, SchedulerLike, share} from 'rxjs';
import {ElementRef} from '@angular/core';

const DEFAULT_THROTTLE_TIME = 125;

export function supportsIntersectionObserver() {
return typeof window.IntersectionObserver !== 'undefined';
}

export function createIntersectionObserver(
observeElement: ElementRef,
options?: IntersectionObserverInit,
cfg?: {
throttleMs?: number;
scheduler?: SchedulerLike;
}
): Observable<IntersectionObserverEntry[]> {
if (!supportsIntersectionObserver()) {
throw new Error('[AngularKit] IntersectionObserver is not supported in this browser');
}
const obs$ = new Observable<IntersectionObserverEntry[]>((subscriber) => {
const intersectionObserver = new IntersectionObserver((entries) => {
subscriber.next(entries);
}, options ?? {});

intersectionObserver.observe(observeElement.nativeElement);

return () => intersectionObserver.disconnect();
});

return obs$.pipe(
cfg?.throttleMs ? debounceTime(cfg?.throttleMs, cfg?.scheduler) : debounceTime(DEFAULT_THROTTLE_TIME),
share({
connector: () => new ReplaySubject(1),
resetOnComplete: false,
resetOnError: false,
resetOnRefCountZero: false,
})
);
}
26 changes: 26 additions & 0 deletions libs/rx/platform/src/lib/create-mutation-observer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {createMutationObserver} from './create-mutation-observer';
import {subscribeSpyTo} from '@hirez_io/observer-spy';
import {createElementRef, mockMutationObserver} from '@angular-kit/testing';
import {fakeAsync, tick} from '@angular/core/testing';

describe('createMutationObserver', () => {
describe('supported', () => {
beforeEach(() => mockMutationObserver());
it('should create', () => {
const observer = createMutationObserver(createElementRef());
expect(observer).toBeTruthy();
});

it('should emit on resize', fakeAsync(() => {
const elementRef = createElementRef();
const observer = createMutationObserver(elementRef);

const result = subscribeSpyTo(observer);
elementRef.nativeElement.dispatchEvent(new Event('mutate'));

tick(1000);

expect(result.getValues().length).toEqual(1);
}));
});
});
40 changes: 40 additions & 0 deletions libs/rx/platform/src/lib/create-mutation-observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {debounceTime, Observable, ReplaySubject, SchedulerLike, share} from 'rxjs';
import {ElementRef} from '@angular/core';

const DEFAULT_THROTTLE_TIME = 125;

export function supportsMutationObserver() {
return typeof window.MutationObserver !== 'undefined';
}

export function createMutationObserver(
observeElement: ElementRef,
options?: MutationObserverInit,
cfg?: {
throttleMs?: number;
scheduler?: SchedulerLike;
}
): Observable<MutationRecord[]> {
if (!supportsMutationObserver()) {
throw new Error('[AngularKit] MutationObserver is not supported in this browser');
}
const obs$ = new Observable<MutationRecord[]>((subscriber) => {
const mutationObserver = new MutationObserver((entries) => {
subscriber.next(entries);
});

mutationObserver.observe(observeElement.nativeElement, options ?? {});

return () => mutationObserver.disconnect();
});

return obs$.pipe(
cfg?.throttleMs ? debounceTime(cfg?.throttleMs, cfg?.scheduler) : debounceTime(DEFAULT_THROTTLE_TIME),
share({
connector: () => new ReplaySubject(1),
resetOnComplete: false,
resetOnError: false,
resetOnRefCountZero: false,
})
);
}
26 changes: 26 additions & 0 deletions libs/rx/platform/src/lib/create-resize-observer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {createResizeObserver} from './create-resize-observer';
import {fakeAsync, tick} from '@angular/core/testing';
import {subscribeSpyTo} from '@hirez_io/observer-spy';
import {createElementRef, mockResizeObserver} from '@angular-kit/testing';

describe('createResizeObserver', () => {
describe('supported', () => {
beforeEach(() => mockResizeObserver());
it('should create', () => {
const observer = createResizeObserver(createElementRef());
expect(observer).toBeTruthy();
});

it('should emit on resize', fakeAsync(() => {
const elementRef = createElementRef();
const observer = createResizeObserver(elementRef);

const result = subscribeSpyTo(observer);
elementRef.nativeElement.dispatchEvent(new Event('resize'));

tick(1000);

expect(result.getValues().length).toEqual(1);
}));
});
});
42 changes: 42 additions & 0 deletions libs/rx/platform/src/lib/create-resize-observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {ElementRef} from '@angular/core';
import {debounceTime, distinctUntilChanged, Observable, ReplaySubject, SchedulerLike, share} from 'rxjs';

const DEFAULT_THROTTLE_TIME = 50;

export function supportsResizeObserver() {
return typeof window.ResizeObserver !== 'undefined';
}

export type ResizeObserverConfig = {
throttleMs?: number;
scheduler?: SchedulerLike;
};

export function createResizeObserver(
observeElement: ElementRef,
cfg?: ResizeObserverConfig
): Observable<ResizeObserverEntry[]> {
if (!supportsResizeObserver()) {
throw new Error('[AngularKit] ResizeObserver is not supported in this browser');
}
const obs$ = new Observable<ResizeObserverEntry[]>((subscriber) => {
const resizeObserver = new ResizeObserver((entries) => {
subscriber.next(entries);
});

resizeObserver.observe(observeElement.nativeElement);

return () => resizeObserver.disconnect();
});

return obs$.pipe(
distinctUntilChanged(),
cfg?.throttleMs ? debounceTime(cfg?.throttleMs, cfg?.scheduler) : debounceTime(DEFAULT_THROTTLE_TIME),
share({
connector: () => new ReplaySubject(1),
resetOnComplete: false,
resetOnError: false,
resetOnRefCountZero: false,
})
);
}

0 comments on commit a71a0ff

Please sign in to comment.