خدمات ملحوظة في الزاوي

مرحبا بالجميع ، اسمي فلاديمير. أنا منخرط في تطوير الواجهة الأمامية في Tinkoff.ru.


في Angular ، نحن معتادون على استخدام الخدمات لنقل البيانات داخل تطبيق أو لتغليف منطق الأعمال. يعد RxJS رائعًا لإدارة سلاسل العمليات غير المتزامنة.


تسمح لك الزاوية مع RxJS بالكتابة بأسلوب تعريفي قصير وقصير. ولكن في بعض الأحيان ، نواجه مكتبات أو واجهات برمجة تطبيقات تابعة لجهات خارجية تستخدم عمليات الاستدعاء والوعود ، مما يدفعنا إلى التراجع عن النمط المعتاد والكتابة بشكل حتمي.


الغرض من هذه المقالة هو إظهار مثال من واجهات برمجة التطبيقات المماثلة لكيفية استخدام RxJS يمكن لفها بسهولة في خدمات يمكن ملاحظتها. هذا سوف يساعد على تحقيق قابلية الاستخدام في الزاوي. لنبدأ بواجهة برمجة تطبيقات تحديد الموقع الجغرافي.



API تحديد الموقع الجغرافي


تسمح Geolocation API للمستخدم بتوفير موقعه لتطبيق الويب إذا وافق المستخدم على ذلك. لأسباب تتعلق بالخصوصية ، سيُطلب من المستخدم الحصول على إذن لتقديم معلومات الموقع.

فيما يلي مثال على الاستخدام الأساسي لواجهة برمجة تطبيقات تحديد الموقع الجغرافي الأصلية.


, Geolocation :


if('geolocation' in navigator) {
 /* geolocation is available */ 
} else {
 /* geolocation IS NOT available */ 
}

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[]) {
       // do something with entry
   }
...

open-source-, npm. «».



RxJS Dependency Injection API, . . , — , @ng-web-apis/midi. .


, .


, , Web APIs for Angular. — API Angular-. , , , Payment Request API Intersection Observer, — .


All Articles