-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(when-document-visible): create whenDocumentVisible rxjs operator * test(when-document-visible): add test for the whenDocumentVisible operator * feat(poll): create poll rxjs operator * docs(poll): add documentation * docs(when-document-visible): add documentation * docs(poll): update doc * docs: update documenation index * update linkedin link * apply formatting to poll operator * test(when-document-visible): fix tests --------- Co-authored-by: Chau Tran <nartc7789@gmail.com>
- Loading branch information
1 parent
8e7a98a
commit 7ffc766
Showing
18 changed files
with
337 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"name": "Fabien Dehopré", | ||
"twitter": "https://twitter.com/FabienDehopre", | ||
"github": "https://github.com/FabienDehopre", | ||
"linkedin": "https://www.linkedin.com/in/fabien1979/" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
--- | ||
title: poll | ||
description: RxJS operator to apply to a stream that you want to poll every "period" milliseconds after an optional "initialDelay" milliseconds. | ||
entryPoint: poll | ||
badge: stable | ||
contributors: ['fabiendehopre'] | ||
--- | ||
|
||
## Import | ||
|
||
```typescript | ||
import { poll } from 'ngxtension/poll'; | ||
``` | ||
|
||
## Usage | ||
|
||
You can use this operator to poll and API at a fixed interval with an optional initial delay. | ||
|
||
```typescript | ||
import { HttpClient } from '@angular/common/http'; | ||
import { inject } from '@angular/core'; | ||
import { poll } from 'ngxtension/poll'; | ||
|
||
const httpClient = inject(HttpClient); | ||
httpClient | ||
.get('https://api.example.com/data') | ||
.pipe( | ||
poll(10000, 5000), // poll every 10s after 5s | ||
) | ||
.subscribe(console.log); | ||
``` |
39 changes: 39 additions & 0 deletions
39
docs/src/content/docs/utilities/Operators/when-document-visible.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
--- | ||
title: whenDocumentVisible | ||
description: RxJS operator to pause a stream when the document is hidden and to resume the stream when the document is visible. | ||
entryPoint: when-document-visible | ||
badge: stable | ||
contributors: ['fabiendehopre'] | ||
--- | ||
|
||
## Import | ||
|
||
```typescript | ||
import { whenDocumentVisible } from 'ngxtension/when-document-visible'; | ||
``` | ||
|
||
## Usage | ||
|
||
You can use it to pause a stream when the document is hidden and to resume the stream when the document is visible. | ||
|
||
It uses the same options as the `injectDocumentVisiblity` function. | ||
|
||
A good use case is to pause an API polling when the user switches to another tab or another application. | ||
|
||
```typescript | ||
import { DOCUMENT } from '@angular/common'; | ||
import { HttpClient } from '@angular/common/http'; | ||
import { inject } from '@angular/core'; | ||
import { poll } from 'ngxtension/poll'; | ||
import { whenDocumentVisible } from 'ngxtension/when-document-visible'; | ||
|
||
const httpClient = inject(HttpClient); | ||
const document = inject(DOCUMENT); | ||
httpClient | ||
.get('https://api.example.com/data') | ||
.pipe( | ||
poll(10000, 5000), // poll every 10s after 5s | ||
whenDocumentVisible({ document }), | ||
) | ||
.subscribe(console.log); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# ngxtension/poll | ||
|
||
Secondary entry point of `ngxtension`. It can be used by importing from `ngxtension/poll`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"lib": { | ||
"entryFile": "src/index.ts" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"name": "ngxtension/poll", | ||
"$schema": "../../../node_modules/nx/schemas/project-schema.json", | ||
"projectType": "library", | ||
"sourceRoot": "libs/ngxtension/poll/src", | ||
"targets": { | ||
"test": { | ||
"executor": "@nx/jest:jest", | ||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"], | ||
"options": { | ||
"jestConfig": "libs/ngxtension/jest.config.ts", | ||
"testPathPattern": ["poll"], | ||
"passWithNoTests": true | ||
}, | ||
"configurations": { | ||
"ci": { | ||
"ci": true, | ||
"codeCoverage": true | ||
} | ||
} | ||
}, | ||
"lint": { | ||
"executor": "@nx/eslint:lint", | ||
"outputs": ["{options.outputFile}"] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { poll } from './poll'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { fakeAsync, tick } from '@angular/core/testing'; | ||
import { of } from 'rxjs'; | ||
import { poll } from './poll'; | ||
|
||
describe(poll.name, () => { | ||
it('should return an observable that polls every 1000ms', fakeAsync(() => { | ||
const callback = jest.fn(); | ||
const source = of('test'); | ||
const result = source.pipe(poll(1000)); | ||
const sub = result.subscribe(() => { | ||
callback(); | ||
}); | ||
expect(callback).toHaveBeenCalledTimes(0); | ||
tick(1000); | ||
expect(callback).toHaveBeenCalledTimes(2); | ||
tick(1000); | ||
expect(callback).toHaveBeenCalledTimes(3); | ||
sub.unsubscribe(); | ||
tick(1000); | ||
expect(callback).toHaveBeenCalledTimes(3); | ||
})); | ||
|
||
it('should return an observable that polls every 1000ms after 500ms', fakeAsync(() => { | ||
const callback = jest.fn(); | ||
const source = of('test'); | ||
const result = source.pipe(poll(1000, 500)); | ||
const sub = result.subscribe(() => { | ||
callback(); | ||
}); | ||
expect(callback).toHaveBeenCalledTimes(0); | ||
tick(500); | ||
expect(callback).toHaveBeenCalledTimes(1); | ||
tick(1000); | ||
expect(callback).toHaveBeenCalledTimes(2); | ||
tick(1000); | ||
expect(callback).toHaveBeenCalledTimes(3); | ||
sub.unsubscribe(); | ||
tick(1000); | ||
expect(callback).toHaveBeenCalledTimes(3); | ||
})); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { concatMap, MonoTypeOperatorFunction, Observable, timer } from 'rxjs'; | ||
// source: https://netbasal.com/use-rxjs-to-modify-app-behavior-based-on-page-visibility-ce499c522be4 | ||
|
||
/** | ||
* RxJS operator to apply to a stream that you want to poll every "period" milliseconds after an optional "initialDelay" milliseconds. | ||
* | ||
* @param period - Indicates the delay between 2 polls. | ||
* @param initialDelay - Indicates the delay before the first poll occurs. | ||
* @example This example shows how to do an HTTP GET every 5 seconds: | ||
* ```ts | ||
* http.get('http://api').pipe(poll(5000)).subscribe(result => {}); | ||
* ``` | ||
* @public | ||
*/ | ||
export function poll<T>( | ||
period: number, | ||
initialDelay = 0, | ||
): MonoTypeOperatorFunction<T> { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument | ||
return (source: Observable<T>) => { | ||
return timer(initialDelay, period).pipe(concatMap(() => source)); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# ngxtension/when-document-visible | ||
|
||
Secondary entry point of `ngxtension`. It can be used by importing from `ngxtension/when-document-visible`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"lib": { | ||
"entryFile": "src/index.ts" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"name": "ngxtension/when-document-visible", | ||
"$schema": "../../../node_modules/nx/schemas/project-schema.json", | ||
"projectType": "library", | ||
"sourceRoot": "libs/ngxtension/when-document-visible/src", | ||
"targets": { | ||
"test": { | ||
"executor": "@nx/jest:jest", | ||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"], | ||
"options": { | ||
"jestConfig": "libs/ngxtension/jest.config.ts", | ||
"testPathPattern": ["when-document-visible"], | ||
"passWithNoTests": true | ||
}, | ||
"configurations": { | ||
"ci": { | ||
"ci": true, | ||
"codeCoverage": true | ||
} | ||
} | ||
}, | ||
"lint": { | ||
"executor": "@nx/eslint:lint", | ||
"outputs": ["{options.outputFile}"] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const greeting = 'Hello World!'; |
61 changes: 61 additions & 0 deletions
61
libs/ngxtension/when-document-visible/src/when-document-visible.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { Injector } from '@angular/core'; | ||
import { fakeAsync, TestBed, tick } from '@angular/core/testing'; | ||
import { Observable, timer } from 'rxjs'; | ||
import { whenDocumentVisible } from './when-document-visible'; | ||
|
||
describe(whenDocumentVisible.name, () => { | ||
function triggerVisibilityChange(newState: DocumentVisibilityState) { | ||
// Change the visibility state | ||
Object.defineProperty(document, 'visibilityState', { | ||
writable: true, | ||
configurable: true, | ||
value: newState, | ||
}); | ||
|
||
// Dispatch the event | ||
const event = new Event('visibilitychange'); | ||
document.dispatchEvent(event); | ||
} | ||
|
||
describe('should emit values only when document is visible', () => { | ||
const runTest = (source: Observable<number>): void => { | ||
const callback = jest.fn(); | ||
triggerVisibilityChange('visible'); | ||
const sub = source.subscribe(callback); | ||
tick(); // trigger initial emission | ||
expect(callback).toHaveBeenCalledTimes(1); | ||
tick(10_000); // will emit 10 times in 10 seconds | ||
expect(callback).toHaveBeenCalledTimes(11); | ||
triggerVisibilityChange('hidden'); | ||
tick(10_000); | ||
expect(callback).toHaveBeenCalledTimes(11); | ||
triggerVisibilityChange('visible'); | ||
tick(10_000); // will emit 11 times (once initially and 10 times in 10 seconds) | ||
expect(callback).toHaveBeenCalledTimes(22); | ||
sub.unsubscribe(); | ||
}; | ||
|
||
it('should emit values only when document is visible using provided injector', fakeAsync(() => { | ||
const source = timer(0, 1000); | ||
runTest( | ||
source.pipe( | ||
whenDocumentVisible({ injector: TestBed.inject(Injector) }), | ||
), | ||
); | ||
})); | ||
|
||
it('should emit values only when document is visible in injector context', fakeAsync(() => { | ||
TestBed.runInInjectionContext(() => { | ||
const source = timer(0, 1000); | ||
runTest(source.pipe(whenDocumentVisible())); | ||
}); | ||
})); | ||
|
||
it('should emit values only when document is visible using provided document', fakeAsync(() => { | ||
TestBed.runInInjectionContext(() => { | ||
const source = timer(0, 1000); | ||
runTest(source.pipe(whenDocumentVisible({ document }))); | ||
}); | ||
})); | ||
}); | ||
}); |
36 changes: 36 additions & 0 deletions
36
libs/ngxtension/when-document-visible/src/when-document-visible.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { | ||
InjectDocumentVisibilityOptions, | ||
injectDocumentVisibilityStream, | ||
} from 'ngxtension/inject-document-visibility'; | ||
import { | ||
MonoTypeOperatorFunction, | ||
Observable, | ||
partition, | ||
repeat, | ||
takeUntil, | ||
} from 'rxjs'; | ||
|
||
/** | ||
* RxJS operator to pause a stream when the document is hidden (i.e.: tab is not active) | ||
* and to resume the stream when the document is visible (i.e.: tab is active). | ||
* | ||
* @example This example shows how to do an HTTP GET every 5 seconds only when the page is visible in the browser: | ||
* ```typescript | ||
* http.get('http://api').pipe(poll(5000), whenPageVisible()).subscribe(result => {}) | ||
* ``` | ||
* @public | ||
*/ | ||
export function whenDocumentVisible<T>( | ||
options?: InjectDocumentVisibilityOptions, | ||
): MonoTypeOperatorFunction<T> { | ||
const visibilityChanged$ = injectDocumentVisibilityStream(options); | ||
const [pageVisible$, pageHidden$] = partition(visibilityChanged$, () => { | ||
return document.visibilityState === 'visible'; | ||
}); | ||
return (source: Observable<T>) => { | ||
return source.pipe( | ||
takeUntil(pageHidden$), | ||
repeat({ delay: () => pageVisible$ }), | ||
); | ||
}; | ||
} |
Oops, something went wrong.