3

Hello my fellow developer. I would like just to ask for help for me to be to achieve the swipe delete feature on our Angular project.

Please see the screenshot below.

enter image description here

This is the code that was given to me to work with that feature.

      <div class="col-md-6 col-sm-6 col-xs-12" *ngFor="let relation of bookList" >
        <div class="" (click)="selectBookFor(relation)">
          <div class="">
            <div class="row introMain-wrapper">
              <div class="col-auto">
                <div class="introImg">
                  <img src="{{ relation && relation.image }}"  alt="user" (error)="setDefaultRelationUserPic($event, relation?.gender)">
                </div>
              </div>
              <div class="col">
                <div class="row">
                  <div class="col"><h5 class="card-title text">{{relation?.firstName}} {{relation?.lastName}}</h5></div>
                  <div class="col-auto"><span class="relation-text">{{relation?.relationship}}</span></div>
                </div>
                <h5 class="card-text">{{relation?.email}}</h5>
                <div class="outerdiv-text">
                    <span class="card-text">{{relation?.phone1}}</span>
                </div>
              </div>
            </div>
            <div class="line"></div>
          </div>
        </div>
      </div>

I've tried the following plugin:

  • mat-list-touch
  • swipe-angular-list

but it won't work when importing in the submodule and used in component features.Thanks in advance.

Eduwow
  • 95
  • 1
  • 11

3 Answers3

2

You can use Angular material Drag and Drop https://material.angular.io/cdk/drag-drop/examples

From that, you can create a swipe delete option.

Demo

<div class="container">
  <div class="row">
    <div
      cdkDropList
      cdkDropListSortingDisabled
      [cdkDropListData]="bookList"
      class="example-list"
      (cdkDropListDropped)="drop($event)">
      <div class="example-box" *ngFor="let item of bookList;let i= index" cdkDrag>{{item.firstName}}</div>
    </div>
  </div>
</div>

TS

