diff --git a/docs/en/UI/Angular/Internet-Connection-Service.md b/docs/en/UI/Angular/Internet-Connection-Service.md new file mode 100644 index 0000000000..d9002b660e --- /dev/null +++ b/docs/en/UI/Angular/Internet-Connection-Service.md @@ -0,0 +1,30 @@ +# Internet Connection Service +`InternetConnectionService` is a service which is exposed by the `@abp/ng.core` package. **You can use this service in order to check your internet connection** + +## Getting Started +When you inject the InternetConnectionService you can get the current internet status, and it gets immediately updated if the status changes. + +`InternetConnectionService` provides two choices to catch the network status: +1. Signal (readonly) +2. Observable + + +# How To Use +İt's easy, just inject the service and get the network status. + +**You can get via signal** +```ts +class SomeComponent{ + internetConnectionService = inject(InternetConnectionService); + isOnline = this.internetConnectionService.networkStatus +} +``` +**or you can get the observable** +```ts +class SomeComponent{ + internetConnectionService = inject(InternetConnectionService); + isOnline = this.internetConnectionService.networkStatus$ +} +``` + +To see how we implement to the template, check the `InternetConnectionStatusComponent` diff --git a/npm/ng-packs/packages/core/src/lib/services/internet-connection-service.ts b/npm/ng-packs/packages/core/src/lib/services/internet-connection-service.ts index a38f429d64..d74e65d036 100644 --- a/npm/ng-packs/packages/core/src/lib/services/internet-connection-service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/internet-connection-service.ts @@ -1,18 +1,32 @@ import { DOCUMENT } from '@angular/common'; -import { Injectable, inject } from '@angular/core'; -import { fromEvent, merge, of } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { Injectable, computed, inject, signal } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root', }) export class InternetConnectionService{ - protected readonly window = inject(DOCUMENT).defaultView; - protected readonly navigator = this.window.navigator; + readonly document = inject(DOCUMENT) + readonly window = this.document.defaultView; + readonly navigator = this.window.navigator; - networkStatus$ = merge( - of(navigator.onLine), - fromEvent(window, 'offline'), - fromEvent(window, 'online') - ).pipe(map(() => navigator.onLine)) + private status$ = new BehaviorSubject(this.navigator.onLine) + + private status = signal(this.navigator.onLine); + + networkStatus = computed(() => this.status()) + + constructor(){ + this.window.addEventListener('offline', () => this.setStatus(false)); + this.window.addEventListener('online', () => this.setStatus(true)); + } + + setStatus(val:boolean){ + this.status.set(val) + this.status$.next(val) + } + + get networkStatus$(){ + return this.status$.asObservable() + } } diff --git a/npm/ng-packs/packages/core/src/lib/tests/internet-connection.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/internet-connection.service.spec.ts new file mode 100644 index 0000000000..b4809471eb --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/internet-connection.service.spec.ts @@ -0,0 +1,100 @@ +import { TestBed} from '@angular/core/testing'; +import { DOCUMENT } from '@angular/common'; + +import { InternetConnectionService } from '../services/internet-connection-service'; +import { first } from 'rxjs'; + +let service: InternetConnectionService; + +describe('Internet connection when disconnected', () => { + const events = {}; + const addEventListener = jest.fn((event, callback) => { + events[event] = callback; + }); + const mockDocument = { defaultView: {navigator: {onLine: false}, addEventListener } } + beforeAll(() => { + TestBed.configureTestingModule({ + providers:[{provide:DOCUMENT, useValue: mockDocument}] + }) + service = TestBed.inject(InternetConnectionService); + }); + + it('document should be created', () => { + expect(service.document).toEqual(mockDocument); + }); + + it('signal value should be false', () => { + expect(service.networkStatus()).toEqual(false); + }); + + it('observable value should be false', + (done: any) => { + service.networkStatus$.pipe(first()).subscribe(value => { + expect(value).toBe(false) + done(); + }); + }); + + test.each(['offline','online'])('should addEventListener for %p, event',(v)=>{ + expect(events[v]).toBeTruthy() + }) + + test.each([['offline',false],["online",true]])('when %p called ,then signal value must be %p',(eventName,value)=>{ + events[eventName]() + expect(service.networkStatus()).toEqual(value); + }) + + test.each([['offline',false],["online",true]])('when %p called,then observable must return %p',(eventName,value)=>{ + events[eventName]() + service.networkStatus$.subscribe(val=>{ + expect(val).toEqual(value) + }) + }) +}); + +describe('when connection value changes for signals', () => { + const events = {}; + const addEventListener = jest.fn((event, callback) => { + events[event] = callback; + }); + const mockDocument = { defaultView: {navigator: {onLine: false}, addEventListener } } + beforeAll(() => { + TestBed.configureTestingModule({ + providers:[{provide:DOCUMENT, useValue: mockDocument}] + }) + service = TestBed.inject(InternetConnectionService); + }); + + it('signal value must be false when offline event is called while internet is connected', () => { + events['online']() + expect(service.networkStatus()).toEqual(true); + events['offline']() + expect(service.networkStatus()).toEqual(false); + }); + + it('signal value must be true when online event is called while internet is disconnected', () => { + events['offline']() + expect(service.networkStatus()).toEqual(false); + events['online']() + expect(service.networkStatus()).toEqual(true); + }); + + it('observable value must be false when offline event is called while internet is connected', (done:any) => { + events['online']() + events['offline']() + service.networkStatus$.subscribe(val=>{ + expect(val).toEqual(false) + done() + }) + }); + + it('observable value must be true when online event is called while internet is disconnected', (done:any) => { + events['offline']() + events['online']() + service.networkStatus$.subscribe(val=>{ + console.log(val); + expect(val).toEqual(true) + done() + }) + }); +}); \ No newline at end of file diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/internet-connection-status/internet-connection-status.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/internet-connection-status/internet-connection-status.component.ts index c944e67ac1..e649555f40 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/internet-connection-status/internet-connection-status.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/internet-connection-status/internet-connection-status.component.ts @@ -1,14 +1,13 @@ -import { Component, inject } from '@angular/core'; -import { CommonModule } from '@angular/common' -import { map } from 'rxjs'; -import { InternetConnectionService, LocalizationModule } from '@abp/ng.core'; +import { Component, computed, inject } from '@angular/core'; +import { NgIf } from '@angular/common' +import { InternetConnectionService , LocalizationModule } from '@abp/ng.core'; @Component({ selector: 'abp-internet-status', standalone: true, - imports:[CommonModule,LocalizationModule], + imports:[NgIf, LocalizationModule], template: ` -
+
@@ -42,7 +41,7 @@ import { InternetConnectionService, LocalizationModule } from '@abp/ng.core'; } `] }) -export class InternetConnectionStatusComponent{ - internetConnectionService = inject(InternetConnectionService) - isOffline$ = this.internetConnectionService.networkStatus$.pipe(map((val) => !val)); +export class InternetConnectionStatusComponent{ + internetConnectionService = inject(InternetConnectionService); + isOnline = this.internetConnectionService.networkStatus }