@angular-helpers/browser-web-apis 21.1.0 → 21.3.0
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.
- package/README.es.md +523 -2
- package/README.md +579 -2
- package/fesm2022/angular-helpers-browser-web-apis.mjs +1990 -3
- package/package.json +1 -1
- package/types/angular-helpers-browser-web-apis.d.ts +864 -6
package/README.md
CHANGED
|
@@ -23,14 +23,61 @@ Angular services package for a structured and secure access layer over browser W
|
|
|
23
23
|
- `MediaDevicesService` - Media device listing and management
|
|
24
24
|
- `GeolocationService` - Geolocation API access
|
|
25
25
|
- `NotificationService` - Browser notifications API
|
|
26
|
+
- `MediaRecorderService` - Record audio/video from MediaStream
|
|
26
27
|
|
|
27
|
-
###
|
|
28
|
+
### Observer APIs
|
|
29
|
+
|
|
30
|
+
- `IntersectionObserverService` - Detect when elements enter/exit viewport
|
|
31
|
+
- `ResizeObserverService` - Watch for element size changes
|
|
32
|
+
- `MutationObserverService` - Watch for DOM mutations
|
|
33
|
+
- `PerformanceObserverService` - Monitor performance entries (LCP, CLS, etc.)
|
|
34
|
+
|
|
35
|
+
### System APIs
|
|
36
|
+
|
|
37
|
+
- `BatteryService` - Monitor battery status and charging state
|
|
38
|
+
- `PageVisibilityService` - Track document visibility state changes
|
|
39
|
+
- `ScreenWakeLockService` - Prevent screen from dimming or locking
|
|
40
|
+
- `ScreenOrientationService` - Read and lock screen orientation
|
|
41
|
+
- `FullscreenService` - Toggle fullscreen mode for elements
|
|
42
|
+
- `VibrationService` - Trigger haptic feedback patterns
|
|
43
|
+
- `SpeechSynthesisService` - Text-to-speech with voice selection
|
|
44
|
+
- `IdleDetectorService` - Detect user idle state and screen lock
|
|
45
|
+
- `GamepadService` - Game controller input polling
|
|
46
|
+
- `WebAudioService` - Audio context, oscillators, and analysers
|
|
47
|
+
|
|
48
|
+
### Network APIs
|
|
28
49
|
|
|
29
|
-
- `WebWorkerService` - Web Worker management
|
|
30
50
|
- `WebSocketService` - WebSocket connection handling
|
|
51
|
+
- `ServerSentEventsService` - Server-Sent Events client
|
|
52
|
+
- `BroadcastChannelService` - Inter-tab communication
|
|
53
|
+
- `NetworkInformationService` - Connection info and online status
|
|
54
|
+
|
|
55
|
+
### Storage & I/O APIs
|
|
56
|
+
|
|
31
57
|
- `WebStorageService` - LocalStorage and SessionStorage helpers
|
|
32
58
|
- `WebShareService` - Native Web Share API support
|
|
33
59
|
- `ClipboardService` - System clipboard access
|
|
60
|
+
- `FileSystemAccessService` - Open/save files via native picker
|
|
61
|
+
|
|
62
|
+
### Web APIs
|
|
63
|
+
|
|
64
|
+
- `WebWorkerService` - Web Worker management
|
|
65
|
+
|
|
66
|
+
### Device APIs
|
|
67
|
+
|
|
68
|
+
- `WebBluetoothService` - Bluetooth Low Energy device communication
|
|
69
|
+
- `WebUsbService` - USB device I/O from the browser
|
|
70
|
+
- `WebNfcService` - NFC tag reading and writing
|
|
71
|
+
|
|
72
|
+
### Detection APIs
|
|
73
|
+
|
|
74
|
+
- `EyeDropperService` - Screen color picker
|
|
75
|
+
- `BarcodeDetectorService` - QR code and barcode scanning
|
|
76
|
+
|
|
77
|
+
### Commerce & Identity APIs
|
|
78
|
+
|
|
79
|
+
- `PaymentRequestService` - Native payment flows
|
|
80
|
+
- `CredentialManagementService` - Passwords, passkeys (WebAuthn)
|
|
34
81
|
|
|
35
82
|
### Security & Capabilities
|
|
36
83
|
|
|
@@ -132,6 +179,536 @@ export class LocationComponent {
|
|
|
132
179
|
}
|
|
133
180
|
```
|
|
134
181
|
|
|
182
|
+
### IntersectionObserverService
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { IntersectionObserverService } from '@angular-helpers/browser-web-apis';
|
|
186
|
+
|
|
187
|
+
export class LazyImageComponent {
|
|
188
|
+
private intersectionService = inject(IntersectionObserverService);
|
|
189
|
+
private elementRef = inject(ElementRef);
|
|
190
|
+
|
|
191
|
+
isVisible = signal(false);
|
|
192
|
+
|
|
193
|
+
ngAfterViewInit() {
|
|
194
|
+
this.intersectionService
|
|
195
|
+
.observeVisibility(this.elementRef.nativeElement, { threshold: 0.5 })
|
|
196
|
+
.subscribe((visible) => this.isVisible.set(visible));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### ResizeObserverService
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { ResizeObserverService } from '@angular-helpers/browser-web-apis';
|
|
205
|
+
|
|
206
|
+
export class ResponsiveComponent {
|
|
207
|
+
private resizeService = inject(ResizeObserverService);
|
|
208
|
+
private elementRef = inject(ElementRef);
|
|
209
|
+
|
|
210
|
+
elementSize = signal<ElementSize | null>(null);
|
|
211
|
+
|
|
212
|
+
ngAfterViewInit() {
|
|
213
|
+
this.resizeService
|
|
214
|
+
.observeSize(this.elementRef.nativeElement)
|
|
215
|
+
.subscribe((size) => this.elementSize.set(size));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### PageVisibilityService
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { PageVisibilityService } from '@angular-helpers/browser-web-apis';
|
|
224
|
+
|
|
225
|
+
export class AnalyticsComponent {
|
|
226
|
+
private visibilityService = inject(PageVisibilityService);
|
|
227
|
+
|
|
228
|
+
ngOnInit() {
|
|
229
|
+
this.visibilityService.watch().subscribe((state) => {
|
|
230
|
+
console.log('Page is now:', state);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### FullscreenService
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { FullscreenService } from '@angular-helpers/browser-web-apis';
|
|
240
|
+
|
|
241
|
+
export class VideoPlayerComponent {
|
|
242
|
+
private fullscreenService = inject(FullscreenService);
|
|
243
|
+
|
|
244
|
+
async toggleFullscreen() {
|
|
245
|
+
await this.fullscreenService.toggle();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### ScreenWakeLockService
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import { ScreenWakeLockService } from '@angular-helpers/browser-web-apis';
|
|
254
|
+
|
|
255
|
+
export class PresentationComponent {
|
|
256
|
+
private wakeLockService = inject(ScreenWakeLockService);
|
|
257
|
+
|
|
258
|
+
async keepScreenOn() {
|
|
259
|
+
await this.wakeLockService.request();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async releaseScreen() {
|
|
263
|
+
await this.wakeLockService.release();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### BroadcastChannelService
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { BroadcastChannelService } from '@angular-helpers/browser-web-apis';
|
|
272
|
+
|
|
273
|
+
export class SyncComponent {
|
|
274
|
+
private broadcastService = inject(BroadcastChannelService);
|
|
275
|
+
|
|
276
|
+
ngOnInit() {
|
|
277
|
+
// Listen for messages from other tabs
|
|
278
|
+
this.broadcastService.open<string>('app-sync').subscribe((msg) => {
|
|
279
|
+
console.log('Received:', msg);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
sendMessage(data: string) {
|
|
284
|
+
this.broadcastService.post('app-sync', data);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### ServerSentEventsService
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import { ServerSentEventsService } from '@angular-helpers/browser-web-apis';
|
|
293
|
+
|
|
294
|
+
export class LiveFeedComponent {
|
|
295
|
+
private sseService = inject(ServerSentEventsService);
|
|
296
|
+
|
|
297
|
+
connectToEvents() {
|
|
298
|
+
this.sseService.connect('https://api.example.com/events').subscribe({
|
|
299
|
+
next: (message) => console.log('Event:', message),
|
|
300
|
+
error: (err) => console.error('SSE error:', err),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### VibrationService
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { VibrationService } from '@angular-helpers/browser-web-apis';
|
|
310
|
+
|
|
311
|
+
export class FeedbackComponent {
|
|
312
|
+
private vibrationService = inject(VibrationService);
|
|
313
|
+
|
|
314
|
+
onSuccess() {
|
|
315
|
+
this.vibrationService.success();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
onError() {
|
|
319
|
+
this.vibrationService.error();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### SpeechSynthesisService
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { SpeechSynthesisService } from '@angular-helpers/browser-web-apis';
|
|
328
|
+
|
|
329
|
+
export class VoiceComponent {
|
|
330
|
+
private speechService = inject(SpeechSynthesisService);
|
|
331
|
+
|
|
332
|
+
speakText(text: string) {
|
|
333
|
+
this.speechService.speak(text).subscribe((state) => {
|
|
334
|
+
console.log('Speech state:', state);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### BatteryService
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
import { BatteryService } from '@angular-helpers/browser-web-apis';
|
|
344
|
+
|
|
345
|
+
export class PowerComponent {
|
|
346
|
+
private batteryService = inject(BatteryService);
|
|
347
|
+
|
|
348
|
+
async ngOnInit() {
|
|
349
|
+
try {
|
|
350
|
+
// Initialize and get initial battery info
|
|
351
|
+
const batteryInfo = await this.batteryService.initialize();
|
|
352
|
+
console.log('Battery level:', batteryInfo.level);
|
|
353
|
+
console.log('Is charging:', batteryInfo.charging);
|
|
354
|
+
|
|
355
|
+
// Watch for battery changes
|
|
356
|
+
this.batteryService.watchBatteryInfo().subscribe((info) => {
|
|
357
|
+
console.log('Battery updated:', info);
|
|
358
|
+
});
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.error('Battery API not supported:', error);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### ClipboardService
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { ClipboardService } from '@angular-helpers/browser-web-apis';
|
|
370
|
+
|
|
371
|
+
export class CopyComponent {
|
|
372
|
+
private clipboardService = inject(ClipboardService);
|
|
373
|
+
|
|
374
|
+
async copyToClipboard(text: string) {
|
|
375
|
+
try {
|
|
376
|
+
await this.clipboardService.writeText(text);
|
|
377
|
+
console.log('Copied successfully');
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.error('Failed to copy:', error);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async pasteFromClipboard(): Promise<string> {
|
|
384
|
+
try {
|
|
385
|
+
const text = await this.clipboardService.readText();
|
|
386
|
+
return text;
|
|
387
|
+
} catch (error) {
|
|
388
|
+
console.error('Failed to read clipboard:', error);
|
|
389
|
+
return '';
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### FileSystemAccessService
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { FileSystemAccessService } from '@angular-helpers/browser-web-apis';
|
|
399
|
+
|
|
400
|
+
export class FileManagerComponent {
|
|
401
|
+
private fileService = inject(FileSystemAccessService);
|
|
402
|
+
|
|
403
|
+
async openFiles() {
|
|
404
|
+
try {
|
|
405
|
+
const files = await this.fileService.openFile({
|
|
406
|
+
multiple: true,
|
|
407
|
+
types: [
|
|
408
|
+
{
|
|
409
|
+
description: 'Text files',
|
|
410
|
+
accept: { 'text/plain': ['.txt'] },
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
});
|
|
414
|
+
console.log('Selected files:', files);
|
|
415
|
+
} catch (error) {
|
|
416
|
+
console.error('Failed to open files:', error);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async saveContent(content: string) {
|
|
421
|
+
try {
|
|
422
|
+
await this.fileService.saveFile(content, {
|
|
423
|
+
suggestedName: 'document.txt',
|
|
424
|
+
});
|
|
425
|
+
} catch (error) {
|
|
426
|
+
console.error('Failed to save file:', error);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### MediaRecorderService
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
import { MediaRecorderService } from '@angular-helpers/browser-web-apis';
|
|
436
|
+
|
|
437
|
+
export class RecorderComponent {
|
|
438
|
+
private recorderService = inject(MediaRecorderService);
|
|
439
|
+
private stream: MediaStream | null = null;
|
|
440
|
+
|
|
441
|
+
async startRecording() {
|
|
442
|
+
try {
|
|
443
|
+
this.stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
|
444
|
+
await this.recorderService.start(this.stream, { mimeType: 'video/webm' });
|
|
445
|
+
} catch (error) {
|
|
446
|
+
console.error('Failed to start recording:', error);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
stopRecording() {
|
|
451
|
+
const result = this.recorderService.stop();
|
|
452
|
+
if (result) {
|
|
453
|
+
console.log('Recording saved, blob URL:', result.url);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### WebSocketService
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
import { WebSocketService } from '@angular-helpers/browser-web-apis';
|
|
463
|
+
|
|
464
|
+
export class LiveComponent {
|
|
465
|
+
private wsService = inject(WebSocketService);
|
|
466
|
+
|
|
467
|
+
connect() {
|
|
468
|
+
this.wsService
|
|
469
|
+
.connect({
|
|
470
|
+
url: 'wss://example.com/socket',
|
|
471
|
+
reconnectInterval: 3000,
|
|
472
|
+
maxReconnectAttempts: 5,
|
|
473
|
+
})
|
|
474
|
+
.subscribe((status) => {
|
|
475
|
+
console.log('Connection status:', status);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
this.wsService.getMessages().subscribe((message) => {
|
|
479
|
+
console.log('Received:', message);
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
sendMessage(data: unknown) {
|
|
484
|
+
this.wsService.send({ type: 'message', data });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### WebStorageService
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
import { WebStorageService } from '@angular-helpers/browser-web-apis';
|
|
493
|
+
|
|
494
|
+
export class SettingsComponent {
|
|
495
|
+
private storageService = inject(WebStorageService);
|
|
496
|
+
|
|
497
|
+
saveSetting(key: string, value: unknown) {
|
|
498
|
+
this.storageService.setLocalStorage(key, value);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
getSetting<T>(key: string): T | null {
|
|
502
|
+
return this.storageService.getLocalStorage<T>(key);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
watchSetting<T>(key: string) {
|
|
506
|
+
return this.storageService.watchLocalStorage<T>(key).subscribe((value) => {
|
|
507
|
+
console.log('Setting changed:', value);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### WebWorkerService
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
import { WebWorkerService } from '@angular-helpers/browser-web-apis';
|
|
517
|
+
|
|
518
|
+
export class WorkerComponent {
|
|
519
|
+
private workerService = inject(WebWorkerService);
|
|
520
|
+
|
|
521
|
+
async createWorker() {
|
|
522
|
+
this.workerService
|
|
523
|
+
.createWorker('calc-worker', '/assets/workers/calc.worker.js')
|
|
524
|
+
.subscribe((status) => {
|
|
525
|
+
console.log('Worker status:', status);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
this.workerService.getMessages('calc-worker').subscribe((message) => {
|
|
529
|
+
console.log('Worker response:', message);
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
sendTask(data: unknown) {
|
|
534
|
+
this.workerService.postMessage('calc-worker', {
|
|
535
|
+
id: 'task-1',
|
|
536
|
+
type: 'CALCULATE',
|
|
537
|
+
data,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
## Signal Fn Primitives
|
|
544
|
+
|
|
545
|
+
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`.
|
|
546
|
+
|
|
547
|
+
### injectPageVisibility
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
import { injectPageVisibility } from '@angular-helpers/browser-web-apis';
|
|
551
|
+
|
|
552
|
+
@Component({...})
|
|
553
|
+
export class MyComponent {
|
|
554
|
+
readonly visibility = injectPageVisibility();
|
|
555
|
+
|
|
556
|
+
// visibility.state() → 'visible' | 'hidden'
|
|
557
|
+
// visibility.isVisible() → boolean
|
|
558
|
+
// visibility.isHidden() → boolean
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### injectResizeObserver
|
|
563
|
+
|
|
564
|
+
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.
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { injectResizeObserver } from '@angular-helpers/browser-web-apis';
|
|
568
|
+
|
|
569
|
+
@Component({...})
|
|
570
|
+
export class MyComponent {
|
|
571
|
+
readonly boxRef = viewChild<ElementRef>('box');
|
|
572
|
+
readonly resize = injectResizeObserver(this.boxRef);
|
|
573
|
+
|
|
574
|
+
// resize.width() → number
|
|
575
|
+
// resize.height() → number
|
|
576
|
+
// resize.inlineSize() → number (logical)
|
|
577
|
+
// resize.blockSize() → number (logical)
|
|
578
|
+
// resize.size() → ElementSize | null
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### injectIntersectionObserver
|
|
583
|
+
|
|
584
|
+
Same `ElementInput` flexibility — works with `viewChild` signals out of the box.
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
import { injectIntersectionObserver } from '@angular-helpers/browser-web-apis';
|
|
588
|
+
|
|
589
|
+
@Component({...})
|
|
590
|
+
export class MyComponent {
|
|
591
|
+
readonly targetRef = viewChild<ElementRef>('target');
|
|
592
|
+
readonly inView = injectIntersectionObserver(this.targetRef, { threshold: 0.25 });
|
|
593
|
+
|
|
594
|
+
// inView.isIntersecting() → boolean
|
|
595
|
+
// inView.isVisible() → boolean
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### injectNetworkInformation
|
|
600
|
+
|
|
601
|
+
```typescript
|
|
602
|
+
import { injectNetworkInformation } from '@angular-helpers/browser-web-apis';
|
|
603
|
+
|
|
604
|
+
@Component({...})
|
|
605
|
+
export class MyComponent {
|
|
606
|
+
readonly net = injectNetworkInformation();
|
|
607
|
+
|
|
608
|
+
// net.online() → boolean
|
|
609
|
+
// net.effectiveType() → '4g' | '3g' | '2g' | 'slow-2g' | undefined
|
|
610
|
+
// net.downlink() → number | undefined (Mbps)
|
|
611
|
+
// net.rtt() → number | undefined (ms)
|
|
612
|
+
// net.type() → ConnectionType | undefined
|
|
613
|
+
// net.saveData() → boolean | undefined
|
|
614
|
+
}
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### injectScreenOrientation
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
import { injectScreenOrientation } from '@angular-helpers/browser-web-apis';
|
|
621
|
+
|
|
622
|
+
@Component({...})
|
|
623
|
+
export class MyComponent {
|
|
624
|
+
readonly orientation = injectScreenOrientation();
|
|
625
|
+
|
|
626
|
+
// orientation.type() → OrientationType
|
|
627
|
+
// orientation.angle() → number
|
|
628
|
+
// orientation.isPortrait() → boolean
|
|
629
|
+
// orientation.isLandscape() → boolean
|
|
630
|
+
// orientation.lock('landscape') → Promise<void>
|
|
631
|
+
// orientation.unlock()
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### injectMutationObserver
|
|
636
|
+
|
|
637
|
+
Accepts the same `ElementInput` type — works with `viewChild` signals.
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
import { injectMutationObserver } from '@angular-helpers/browser-web-apis';
|
|
641
|
+
|
|
642
|
+
@Component({...})
|
|
643
|
+
export class MyComponent {
|
|
644
|
+
readonly targetRef = viewChild<ElementRef>('target');
|
|
645
|
+
readonly mo = injectMutationObserver(this.targetRef, { childList: true });
|
|
646
|
+
|
|
647
|
+
// mo.mutations() → MutationRecord[]
|
|
648
|
+
// mo.mutationCount() → number
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### injectPerformanceObserver
|
|
653
|
+
|
|
654
|
+
```typescript
|
|
655
|
+
import { injectPerformanceObserver } from '@angular-helpers/browser-web-apis';
|
|
656
|
+
|
|
657
|
+
@Component({...})
|
|
658
|
+
export class MyComponent {
|
|
659
|
+
readonly perf = injectPerformanceObserver({ type: 'navigation', buffered: true });
|
|
660
|
+
|
|
661
|
+
// perf.entries() → PerformanceEntryList
|
|
662
|
+
// perf.entryCount() → number
|
|
663
|
+
// perf.latestEntry() → PerformanceEntry | undefined
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### injectIdleDetector
|
|
668
|
+
|
|
669
|
+
```typescript
|
|
670
|
+
import { injectIdleDetector } from '@angular-helpers/browser-web-apis';
|
|
671
|
+
|
|
672
|
+
@Component({...})
|
|
673
|
+
export class MyComponent {
|
|
674
|
+
readonly idle = injectIdleDetector({ threshold: 120_000 });
|
|
675
|
+
|
|
676
|
+
// idle.userState() → 'active' | 'idle'
|
|
677
|
+
// idle.screenState() → 'locked' | 'unlocked'
|
|
678
|
+
// idle.isUserIdle() → boolean
|
|
679
|
+
// idle.isScreenLocked() → boolean
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### injectGamepad
|
|
684
|
+
|
|
685
|
+
```typescript
|
|
686
|
+
import { injectGamepad } from '@angular-helpers/browser-web-apis';
|
|
687
|
+
|
|
688
|
+
@Component({...})
|
|
689
|
+
export class MyComponent {
|
|
690
|
+
readonly gp = injectGamepad(0);
|
|
691
|
+
|
|
692
|
+
// gp.connected() → boolean
|
|
693
|
+
// gp.buttons() → ReadonlyArray<{ pressed: boolean; value: number }>
|
|
694
|
+
// gp.axes() → readonly number[]
|
|
695
|
+
// gp.state() → GamepadState | null
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### ElementInput type
|
|
700
|
+
|
|
701
|
+
Both `injectResizeObserver` and `injectIntersectionObserver` accept the `ElementInput` type:
|
|
702
|
+
|
|
703
|
+
```typescript
|
|
704
|
+
type ElementInput =
|
|
705
|
+
| Element
|
|
706
|
+
| ElementRef<Element>
|
|
707
|
+
| Signal<Element | ElementRef<Element> | undefined>;
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
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.
|
|
711
|
+
|
|
135
712
|
## Browser Support
|
|
136
713
|
|
|
137
714
|
The services automatically validate browser support and unsupported-path handling:
|