具有ChangeDetectionStrategy.OnPush的组件的runOutsideAngular问题

时间:2019-08-27 22:00:30

标签: angular angular-directive angular2-changedetection angular-changedetection ngzone

我正在使用以下指令在Angular应用中向左滑动和向右滑动手势。

import { Directive, ElementRef, EventEmitter, NgZone, OnDestroy, OnInit, Output, Input } from '@angular/core';
import { fromEvent, Observable } from 'rxjs';
import { elementAt, filter, map, switchMap, take, takeUntil, takeWhile, tap } from 'rxjs/operators';
import { SwipeCoordinates, SwipeDirection, SwipeEvent } from './interfaces';

@Directive({
  selector: '[swipe]'
})

export class SwipeDirective implements OnInit, OnDestroy {

  @Input() swipe: boolean;

  @Output() swipeLeft: EventEmitter<true> = new EventEmitter<true>();
  @Output() swipeRight: EventEmitter<true> = new EventEmitter<true>();

  //property used to unsubscribe from all subscriptions on destroy event
  private alive = true;
  private horizontalMovesSub: any;

  constructor(
    private elementRef: ElementRef,
    private zone: NgZone
  ) { }

  ngOnInit() {
    if (this.swipe) {
      const domElement = this.elementRef.nativeElement;

      const touchStarts: Observable<SwipeCoordinates> = fromEvent(domElement, 'touchstart').pipe(map(this.touchEventToCoordinate));
      const touchMoves: Observable<SwipeCoordinates> = fromEvent(domElement, 'touchmove').pipe(map(this.touchEventToCoordinate));
      const touchEnds: Observable<SwipeCoordinates> = fromEvent(domElement, 'touchend').pipe(map(this.touchEventToCoordinate));


      //Move starts with direction: Pair the move start events with the 3rd subsequent move event, but only if no touch end event happens in between.
      const moveStartsWithDirection = touchStarts.pipe(
        switchMap((dragStartEvent: SwipeCoordinates) => touchMoves.pipe(
          elementAt(3),
          map((dragEvent: SwipeCoordinates) => {
            const intialDeltaX = dragEvent.x - dragStartEvent.x;
            const initialDeltaY = dragEvent.y - dragStartEvent.y;
            //this.swipe(dragEvent, 'start');
            return { x: dragStartEvent.x, y: dragStartEvent.y, intialDeltaX, initialDeltaY };
          })
        )));


      //Horizontal move starts: Keep only those move start events where the 3rd subsequent move event is rather horizontal than vertical
      const horizontalMoveStarts = moveStartsWithDirection.pipe(
        filter(dragStartEvent => Math.abs(dragStartEvent.intialDeltaX) >= Math.abs(dragStartEvent.initialDeltaY))
      );


      //Take the moves until touch ends On move end emit swipe end event to parent element
      const movesUntilEnds = (dragStartEvent: any, direction: SwipeDirection) => touchMoves.pipe(
        map(dragEvent => this.getSwipeDistance(dragStartEvent, dragEvent)),
        takeUntil(touchEnds.pipe(
          take(1),
          map(dragEndEvent => this.getSwipeDistance(dragStartEvent, dragEndEvent)),
          tap((coordinates: SwipeCoordinates) => this.emitSwipeEndEvent(direction, coordinates))
        )));

      //const verticalMoves = verticalMoveStarts.pipe(
      //  switchMap(dragStartEvent => movesUntilEnds(dragStartEvent, 'y'))
      //);

      const horizontalMoves = horizontalMoveStarts.pipe(
        switchMap(dragStartEvent => movesUntilEnds(dragStartEvent, 'x'))
      );

      //Run swipe subscriptions outside zone for better performance On move emit swipe move event to parent element
      this.zone.runOutsideAngular(() => {
        this.horizontalMovesSub = horizontalMoves.pipe(
          takeWhile(() => this.alive)
        ).subscribe(() => { });
      });
    }
  }

  //Format touch event to coordinates object that is easier to read
  public touchEventToCoordinate(touchEvent: TouchEvent): SwipeCoordinates {
    return {
      x: touchEvent.changedTouches[0].clientX,
      y: touchEvent.changedTouches[0].clientY
    };
  }

  private getSwipeDistance(dragStartEvent, dragEvent): SwipeCoordinates {
    return {
      x: dragEvent.x - dragStartEvent.x,
      y: dragEvent.y - dragStartEvent.y
    };
  }


  //Emits swipe event
  private emitSwipeEndEvent(direction: SwipeDirection, coordinates: SwipeCoordinates) {

    if (direction == 'x') {

      let longEnough = Math.abs(coordinates.x) > 50;

      if (longEnough) {
        if (coordinates.x < 0) {
          this.swipeLeft.emit(true);
        } else {
          this.swipeRight.emit(true);
        }
      }
    }
  }

  //Set alive property to false to unsubscribe from all subscriptions
  ngOnDestroy() {
    this.alive = false;

    if (this.horizontalMovesSub) {
      this.horizontalMovesSub.unsubscribe();
    }
  }
}

然后在这样的组件中使用

<div [swipe]="canNavigate" (swipeLeft)="navigate()" (swipeRight)="navigate(true)">
<my-onpush-component>
</my-onpush-component>
</div>

问题是当在Swipe指令中我在runOutsideAngular中使用Swipe订阅时,my-onpush-component上的更改检测未在swipe事件上运行。即使删除ChangeDetectionStrategy.OnPush,my-onpush组件也不会更新。

this.zone.runOutsideAngular(() => {
            this.horizontalMovesSub = horizontalMoves.pipe(
              takeWhile(() => this.alive)
            ).subscribe(() => { });
          });

但是如果我从Swipe指令中删除this.zone.runOutsideAngular(() => {,一切正常。

有人可以指导解决此问题吗?

0 个答案:

没有答案