drop(event: CdkDragDrop<any, any>): any {
    this.bookList.splice(event.currentIndex, 1);
}
  • Unfortunately, I encountering import problems with this approach. – Eduwow Aug 05 '21 at 18:50
  • Here is the error ERROR in ./src/styles.scss (./node_modules/css-loader/dist/cjs.js??ref--14-1!./node_modules/postcss-loader/src??embedded!./node_modules/resolve-url-loader??ref--14-3!./node_modules/sass-loader/dist/cjs.js??ref--14-4!./src/styles.scss) Module build failed (from ./node_modules/sass-loader/dist/cjs.js): SassError: Can't find stylesheet to import. ╷ 4 │ @import '~@angular/material'; │ ^^^^^^^^^^^^^^^^^^^^ ╵ I tried this https://stackoverflow.com/questions/67652012/sasserror-cant-find-stylesheet-to-import-use-angular-material-as-mat but it won't work – Eduwow Aug 06 '21 at 07:51
  • Are you sure that, you install angular material correctly – Chiranjaya Denuwan Aug 06 '21 at 07:55
  • Unfortunately, yes :( – Eduwow Aug 06 '21 at 09:30
1

The events touchstart, touchend and touchmove are supported by angular. So you can imagine a directive like:

export interface TouchEventType {
  element: TouchDirective;
  incrX: number;
  incrY: number;
}

@Directive({
  selector: '[touch]',
  exportAs: 'touch'
})
export class TouchDirective {
  origin: any = { x: 0, y: 0 };
  style: any = null;
  rect: any = { x: 0, y: 0 };
  incrX: number = 0;
  incrY: number = 0;
  @Input('touch') direction: 'horizontal' | 'vertical' | null = null;
  @Output() touchMove: EventEmitter<any> = new EventEmitter<any>();
  @HostListener('touchstart', ['$event']) touchStart(event) {
    this.origin = {
      x: event.touches[0].screenX,
      y: event.touches[0].screenY
    };
  }

  @HostListener('touchmove', ['$event']) touch(event: TouchEvent) {
    this.incrX = this.rect.x + event.touches[0].screenX - this.origin.x;
    this.incrY = this.rect.y + event.touches[0].screenY - this.origin.y;
    this.style =
      this.direction == 'horizontal'
        ? {
            transform: 'translateX(' + this.incrX + 'px)'
          }
        : this.direction == 'vertical'
        ? {
            transform: 'translateY(' + this.incrY + 'px)'
          }
        : {
            transform: 'translateY(' + this.incrX + 'px,' + this.incrY + 'px)'
          };

      if (this.direction)
          window.scrollBy(this.direction=='horizontal'?
                event.touches[0].screenX - this.origin.x:0,
          this.direction=='vertical'?
                event.touches[0].screenY - this.origin.y:0)

  }

  @HostListener('touchend', ['$event']) touchEnd() {
    this.rect = { y: this.incrY, x: this.incrX };
    this.touchMove.emit({
      element: this,
      incrX: this.incrX,
      incrY: this.incrY
    });
  }
  @HostBinding('style') get _() {
    return this.style;
  }
  constructor(private elementRef: ElementRef) {}

  reset() {
    this.style = null;
    this.rect = { x: 0, y: 0 };
  }
}

You can use in an .html like

<ng-container *ngFor="let item of array;let i=index">
  <p touch='horizontal' (touchMove)="touchmove($event,i)">
  Start editing to see some magic happen {{item}}
  </p>
</ng-container>

  array:any[]=[1,2,3,4,5,6,7]

  touchmove(event:TouchEventType,index:number)
  {
    if (event.incrX<-10) //10px to the left
      this.array.splice(index,1)
    else
      event.element.reset()
  }

See the stackblitz without any warranty

Update If we want that work in touched and no touched screen we can take another approach. Instead of use @HostListener we can use fromEvent rxjs operator. (else we need make a hostListenr over mousedown,mouseup and mousemove)

To control at time the touch events and mouse events we use rxjs merge operator, that received the value from the two observables. To use exact the same code, we use "map" in the touch events to convert the respose (a TouchEvent) in a MouseEvent.

The last point is use the "tap" to know if is a touchscreen or not. In a touch screen if we swipe down/up, we need scroll the window dwon/up is oure directive only allow horizontal movements

@Directive({
  selector: '[touch]',
  exportAs: 'touch'
})
export class TouchDirective {
  origin: any = { x: 0, y: 0 };
  style: any = null;
  rect: any = { x: 0, y: 0 };
  incrX: number = 0;
  incrY: number = 0;

  onDrag:boolean=false;    
  isTouched:boolean=false;
  moveSubscription: any;
  downSubscription: any;

  @Input('touch') direction: 'horizontal' | 'vertical' | null = null;
  @Output() touchMove: EventEmitter<any> = new EventEmitter<any>();

  @HostBinding('style') get _() {
    return this.style;
  }
  @HostBinding('class.no-select') get __() {
    return this.onDrag;
  }

  constructor(private elementRef: ElementRef) {}
  ngOnInit() {
    this.downSubscription=merge(
      fromEvent(this.elementRef.nativeElement, 'mousedown').pipe(tap(_=>this.isTouched=false)),
      fromEvent(this.elementRef.nativeElement, 'touchstart').pipe(tap(_=>this.isTouched=true),
        map((event: TouchEvent) => ({
          target: event.target,
          screenX: event.touches[0].screenX,
          screenY: event.touches[0].screenY
        }))
      )
    ).subscribe((event: MouseEvent) => {

      //see that is the same code that @HostListener('touchstart', ['$event'])
      this.origin = {
        x: event.screenX,
        y: event.screenY
      };
      this.onDrag=true;

      merge(fromEvent(document, 'mouseup'), fromEvent(document, 'touchend'))
        .pipe(take(1))
        .subscribe(() => {
      //see that is the same code that @HostListener('touchend', ['$event'])
          if (this.moveSubscription) {
            this.moveSubscription.unsubscribe();
            this.moveSubscription = undefined;
          }
          this.rect = { y: this.incrY, x: this.incrX };
          this.touchMove.emit({
            element: this,
            incrX: this.incrX,
            incrY: this.incrY
          });
          this.onDrag=false;
        });

      if (!this.moveSubscription) {
        this.moveSubscription = merge(
          fromEvent(document, 'mousemove'),
          fromEvent(document, 'touchmove').pipe(
            map((event: TouchEvent) => ({
              target: event.target,
              screenX: event.touches[0].screenX,
              screenY: event.touches[0].screenY
            }))
            ))
            .subscribe((event: MouseEvent) => {
      //see that is the same code that @HostListener('touchmove', ['$event'])

              this.incrX = this.rect.x + event.screenX - this.origin.x;
              this.incrY = this.rect.y + event.screenY - this.origin.y;
              this.style =
                this.direction == 'horizontal'
                  ? {
                      transform: 'translateX(' + this.incrX + 'px)'
                    }
                  : this.direction == 'vertical'
                  ? {
                      transform: 'translateY(' + this.incrY + 'px)'
                    }
                  : {
                      transform: 'translateY(' + this.incrX + 'px,' + this.incrY + 'px)'
                    };
              if (this.direction && this.isTouched)
                window.scrollBy(
                  this.direction == 'horizontal'
                    ? event.screenX - this.origin.x
                    : 0,
                  this.direction == 'vertical'
                    ? event.screenY - this.origin.y
                    : 0
                );
            })
      }
    });

  }
  ngOnDestroy() {
    this.downSubscription.unsubscribe();
  }
  reset() {
    this.style = null;
    this.rect = { x: 0, y: 0 };
  }
}

we add the class

.no-select {
    -webkit-touch-callout: none; /* iOS Safari */
    -webkit-user-select: none; /* Safari */
    -khtml-user-select: none; /* Konqueror HTML */
    -moz-user-select: none; /* Old versions of Firefox */
    -ms-user-select: none; /* Internet Explorer/Edge */
    user-select: none; /* Non-prefixed version, currently
                                  supported by Chrome, Edge, Opera and Firefox */
  }

in styles.css and

  @HostBinding('class.no-select') get __() {
    return this.onDrag;
  }

To not select when the element is swipping

The stackblitz for touch/no touch screen

Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • Hello Eliseo, I've tried your solution but it won't work? Please see this video. https://recordit.co/N5PkHr0w4D then also tried the stackblitz you provided, sadly it won't work. – Eduwow Aug 05 '21 at 17:40
  • @Edward, the directive was only for touched screen, I update the answer with another directive that instead use `@HostListener` use the rxjs operator `fromEvent` – Eliseo Aug 05 '21 at 19:06
  • I've tried the demo in the link you provided and it did work. But as I've copied the sample codes on our solution it has CLI errors. Sample: o overload matches this call. Overload 1 of 5, '(observer?: NextObserver | ErrorObserver | CompletionObserver | undefined): Subscription', gave the following error. Argument of type '(event: MouseEvent) => void' is not assignable to parameter of type 'NextObserver | ErrorObserver | CompletionObserver | under.... – Eduwow Aug 06 '21 at 07:17
  • has you include the directive in the "app.module" (well really in the module where your component is or in a shared.module if you use export)? what rxjs version do you use (I think that works on rxjs 6 and rxjs 7)? – Eliseo Aug 06 '21 at 08:03
  • check the "parenthesis" and "brakes" perfhafs your code has one in "bad position" – Eliseo Aug 08 '21 at 16:43
-1

Good day fellow developers, thank you for all the answers. By the way, I used https://www.npmjs.com/package/swipe-angular-list as a solution to my problem.

Eduwow
  • 95
  • 1
  • 11