Front End

19 mar, 2019

Listas interativas com Angular – Drag and Drop

Publicidade

Na versão 7 do Angular é possível utilizar um aspecto muito interessante para efeitos de interação: o drag and drop, famoso arrasta e solta.

Neste artigo eu mostro como utilizar esse recurso aplicado à uma lista com personagens de um jogo.

Requisitos

Criado o projeto, é necessário incluir as seguintes dependências. No terminal, informe:

$ ng add @angular/material

Aceite a instalação da biblioteca hammerjs e escolha um tema de sua preferência.

Com isso já podemos abrir o módulo principal e definir no array de import o módulo DragDropModule.

Para esse exemplo eu vou utilizar o HttpClientModule para carregar os dados de um arquivo local.

// Outros imports omitidos
'@angular/platform-browser/animations';
import { HttpClientModule } from '@angular/common/http';
import { DragDropModule } from '@angular/cdk/drag-drop';

@NgModule({
 declarations: [
   AppComponent
 ],
 imports: [
   BrowserModule,
   AppRoutingModule,
   BrowserAnimationsModule,
   HttpClientModule,
   DragDropModule

 ],
 providers: [],
 bootstrap: [AppComponent]
})
export class AppModule { }

Na pasta assets vamos criar um arquivo data.json com o seguinte conteúdo:

[
 {
 “name”: “Scorpion”,
 “image”: “https://i.pinimg.com/564x/c4/b2/65/c4b2659a04c7cb1f99c1b2ed738f73d2.jpg",
 “fatality”: “X + L2 + UP”
 },
 {
 “name”: “Subzero”,
 “image”: “https://i.pinimg.com/564x/80/b5/45/80b5458dd048c3457ca54a903de3f7ba.jpg",
 “fatality”: “Up + Down + Up + Kick medium”
 },
 {
 “name”: “Kitana”,
 “image”: “https://i.pinimg.com/originals/4f/cc/cd/4fcccdf78814311d77be47d233db1699.png",
 “fatality”: “X + L2 + UP”
 },
 {
 “name”: “Aang”,
 “image”: “http://rehatron-alpha.eu/images/6321365424_essay-help-forum.png",
 “fatality”: “ L2 + UP”
 }
]

Dessa forma, temos um array de objetos com as propriedades que representam nossos personagens.

Lógica do componente

Abrindo a classe app.component.ts vamos definir uma interface para mapear o personagem com os campos name, image e fatality.

export interface Character {
 name: string;
 image: string;
 fatality: string;
}

Após a assinatura da classe vamos definir dois arrays que representarão nossas listas.

No método construtor fazemos a injeção do HttpClient e chamamos o método getMyList().

A implementação é basicamente um get tipando o resultado para nossa interface <Character[ ]>, informando o caminho da fonte de dados “assets/data.json”.

Ficamos ouvindo a emissão de eventos no subscribe, passando a lista para o mylist.

A próxima função, drop é responsável por duas ações.

Alterar a ordem dos elementos, utilizando a função movieItemInArray e/ou, mudar o item (no caso, os personagens) para outra lista. Para isso utilizamos a função transferArrayItem.

A implementação deve verificar antes, qual a origem (container) do item, através do comando:

if (event.previousContainer === event.container) {

Segue o código completo da classe.

import { Component } from '@angular/core';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import { HttpClient } from '@angular/common/http';

export interface Character {
  name: string;
  image: string;
  fatality: string;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  myList: Character[]
  confirmList: Character[] = [];

  constructor(private httpClient: HttpClient) {
    this.getMyList()
  }

  getMyList() {
     this.httpClient.get<Character[]>("assets/data.json")
     .subscribe(list =>{
      this.myList = list;
     })
  }

  drop(event: CdkDragDrop<Character[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {

      transferArrayItem(event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex);
    }
  }

}

Template do componente

Vamos codifcar o template. Edite o app.component.html e deixe assim:

<div class="container mx-auto p-2">
  <div class="row p-2">
    <div class="col-lg-12">
      <h1>Mortal Kombat 11</h1>
    </div>
  </div>
  <div class="row">
    <div cdkDropList #minha="cdkDropList" [cdkDropListConnectedTo]="[confirmados]" [cdkDropListData]="myList"
      class="col-lg-6" (cdkDropListDropped)="drop($event)">
      <ul class="list-group">
        <li class="list-group-item list-group-item-danger">
          <h3>Minha Lista</h3>
        </li>
        <li class="list-group-item" *ngFor="let p of myList " cdkDrag>{{p.name}}</li>
      </ul>
    </div>
    <div cdkDropList #confirmados="cdkDropList" [cdkDropListData]="confirmList" [cdkDropListConnectedTo]="[minha]"
      (cdkDropListDropped)="drop($event)" class="col-lg-6">
      <ul class="list-group">
        <li class="list-group-item active">
          <h3>Confirmados</h3>
        </li>
        <li class="list-group-item" *ngFor="let p of confirmList" cdkDrag>
          <img [src]="p.image" class="img-person">
          {{p.name}}</li>
      </ul>
    </div>
  </div>
</div>

As linhas 8 e 17 são os locais onde definimos o container, utilizando a diretiva cdkDropList. Ainda na linha 8 criamos um atributo #minha e atribuímos a diretiva.

Com isso, podemos apontar para qual lista podemos arrastar os personagens, utilizando o atributo [cdkDropListConnectedTo].

Definimos a fonte de dados com [cdkDropListData] e finalmente a função (cdkDropListDropped)=”drop($event)”, responsável pelas ações definidas anteriormente.

Na linha 14 realizamos a iteração da lista com o *ngFor. Importante: aqui também declaramos a diretiva cdkDrag, indicando o elemento que pode ser arrastado.

Note que, a partir da linha 17, codificamos a outra lista, alterando o nome e a fonte.

No arquivo de estilos, codificamos duas animações para alterar a ordem e para soltar o personagem.

.cdk-drop-list-dragging .cdk-drag {

transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);

}

.cdk-drag-animating {

transition: transform 300ms cubic-bezier(0, 0, 0.2, 1);

}

.img-person {

border-radius: 35%;

width: 80px;

}

Fácil, né?

Até mais!