@angular-helpers/browser-web-apis 1.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.es.md +826 -0
  2. package/README.md +918 -0
  3. package/package.json +52 -0
package/README.md ADDED
@@ -0,0 +1,918 @@
1
+ # @angular-helpers/browser-web-apis
2
+
3
+ Angular services package for a structured and secure access layer over browser Web APIs.
4
+
5
+ 🌐 **Documentation & Demo**: https://gaspar1992.github.io/angular-helpers/
6
+
7
+ [Leer en espaΓ±ol](./README.es.md)
8
+
9
+ ## Features
10
+
11
+ - Unified browser API access through strongly typed services
12
+ - **True tree-shaking** β€” individual `provideX()` functions import only their own service
13
+ - All-in-one `provideBrowserWebApis()` for quick setup when bundle size is not a concern
14
+ - Reactive APIs using signals and observables
15
+ - Lifecycle-safe integration with `DestroyRef` (automatic cleanup)
16
+ - Centralized logging and error handling via `BrowserApiBaseService`
17
+ - Secure context validation and browser support detection built in
18
+
19
+ ## Available Services
20
+
21
+ ### Media and Device APIs
22
+
23
+ - `CameraService` - Camera access with permission handling
24
+ - `MediaDevicesService` - Media device listing and management
25
+ - `GeolocationService` - Geolocation API access
26
+ - `NotificationService` - Browser notifications API
27
+ - `MediaRecorderService` - Record audio/video from MediaStream
28
+
29
+ ### Observer APIs
30
+
31
+ - `IntersectionObserverService` - Detect when elements enter/exit viewport
32
+ - `ResizeObserverService` - Watch for element size changes
33
+ - `MutationObserverService` - Watch for DOM mutations
34
+ - `PerformanceObserverService` - Monitor performance entries (LCP, CLS, etc.)
35
+
36
+ ### System APIs
37
+
38
+ - `BatteryService` - Monitor battery status and charging state
39
+ - `PageVisibilityService` - Track document visibility state changes
40
+ - `ScreenWakeLockService` - Prevent screen from dimming or locking
41
+ - `ScreenOrientationService` - Read and lock screen orientation
42
+ - `FullscreenService` - Toggle fullscreen mode for elements
43
+ - `VibrationService` - Trigger haptic feedback patterns
44
+ - `SpeechSynthesisService` - Text-to-speech with voice selection
45
+ - `IdleDetectorService` - Detect user idle state and screen lock
46
+ - `GamepadService` - Game controller input polling
47
+ - `WebAudioService` - Audio context, oscillators, and analysers
48
+ - `WebLocksService` - Cross-tab resource locking coordination
49
+ - `StorageManagerService` - Storage quotas and persistence
50
+ - `CompressionService` - Gzip/deflate compression streams
51
+
52
+ ### Network APIs
53
+
54
+ - `WebSocketService` - WebSocket connection handling
55
+ - `ServerSentEventsService` - Server-Sent Events client
56
+ - `BroadcastChannelService` - Inter-tab communication
57
+ - `NetworkInformationService` - Connection info and online status
58
+
59
+ ### Storage & I/O APIs
60
+
61
+ - `WebStorageService` - LocalStorage and SessionStorage helpers
62
+ - `WebShareService` - Native Web Share API support
63
+ - `ClipboardService` - System clipboard access
64
+ - `FileSystemAccessService` - Open/save files via native picker
65
+
66
+ ### Web APIs
67
+
68
+ - `WebWorkerService` - Web Worker management
69
+
70
+ ### Detection APIs
71
+
72
+ - `EyeDropperService` - Screen color picker
73
+
74
+ ### Security & Capabilities
75
+
76
+ - `PermissionsService` - Centralized browser permissions handling
77
+ - `BrowserCapabilityService` - Feature-detection for browser API availability
78
+
79
+ ### Utilities
80
+
81
+ - `BrowserApiBaseService` - Shared base class for browser API services
82
+
83
+ ## Installation
84
+
85
+ ```bash
86
+ pnpm add @angular-helpers/browser-web-apis
87
+ ```
88
+
89
+ ## Quick Setup
90
+
91
+ ### All-in-one (zero bundle budget concern)
92
+
93
+ ```typescript
94
+ import { provideBrowserWebApis } from '@angular-helpers/browser-web-apis';
95
+ import {
96
+ CameraService,
97
+ GeolocationService,
98
+ NotificationService,
99
+ } from '@angular-helpers/browser-web-apis';
100
+
101
+ bootstrapApplication(AppComponent, {
102
+ providers: [
103
+ provideBrowserWebApis({
104
+ services: [
105
+ CameraService,
106
+ GeolocationService,
107
+ NotificationService,
108
+ // Add more services as needed
109
+ ],
110
+ }),
111
+ ],
112
+ });
113
+ ```
114
+
115
+ ### Granular setup (recommended for production)
116
+
117
+ Each `provideX()` lives in its own module and imports only the service it needs. Bundlers (webpack, Rollup, Vite) will tree-shake anything you don't include.
118
+
119
+ ```typescript
120
+ import {
121
+ provideCamera,
122
+ provideGeolocation,
123
+ provideWebStorage,
124
+ } from '@angular-helpers/browser-web-apis';
125
+
126
+ bootstrapApplication(AppComponent, {
127
+ providers: [
128
+ provideCamera(), // β†’ only CameraService + PermissionsService
129
+ provideGeolocation(), // β†’ only GeolocationService + PermissionsService
130
+ provideWebStorage(), // β†’ only WebStorageService
131
+ ],
132
+ });
133
+ ```
134
+
135
+ Every service has a matching `provideX()` function:
136
+
137
+ | Function | Services included |
138
+ | ------------------------------- | ------------------------------------------- |
139
+ | `provideCamera()` | `PermissionsService`, `CameraService` |
140
+ | `provideGeolocation()` | `PermissionsService`, `GeolocationService` |
141
+ | `provideNotifications()` | `PermissionsService`, `NotificationService` |
142
+ | `provideClipboard()` | `PermissionsService`, `ClipboardService` |
143
+ | `provideMediaDevices()` | `PermissionsService`, `MediaDevicesService` |
144
+ | `provideWebStorage()` | `WebStorageService` |
145
+ | `provideWebSocket()` | `WebSocketService` |
146
+ | `provideWebWorker()` | `WebWorkerService` |
147
+ | `provideBattery()` | `BatteryService` |
148
+ | `provideWebShare()` | `WebShareService` |
149
+ | `provideIntersectionObserver()` | `IntersectionObserverService` |
150
+ | `provideResizeObserver()` | `ResizeObserverService` |
151
+ | `provideMutationObserver()` | `MutationObserverService` |
152
+ | `providePerformanceObserver()` | `PerformanceObserverService` |
153
+ | `providePageVisibility()` | `PageVisibilityService` |
154
+ | `provideNetworkInformation()` | `NetworkInformationService` |
155
+ | `provideScreenWakeLock()` | `ScreenWakeLockService` |
156
+ | `provideScreenOrientation()` | `ScreenOrientationService` |
157
+ | `provideFullscreen()` | `FullscreenService` |
158
+ | `provideFileSystemAccess()` | `FileSystemAccessService` |
159
+ | `provideMediaRecorder()` | `MediaRecorderService` |
160
+ | `provideBroadcastChannel()` | `BroadcastChannelService` |
161
+ | `provideServerSentEvents()` | `ServerSentEventsService` |
162
+ | `provideVibration()` | `VibrationService` |
163
+ | `provideSpeechSynthesis()` | `SpeechSynthesisService` |
164
+ | `provideWebAudio()` | `WebAudioService` |
165
+ | `provideGamepad()` | `GamepadService` |
166
+ | `provideWebLocks()` | `WebLocksService` |
167
+ | `provideStorageManager()` | `StorageManagerService` |
168
+ | `provideCompression()` | `CompressionService` |
169
+ | `provideEyeDropper()` | `EyeDropperService` |
170
+ | `provideIdleDetector()` | `IdleDetectorService` |
171
+ | `providePermissions()` | `PermissionsService` |
172
+
173
+ ### Combo providers
174
+
175
+ Convenience functions that bundle related services:
176
+
177
+ ```typescript
178
+ import {
179
+ provideMediaApis,
180
+ provideLocationApis,
181
+ provideStorageApis,
182
+ provideCommunicationApis,
183
+ } from '@angular-helpers/browser-web-apis';
184
+
185
+ bootstrapApplication(AppComponent, {
186
+ providers: [
187
+ provideMediaApis(), // Camera + MediaDevices + Permissions
188
+ provideLocationApis(), // Geolocation + Permissions
189
+ provideStorageApis(), // Clipboard + WebStorage + Permissions
190
+ provideCommunicationApis(), // Notification + WebShare + WebSocket + Permissions
191
+ ],
192
+ });
193
+ ```
194
+
195
+ ## Usage by Service
196
+
197
+ ### CameraService
198
+
199
+ ```typescript
200
+ import { CameraService } from '@angular-helpers/browser-web-apis';
201
+
202
+ export class PhotoComponent {
203
+ private cameraService = inject(CameraService);
204
+
205
+ async takePhoto() {
206
+ try {
207
+ const stream = await this.cameraService.startCamera({
208
+ video: true,
209
+ audio: false,
210
+ });
211
+
212
+ // Use stream for video/photo capture
213
+ } catch (error) {
214
+ console.error('Error accessing camera:', error);
215
+ }
216
+ }
217
+
218
+ async stopCamera() {
219
+ await this.cameraService.stopCamera();
220
+ }
221
+ }
222
+ ```
223
+
224
+ ### BrowserCapabilityService
225
+
226
+ ```typescript
227
+ import { BrowserCapabilityService } from '@angular-helpers/browser-web-apis';
228
+
229
+ export class MyComponent {
230
+ private capability = inject(BrowserCapabilityService);
231
+
232
+ ngOnInit() {
233
+ if (this.capability.isSupported('geolocation')) {
234
+ console.log('Geolocation is available');
235
+ }
236
+ }
237
+ }
238
+ ```
239
+
240
+ ### GeolocationService
241
+
242
+ ```typescript
243
+ import { GeolocationService } from '@angular-helpers/browser-web-apis';
244
+
245
+ export class LocationComponent {
246
+ private geolocation = inject(GeolocationService);
247
+
248
+ async getCurrentLocation() {
249
+ try {
250
+ const position = await this.geolocation.getCurrentPosition({
251
+ enableHighAccuracy: true,
252
+ timeout: 10000,
253
+ maximumAge: 60000,
254
+ });
255
+
256
+ console.log('Position:', position.coords);
257
+ } catch (error) {
258
+ console.error('Error getting location:', error);
259
+ }
260
+ }
261
+ }
262
+ ```
263
+
264
+ ### IntersectionObserverService
265
+
266
+ ```typescript
267
+ import { IntersectionObserverService } from '@angular-helpers/browser-web-apis';
268
+
269
+ export class LazyImageComponent {
270
+ private intersectionService = inject(IntersectionObserverService);
271
+ private elementRef = inject(ElementRef);
272
+
273
+ isVisible = signal(false);
274
+
275
+ ngAfterViewInit() {
276
+ this.intersectionService
277
+ .observeVisibility(this.elementRef.nativeElement, { threshold: 0.5 })
278
+ .subscribe((visible) => this.isVisible.set(visible));
279
+ }
280
+ }
281
+ ```
282
+
283
+ ### ResizeObserverService
284
+
285
+ ```typescript
286
+ import { ResizeObserverService } from '@angular-helpers/browser-web-apis';
287
+
288
+ export class ResponsiveComponent {
289
+ private resizeService = inject(ResizeObserverService);
290
+ private elementRef = inject(ElementRef);
291
+
292
+ elementSize = signal<ElementSize | null>(null);
293
+
294
+ ngAfterViewInit() {
295
+ this.resizeService
296
+ .observeSize(this.elementRef.nativeElement)
297
+ .subscribe((size) => this.elementSize.set(size));
298
+ }
299
+ }
300
+ ```
301
+
302
+ ### PageVisibilityService
303
+
304
+ ```typescript
305
+ import { PageVisibilityService } from '@angular-helpers/browser-web-apis';
306
+
307
+ export class AnalyticsComponent {
308
+ private visibilityService = inject(PageVisibilityService);
309
+
310
+ ngOnInit() {
311
+ this.visibilityService.watch().subscribe((state) => {
312
+ console.log('Page is now:', state);
313
+ });
314
+ }
315
+ }
316
+ ```
317
+
318
+ ### FullscreenService
319
+
320
+ ```typescript
321
+ import { FullscreenService } from '@angular-helpers/browser-web-apis';
322
+
323
+ export class VideoPlayerComponent {
324
+ private fullscreenService = inject(FullscreenService);
325
+
326
+ async toggleFullscreen() {
327
+ await this.fullscreenService.toggle();
328
+ }
329
+ }
330
+ ```
331
+
332
+ ### ScreenWakeLockService
333
+
334
+ ```typescript
335
+ import { ScreenWakeLockService } from '@angular-helpers/browser-web-apis';
336
+
337
+ export class PresentationComponent {
338
+ private wakeLockService = inject(ScreenWakeLockService);
339
+
340
+ async keepScreenOn() {
341
+ await this.wakeLockService.request();
342
+ }
343
+
344
+ async releaseScreen() {
345
+ await this.wakeLockService.release();
346
+ }
347
+ }
348
+ ```
349
+
350
+ ### BroadcastChannelService
351
+
352
+ ```typescript
353
+ import { BroadcastChannelService } from '@angular-helpers/browser-web-apis';
354
+
355
+ export class SyncComponent {
356
+ private broadcastService = inject(BroadcastChannelService);
357
+
358
+ ngOnInit() {
359
+ // Listen for messages from other tabs
360
+ this.broadcastService.open<string>('app-sync').subscribe((msg) => {
361
+ console.log('Received:', msg);
362
+ });
363
+ }
364
+
365
+ sendMessage(data: string) {
366
+ this.broadcastService.post('app-sync', data);
367
+ }
368
+ }
369
+ ```
370
+
371
+ ### ServerSentEventsService
372
+
373
+ ```typescript
374
+ import { ServerSentEventsService } from '@angular-helpers/browser-web-apis';
375
+
376
+ export class LiveFeedComponent {
377
+ private sseService = inject(ServerSentEventsService);
378
+
379
+ connectToEvents() {
380
+ this.sseService.connect('https://api.example.com/events').subscribe({
381
+ next: (message) => console.log('Event:', message),
382
+ error: (err) => console.error('SSE error:', err),
383
+ });
384
+ }
385
+ }
386
+ ```
387
+
388
+ ### VibrationService
389
+
390
+ ```typescript
391
+ import { VibrationService } from '@angular-helpers/browser-web-apis';
392
+
393
+ export class FeedbackComponent {
394
+ private vibrationService = inject(VibrationService);
395
+
396
+ onSuccess() {
397
+ this.vibrationService.success();
398
+ }
399
+
400
+ onError() {
401
+ this.vibrationService.error();
402
+ }
403
+ }
404
+ ```
405
+
406
+ ### SpeechSynthesisService
407
+
408
+ ```typescript
409
+ import { SpeechSynthesisService } from '@angular-helpers/browser-web-apis';
410
+
411
+ export class VoiceComponent {
412
+ private speechService = inject(SpeechSynthesisService);
413
+
414
+ speakText(text: string) {
415
+ this.speechService.speak(text).subscribe((state) => {
416
+ console.log('Speech state:', state);
417
+ });
418
+ }
419
+ }
420
+ ```
421
+
422
+ ### BatteryService
423
+
424
+ ```typescript
425
+ import { BatteryService } from '@angular-helpers/browser-web-apis';
426
+
427
+ export class PowerComponent {
428
+ private batteryService = inject(BatteryService);
429
+
430
+ async ngOnInit() {
431
+ try {
432
+ // Initialize and get initial battery info
433
+ const batteryInfo = await this.batteryService.initialize();
434
+ console.log('Battery level:', batteryInfo.level);
435
+ console.log('Is charging:', batteryInfo.charging);
436
+
437
+ // Watch for battery changes
438
+ this.batteryService.watchBatteryInfo().subscribe((info) => {
439
+ console.log('Battery updated:', info);
440
+ });
441
+ } catch (error) {
442
+ console.error('Battery API not supported:', error);
443
+ }
444
+ }
445
+ }
446
+ ```
447
+
448
+ ### ClipboardService
449
+
450
+ ```typescript
451
+ import { ClipboardService } from '@angular-helpers/browser-web-apis';
452
+
453
+ export class CopyComponent {
454
+ private clipboardService = inject(ClipboardService);
455
+
456
+ async copyToClipboard(text: string) {
457
+ try {
458
+ await this.clipboardService.writeText(text);
459
+ console.log('Copied successfully');
460
+ } catch (error) {
461
+ console.error('Failed to copy:', error);
462
+ }
463
+ }
464
+
465
+ async pasteFromClipboard(): Promise<string> {
466
+ try {
467
+ const text = await this.clipboardService.readText();
468
+ return text;
469
+ } catch (error) {
470
+ console.error('Failed to read clipboard:', error);
471
+ return '';
472
+ }
473
+ }
474
+ }
475
+ ```
476
+
477
+ ### FileSystemAccessService
478
+
479
+ ```typescript
480
+ import { FileSystemAccessService } from '@angular-helpers/browser-web-apis';
481
+
482
+ export class FileManagerComponent {
483
+ private fileService = inject(FileSystemAccessService);
484
+
485
+ async openFiles() {
486
+ try {
487
+ const files = await this.fileService.openFile({
488
+ multiple: true,
489
+ types: [
490
+ {
491
+ description: 'Text files',
492
+ accept: { 'text/plain': ['.txt'] },
493
+ },
494
+ ],
495
+ });
496
+ console.log('Selected files:', files);
497
+ } catch (error) {
498
+ console.error('Failed to open files:', error);
499
+ }
500
+ }
501
+
502
+ async saveContent(content: string) {
503
+ try {
504
+ await this.fileService.saveFile(content, {
505
+ suggestedName: 'document.txt',
506
+ });
507
+ } catch (error) {
508
+ console.error('Failed to save file:', error);
509
+ }
510
+ }
511
+ }
512
+ ```
513
+
514
+ ### MediaRecorderService
515
+
516
+ ```typescript
517
+ import { MediaRecorderService } from '@angular-helpers/browser-web-apis';
518
+
519
+ export class RecorderComponent {
520
+ private recorderService = inject(MediaRecorderService);
521
+ private stream: MediaStream | null = null;
522
+
523
+ async startRecording() {
524
+ try {
525
+ this.stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
526
+ await this.recorderService.start(this.stream, { mimeType: 'video/webm' });
527
+ } catch (error) {
528
+ console.error('Failed to start recording:', error);
529
+ }
530
+ }
531
+
532
+ stopRecording() {
533
+ const result = this.recorderService.stop();
534
+ if (result) {
535
+ console.log('Recording saved, blob URL:', result.url);
536
+ }
537
+ }
538
+ }
539
+ ```
540
+
541
+ ### WebSocketService
542
+
543
+ ```typescript
544
+ import { WebSocketService } from '@angular-helpers/browser-web-apis';
545
+
546
+ export class LiveComponent {
547
+ private wsService = inject(WebSocketService);
548
+
549
+ connect() {
550
+ this.wsService
551
+ .connect({
552
+ url: 'wss://example.com/socket',
553
+ reconnectInterval: 3000,
554
+ maxReconnectAttempts: 5,
555
+ })
556
+ .subscribe((status) => {
557
+ console.log('Connection status:', status);
558
+ });
559
+
560
+ this.wsService.getMessages().subscribe((message) => {
561
+ console.log('Received:', message);
562
+ });
563
+ }
564
+
565
+ sendMessage(data: unknown) {
566
+ this.wsService.send({ type: 'message', data });
567
+ }
568
+ }
569
+ ```
570
+
571
+ ### WebStorageService
572
+
573
+ ```typescript
574
+ import { WebStorageService } from '@angular-helpers/browser-web-apis';
575
+
576
+ export class SettingsComponent {
577
+ private storageService = inject(WebStorageService);
578
+
579
+ saveSetting(key: string, value: unknown) {
580
+ this.storageService.setLocalStorage(key, value);
581
+ }
582
+
583
+ getSetting<T>(key: string): T | null {
584
+ return this.storageService.getLocalStorage<T>(key);
585
+ }
586
+
587
+ watchSetting<T>(key: string) {
588
+ return this.storageService.watchLocalStorage<T>(key).subscribe((value) => {
589
+ console.log('Setting changed:', value);
590
+ });
591
+ }
592
+ }
593
+ ```
594
+
595
+ ### WebWorkerService
596
+
597
+ ```typescript
598
+ import { WebWorkerService } from '@angular-helpers/browser-web-apis';
599
+
600
+ export class WorkerComponent {
601
+ private workerService = inject(WebWorkerService);
602
+
603
+ async createWorker() {
604
+ this.workerService
605
+ .createWorker('calc-worker', '/assets/workers/calc.worker.js')
606
+ .subscribe((status) => {
607
+ console.log('Worker status:', status);
608
+ });
609
+
610
+ this.workerService.getMessages('calc-worker').subscribe((message) => {
611
+ console.log('Worker response:', message);
612
+ });
613
+ }
614
+
615
+ sendTask(data: unknown) {
616
+ this.workerService.postMessage('calc-worker', {
617
+ id: 'task-1',
618
+ type: 'CALCULATE',
619
+ data,
620
+ });
621
+ }
622
+ }
623
+ ```
624
+
625
+ ## Signal Fn Primitives
626
+
627
+ Zero-boilerplate reactive alternatives to the RxJS-based services. Each `inject*` function returns a ref object with read-only signals and handles cleanup automatically via `DestroyRef`.
628
+
629
+ ### injectPageVisibility
630
+
631
+ ```typescript
632
+ import { injectPageVisibility } from '@angular-helpers/browser-web-apis';
633
+
634
+ @Component({...})
635
+ export class MyComponent {
636
+ readonly visibility = injectPageVisibility();
637
+
638
+ // visibility.state() β†’ 'visible' | 'hidden'
639
+ // visibility.isVisible() β†’ boolean
640
+ // visibility.isHidden() β†’ boolean
641
+ }
642
+ ```
643
+
644
+ ### injectResizeObserver
645
+
646
+ Accepts `Element`, `ElementRef`, or a **`Signal<ElementRef | undefined>`** (e.g. from `viewChild`). When a signal is passed, the observer automatically starts when the element becomes available.
647
+
648
+ ```typescript
649
+ import { injectResizeObserver } from '@angular-helpers/browser-web-apis';
650
+
651
+ @Component({...})
652
+ export class MyComponent {
653
+ readonly boxRef = viewChild<ElementRef>('box');
654
+ readonly resize = injectResizeObserver(this.boxRef);
655
+
656
+ // resize.width() β†’ number
657
+ // resize.height() β†’ number
658
+ // resize.inlineSize() β†’ number (logical)
659
+ // resize.blockSize() β†’ number (logical)
660
+ // resize.size() β†’ ElementSize | null
661
+ }
662
+ ```
663
+
664
+ ### injectIntersectionObserver
665
+
666
+ Same `ElementInput` flexibility β€” works with `viewChild` signals out of the box.
667
+
668
+ ```typescript
669
+ import { injectIntersectionObserver } from '@angular-helpers/browser-web-apis';
670
+
671
+ @Component({...})
672
+ export class MyComponent {
673
+ readonly targetRef = viewChild<ElementRef>('target');
674
+ readonly inView = injectIntersectionObserver(this.targetRef, { threshold: 0.25 });
675
+
676
+ // inView.isIntersecting() β†’ boolean
677
+ // inView.isVisible() β†’ boolean
678
+ }
679
+ ```
680
+
681
+ ### injectNetworkInformation
682
+
683
+ ```typescript
684
+ import { injectNetworkInformation } from '@angular-helpers/browser-web-apis';
685
+
686
+ @Component({...})
687
+ export class MyComponent {
688
+ readonly net = injectNetworkInformation();
689
+
690
+ // net.online() β†’ boolean
691
+ // net.effectiveType() β†’ '4g' | '3g' | '2g' | 'slow-2g' | undefined
692
+ // net.downlink() β†’ number | undefined (Mbps)
693
+ // net.rtt() β†’ number | undefined (ms)
694
+ // net.type() β†’ ConnectionType | undefined
695
+ // net.saveData() β†’ boolean | undefined
696
+ }
697
+ ```
698
+
699
+ ### injectScreenOrientation
700
+
701
+ ```typescript
702
+ import { injectScreenOrientation } from '@angular-helpers/browser-web-apis';
703
+
704
+ @Component({...})
705
+ export class MyComponent {
706
+ readonly orientation = injectScreenOrientation();
707
+
708
+ // orientation.type() β†’ OrientationType
709
+ // orientation.angle() β†’ number
710
+ // orientation.isPortrait() β†’ boolean
711
+ // orientation.isLandscape() β†’ boolean
712
+ // orientation.lock('landscape') β†’ Promise<void>
713
+ // orientation.unlock()
714
+ }
715
+ ```
716
+
717
+ ### injectMutationObserver
718
+
719
+ Accepts the same `ElementInput` type β€” works with `viewChild` signals.
720
+
721
+ ```typescript
722
+ import { injectMutationObserver } from '@angular-helpers/browser-web-apis';
723
+
724
+ @Component({...})
725
+ export class MyComponent {
726
+ readonly targetRef = viewChild<ElementRef>('target');
727
+ readonly mo = injectMutationObserver(this.targetRef, { childList: true });
728
+
729
+ // mo.mutations() β†’ MutationRecord[]
730
+ // mo.mutationCount() β†’ number
731
+ }
732
+ ```
733
+
734
+ ### injectPerformanceObserver
735
+
736
+ ```typescript
737
+ import { injectPerformanceObserver } from '@angular-helpers/browser-web-apis';
738
+
739
+ @Component({...})
740
+ export class MyComponent {
741
+ readonly perf = injectPerformanceObserver({ type: 'navigation', buffered: true });
742
+
743
+ // perf.entries() β†’ PerformanceEntryList
744
+ // perf.entryCount() β†’ number
745
+ // perf.latestEntry() β†’ PerformanceEntry | undefined
746
+ }
747
+ ```
748
+
749
+ ### injectIdleDetector
750
+
751
+ ```typescript
752
+ import { injectIdleDetector } from '@angular-helpers/browser-web-apis';
753
+
754
+ @Component({...})
755
+ export class MyComponent {
756
+ readonly idle = injectIdleDetector({ threshold: 120_000 });
757
+
758
+ // idle.userState() β†’ 'active' | 'idle'
759
+ // idle.screenState() β†’ 'locked' | 'unlocked'
760
+ // idle.isUserIdle() β†’ boolean
761
+ // idle.isScreenLocked() β†’ boolean
762
+ }
763
+ ```
764
+
765
+ ### injectGamepad
766
+
767
+ ```typescript
768
+ import { injectGamepad } from '@angular-helpers/browser-web-apis';
769
+
770
+ @Component({...})
771
+ export class MyComponent {
772
+ readonly gp = injectGamepad(0);
773
+
774
+ // gp.connected() β†’ boolean
775
+ // gp.buttons() β†’ ReadonlyArray<{ pressed: boolean; value: number }>
776
+ // gp.axes() β†’ readonly number[]
777
+ // gp.state() β†’ GamepadState | null
778
+ }
779
+ ```
780
+
781
+ ### injectClipboard
782
+
783
+ ```typescript
784
+ import { injectClipboard } from '@angular-helpers/browser-web-apis';
785
+
786
+ @Component({...})
787
+ export class MyComponent {
788
+ readonly cb = injectClipboard();
789
+
790
+ // cb.text() β†’ string | null (last copied text)
791
+ // cb.error() β†’ string | null
792
+ // cb.busy() β†’ boolean
793
+ // cb.isSupported() β†’ boolean
794
+
795
+ async copy(text: string) {
796
+ await this.cb.writeText(text);
797
+ }
798
+ }
799
+ ```
800
+
801
+ ### injectGeolocation
802
+
803
+ ```typescript
804
+ import { injectGeolocation } from '@angular-helpers/browser-web-apis';
805
+
806
+ @Component({...})
807
+ export class MyComponent {
808
+ readonly geo = injectGeolocation({ watch: true });
809
+
810
+ // geo.position() β†’ GeolocationPosition | null
811
+ // geo.error() β†’ GeolocationPositionError | null
812
+ // geo.watching() β†’ boolean
813
+ // geo.isSupported() β†’ boolean
814
+
815
+ stopWatching() {
816
+ this.geo.stop();
817
+ }
818
+ }
819
+ ```
820
+
821
+ ### injectBattery
822
+
823
+ ```typescript
824
+ import { injectBattery } from '@angular-helpers/browser-web-apis';
825
+
826
+ @Component({...})
827
+ export class MyComponent {
828
+ readonly battery = injectBattery();
829
+
830
+ // battery.info() β†’ BatteryInfo | null (level, charging, times)
831
+ // battery.error() β†’ string | null
832
+ // battery.isSupported() β†’ boolean
833
+
834
+ async refresh() {
835
+ await this.battery.refresh();
836
+ }
837
+ }
838
+ ```
839
+
840
+ ### injectWakeLock
841
+
842
+ ```typescript
843
+ import { injectWakeLock } from '@angular-helpers/browser-web-apis';
844
+
845
+ @Component({...})
846
+ export class MyComponent {
847
+ readonly wakeLock = injectWakeLock();
848
+
849
+ // wakeLock.active() β†’ boolean
850
+ // wakeLock.error() β†’ string | null
851
+ // wakeLock.isSupported() β†’ boolean
852
+
853
+ async toggle() {
854
+ if (this.wakeLock.active()) {
855
+ await this.wakeLock.release();
856
+ } else {
857
+ await this.wakeLock.request();
858
+ }
859
+ }
860
+ }
861
+ ```
862
+
863
+ ### injectEyeDropper
864
+
865
+ ```typescript
866
+ import { injectEyeDropper } from '@angular-helpers/browser-web-apis';
867
+
868
+ @Component({...})
869
+ export class MyComponent {
870
+ readonly dropper = injectEyeDropper();
871
+
872
+ // dropper.color() β†’ Signal<string | null> (sRGBHex)
873
+ // dropper.isOpening() β†’ Signal<boolean>
874
+ // dropper.error() β†’ Signal<Error | null>
875
+ // dropper.isSupported() β†’ Signal<boolean>
876
+
877
+ async pickColor() {
878
+ const result = await this.dropper.open();
879
+ if (result) {
880
+ console.log('Selected color:', result.sRGBHex);
881
+ }
882
+ }
883
+ }
884
+ ```
885
+
886
+ ### ElementInput type
887
+
888
+ Both `injectResizeObserver` and `injectIntersectionObserver` accept the `ElementInput` type:
889
+
890
+ ```typescript
891
+ type ElementInput =
892
+ | Element
893
+ | ElementRef<Element>
894
+ | Signal<Element | ElementRef<Element> | undefined>;
895
+ ```
896
+
897
+ This means you can pass a `viewChild` signal directly β€” the function will internally use an `effect` to start observing once the element is rendered, with automatic cleanup.
898
+
899
+ ## Browser Support
900
+
901
+ The services automatically validate browser support and unsupported-path handling:
902
+
903
+ - Chrome: full support
904
+ - Firefox: full support
905
+ - Safari: partial support
906
+ - Edge: full support
907
+ - Mobile browsers: depends on platform and API
908
+
909
+ ## Notes
910
+
911
+ - Secure context (HTTPS) is required for several APIs
912
+ - Some APIs require explicit user interaction
913
+ - Permission behavior varies by browser
914
+ - Always implement error handling and fallback logic
915
+
916
+ ## License
917
+
918
+ MIT