codediodeio / angular-firestarter

🍱 :fire: Angular + Firebase Progressive Web App Starter
https://firestarter.fireship.io/
956 stars 437 forks source link

[Enhancement] Allow tasks to be moved between boards (Real Kanban style) #94

Open kjuulh opened 3 years ago

kjuulh commented 3 years ago

I just completed the course, and I wondered why I couldn't move tasks between boards, Real Trello style. As such, I've included some code that makes just that possible.

All in all, there is probably only 15-20 lines of actual code changes for it to work. And I believe it is worth it =D.

// boards.component.ts
...

@Output() taskMoved = new EventEmitter<{ previous: string; next: string }>();

  constructor(private boardService: BoardService, private dialog: MatDialog) {}

  ngOnInit(): void {}

  taskDrop(event: any) {
    if (event.previousContainer === event.container) {
      moveItemInArray(
        this.board.tasks,
        event.previousIndex,
        event.currentIndex
      );
      this.boardService.updateTasks(this.board.id, this.board.tasks);
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex
      );

      this.taskMoved.emit({
        previous: event.previousContainer.id,
        next: event.container.id,
      });
    }
  }

...

For this to work, the containerIds, have to have proper ids. Otherwise it wont work.

<!-- board.component.html -->
...

<div class="tasks">
    <div
      class="collection"
      cdkDropList
      [id]="board.id"
      [cdkDropListData]="board.tasks"
      (cdkDropListDropped)="taskDrop($event)"
    >
      <div
        class="inner-card"
        cdkDrag
        *ngFor="let task of board.tasks; let i = index"
        (click)="openDialog(task, i)"
      >
        <mat-card [ngClass]="task.label">{{ task.description }}</mat-card>
      </div>
    </div>

...

The board list now needs a new handler for the array move

// board-list.component.ts
...

taskMoved({ previous, next }) {
    const previousTasks = this.boards.find((b) => b.id === previous).tasks;
    const nextTasks = this.boards.find((b) => b.id === next).tasks;

    this.boardService.moveTask(previous, previousTasks, next, nextTasks);
  }

This will allow, the board list to make the actual commit.

Add the handler to the html

<!-- board-list.component.html -->
...

<div
  cdkDropList
  cdkDropListOrientation="horizontal"
  class="boards"
  (cdkDropListDropped)="drop($event)"
>
<div class="container" cdkDropListGroup>
    <angular-board
      cdkDrag
      *ngFor="let board of boards"
      [board]="board"
      (taskMoved)="taskMoved($event)"
    >
      <mat-icon cdkDragHandle class="handle">drag_indicator</mat-icon>
    </angular-board>

    <div class="board-button">
      <button
        mat-raised-button
        color="accent"
        cdkDragDisabled
        (click)="openBoardDialog()"
      >
        New Board
      </button>
    </div>
  </div>

...

Now to add the last task to move the item in the db.

// board.service.ts
...

moveTask(
    previousBoardId: string,
    previousTasks: Task[],
    boardId: string,
    tasks: Task[]
  ) {
    const db = firebase.firestore();
    const batch = db.batch();
    const previousRef = db.collection('boards').doc(previousBoardId);
    const nextRef = db.collection('boards').doc(boardId);
    batch.update(previousRef, { tasks: previousTasks });
    batch.update(nextRef, { tasks: tasks });
    batch.commit().catch((err) => console.error(err));
  }

All in all there was a fairly minimal amount of code for it to work. And I think it enhances the appeal of the application quite a bit, even if it is just a small project.

I haven't included the changes to the styles. But if people want it to work, then the only thing that needs to change is to take into account that there is another layer in the div in both files.

Like this.

// board.component.scss
.tasks .collection.cdk-drop
1fxe commented 3 years ago

Thanks this is really cool! For the css it the class "container" in board-list.component.html makes the boards display vertically, if you want it horizontal again you can add (not sure if it was just my code)

// boards-list.component.css
.container {
  display: flex;
  flex-direction: row;
}
kjuulh commented 3 years ago

Thanks this is really cool! For the css it the class "container" in board-list.component.html makes the boards display vertically, if you want it horizontal again you can add (not sure if it was just my code)

// boards-list.component.css
.container {
  display: flex;
  flex-direction: row;
}

Yes, as there is an extra div. The flexbox will only apply to the top level div, as such, it becomes vertical. You are correct that it should be moved down into the div.container.

Actually you could just do the following

// board-list.component.scss original
.boards {
  width: auto;
  padding: 24px;
  display: flex;
  flex-direction: row;
  overflow-x: scroll;

...

to

// board-list.component.scss new
.boards .container {
  width: auto;
  padding: 24px;
  display: flex;
  flex-direction: row;
  overflow-x: scroll;

...