大家好,我叫弗拉基米尔。我在Tinkoff.ru中从事前端开发。
在Angular中,我们习惯于使用服务在应用程序内部传输数据或封装业务逻辑。RxJS非常适合管理异步线程。
Angular与RxJS的结合使您可以以简洁明了的声明风格进行编写。但是有时我们会遇到使用回调,promise的第三方库或API,从而迫使我们放弃通常的样式并强制性地编写。
本文的目的是通过类似的API的示例展示如何使用RxJS将它们轻松地包装在Observable服务中。这将有助于实现Angular的可用性。让我们从Geolocation API开始。

地理位置API
如果用户同意,则Geolocation API允许用户向Web应用程序提供其位置。出于隐私原因,将要求用户允许提供位置信息。
以下是本地Geolocation API的基本用法示例。
, Geolocation :
if('geolocation' in navigator) {
} else {
}
getCurrentPosition(), . watchPosition(), , . , .
...
success(position) {
doSomething(position.coords.latitude, position.coords.longitude);
}
error() {
alert('Sorry, no position available.');
}
watch() {
this.watchID = navigator.geolocation.watchPosition(this.success, this.error);
}
stopWatch() {
navigator.geolocation.clearWatch(this.watchID);
}
...
- , clearWatch() id . . . , RxJS Observable.
Geolocation, . . NAVIGATOR @ng-web-apis/common.
export const GEOLOCATION = new InjectionToken<Geolocation>(
'An abstraction over window.navigator.geolocation object',
{
factory: () => inject(NAVIGATOR).geolocation,
},
);
, Geolocation API:
export const GEOLOCATION_SUPPORT = new InjectionToken<boolean>(
'Is Geolocation API supported?',
{
factory: () => !!inject(GEOLOCATION),
},
);
PositionOptions:
import {InjectionToken} from '@angular/core';
export const POSITION_OPTIONS = new InjectionToken<PositionOptions>(
'Token for an additional position options',
{factory: () => ({})},
);
! Observable .
Observable, , , . , . Subscriber, , next(), complete() error(). , :
@Injectable({
providedIn: 'root',
})
export class GeolocationService extends Observable<Position> {
constructor(
@Inject(GEOLOCATION) geolocationRef: Geolocation) {
super(subscriber => {
geolocationRef.watchPosition(
position => subscriber.next(position),
positionError => subscriber.error(positionError),
);
});
}
}
:
@Injectable({
providedIn: 'root',
})
export class GeolocationService extends Observable<Position> {
constructor(
@Inject(GEOLOCATION) geolocationRef: Geolocation,
@Inject(GEOLOCATION_SUPPORT) geolocationSupported: boolean,
@Inject(POSITION_OPTIONS) positionOptions: PositionOptions,
) {
super(subscriber => {
if (!geolocationSupported) {
subscriber.error('Geolocation is not supported in your browser');
}
geolocationRef.watchPosition(
position => subscriber.next(position),
positionError => subscriber.error(positionError),
positionOptions,
);
})
}
}
! Observable, pipe(). RxJS- . , , RxJS- shareReplay().
share()? , Geolocation API getCurrentPosition() watchPosition() Timeout expired. shareReplay() , , . {bufferSize: 1, refCount: true}, . clearWatch()-, , RxJS- finalize():
@Injectable({
providedIn: 'root',
})
export class GeolocationService extends Observable<Position> {
constructor(
@Inject(GEOLOCATION) geolocationRef: Geolocation,
@Inject(GEOLOCATION_SUPPORT) geolocationSupported: boolean,
@Inject(POSITION_OPTIONS)
positionOptions: PositionOptions,
) {
let watchPositionId = 0;
super(subscriber => {
if (!geolocationSupported) {
subscriber.error('Geolocation is not supported in your browser');
}
watchPositionId = geolocationRef.watchPosition(
position => subscriber.next(position),
positionError => subscriber.error(positionError),
positionOptions,
);
});
return this.pipe(
finalize(() => geolocationRef.clearWatch(watchPositionId)),
shareReplay({bufferSize: 1, refCount: true}),
);
}
}
! , :
...
constructor(@Inject(GeolocationService) private readonly position$: Observable<Position>) {}
...
position$.pipe(take(1)).subscribe(position => doSomethingWithPosition(position));
...
, async pipe. @angular/google-maps .
<app-map
[position]="position$ | async"
></app-map>
RxJS .
Geolocation API. . npm.
ResizeObserver API , .
ResizeObserver API
API Resize Observer , .
Geolocation API, ResizeObserver API . Observable-. , . , , :
@Injectable()
export class ResizeObserverService extends Observable<
ReadonlyArray<ResizeObserverEntry>
> {
constructor(
@Inject(ElementRef) {nativeElement}: ElementRef<Element>,
@Inject(NgZone) ngZone: NgZone,
@Inject(RESIZE_OBSERVER_SUPPORT) support: boolean,
@Inject(RESIZE_OPTION_BOX) box: ResizeObserverOptions['box'],
) {
let observer: ResizeObserver;
super(subscriber => {
if (!support) {
subscriber.error('ResizeObserver is not supported in your browser');
}
observer = new ResizeObserver(entries => {
ngZone.run(() => {
subscriber.next(entries);
});
});
observer.observe(nativeElement, {box});
});
return this.pipe(
finalize(() => observer.disconnect()),
share(),
);
}
}
ResizeObserver. next() ngZone() .
Geolocation- , ElementRef. , observe(). . Dependency Injection , .
, . . Output(), :
@Directive({
selector: '[waResizeObserver]',
providers: [
ResizeObserverService,
{
provide: RESIZE_OPTION_BOX,
deps: [[new Attribute('waResizeBox')]],
useFactory: boxFactory,
},
],
})
export class ResizeObserverDirective {
@Output()
readonly waResizeObserver: Observable<ResizeObserverEntry[]>;
constructor(
@Inject(ResizeObserverService)
entries$: Observable<ResizeObserverEntry[]>
) {
this.waResizeObserver = entries$;
}
}
, RESIZE_OPTION_BOX . ReziseObserver .
. , :
<div
waResizeBox="content-box"
(waResizeObserver)="onResize($event)"
>
Resizable box
</div>
...
onResize(entry: ResizeObserverEntry[]) {
}
...
open-source-, npm. «».
RxJS Dependency Injection API, . . , — , @ng-web-apis/midi. .
, .
, , Web APIs for Angular. — API Angular-. , , , Payment Request API Intersection Observer, — .