skip to Main Content

I’ve two components getting

todos.services.ts:

import { Injectable } from '@angular/core';
import { TodoItem } from '../interfaces/todo-item';

@Injectable({
  providedIn: 'root',
})
export class TodosService {
  protected todoList: TodoItem[] = [
    {
      id: 1,
      title: 'Купить хлеба',
      description: 'Сходить и купить немного хлеба, а то жена заклевала уже.',
      isComplete: true,
    },
    {
      id: 2,
      title: 'Купить селедки',
      description: 'Сходить и купить селедочку, а то закусывать надо.',
      isComplete: false,
    },
    {
      id: 3,
      title: 'Купить пива',
      description: 'Просто купить холодненького и освежающего.',
      isComplete: false,
    },
    {
      id: 4,
      title: 'Связать носки',
      description: 'Да, надо готовиться к зиме.',
      isComplete: false,
    },
    {
      id: 5,
      title: 'Погладить кота',
      description: 'Ну а почему бы и нет?!.',
      isComplete: false,
    },
    {
      id: 6,
      title: 'Засунуть руку в унитаз',
      description: 'Зачем и нет?!.',
      isComplete: false,
    },
  ];

  constructor() {
    // this.todoList = JSON.parse(localStorage.getItem('todoList'));
  }

  getAllTodos(): TodoItem[] {
    return this.todoList;
  }

  deleteTodo(id: number): void {
    const todoItemIndex = this.todoList.findIndex((todo) => todo.id === id);
    this.todoList.splice(todoItemIndex, 1);
  }

  completeTodo(id: number) {
    this.todoList = this.todoList.map((todo) => {
      if (todo.id === id) {
        todo.isComplete = !todo.isComplete;
        return todo;
      }

      return todo;
    });
  }

  createTodo(todo: TodoItem): void {
    this.todoList.push(todo);
  }

  updateTodo(id: number) {}
}

todo-item.component.html

<li
  class="flex flex-row border rounded-lg p-3 cursor-pointer my-3 {{
    todoItem.isComplete ? 'isComplete' : ''
  }}"
  (click)="toggleComplete(todoItem.id)"
>
  <div class="flex flex-col w-3/4">
    <h3>{{ todoItem.title }}</h3>
    <p class="text-xs text-gray-400">
      {{ todoItem.description }}
    </p>
  </div>
  <div class="flex flex-col justify-center w-1/4">
    <button
      class="flex items-center justify-center rounded-lg text-white bg-blue-700 px-3 py-2 h-1/2"
      (click)="editTodo(todoItem.id)"
    >
      Edit task
    </button>
    <button
      (click)="deleteTodo(todoItem.id)"
      class="flex items-center justify-center rounded-lg text-white bg-red-700 px-3 py-2 h-1/2 mt-3"
    >
      {{ todoItem.id }}
      Delete task
    </button>
  </div>
</li>

todo-item.component.ts

import { Component, inject, Input } from '@angular/core';
import { TodoItem } from '../interfaces/todo-item';
import { TodosService } from '../services/todos.service';

@Component({
  selector: 'app-todo-item',
  standalone: true,
  imports: [],
  templateUrl: './todo-item.component.html',
  styleUrl: './todo-item.component.css',
})
export class TodoItemComponent {
  @Input() todoItem!: TodoItem;

  todosService: TodosService = inject(TodosService);

  deleteTodo(id: number): void {
    this.todosService.deleteTodo(id);
  }

  toggleComplete(id: number): void {
    this.todosService.completeTodo(id);
  }

  editTodo(id: number): void {
    this.todosService.updateTodo(id);
  }
}

So the first click works well. It deletes todo and rerender a component. The next clicks only delete items from array, but no rerender is happening. Why?

I tried to do this.todoList = this.todoList.filter(todo => todo.id !== id) function. It also only update array state, but no rerender is happening. What I do wrong?

2

Answers


  1. Chosen as BEST ANSWER

    I solved the problem with ngDoCheck() hook.


  2. When you modify the internal properties of an array, the actual memory reference to the array does not change. So ngFor has no way to know if the changes have happened.

    You can have two fixes here.

    After doing any modifications or deletion, just use array destructuring to create a shallow copy of the array, so that the ngFor knows to refresh the view.

      getAllTodos(): TodoItem[] {
        return this.todoList;
      }
    
      deleteTodo(id: number): void {
        const todoItemIndex = this.todoList.findIndex((todo) => todo.id === id);
        this.todoList.splice(todoItemIndex, 1);
        this.todoList = [ ...this.todoList ]; // <- changed here!
      }
    
      completeTodo(id: number) {
        this.todoList = this.todoList.map((todo) => {
          if (todo.id === id) {
            todo.isComplete = !todo.isComplete;
            return todo;
          }
    
          return todo;
        });
        this.todoList = [ ...this.todoList ]; // <- changed here!
      }
    
      createTodo(todo: TodoItem): void {
        this.todoList.push(todo);
        this.todoList = [ ...this.todoList ]; // <- changed here!
      }
    
      updateTodo(id: number) {}
    

    Another way is to try adding a trackBy function to the ngFor so that the application has another way to track the deletions/addition.

    <div *ngFor="let item of list; trackBy:identify">
        <!-- TODO code here -->
    </div>
    

    The trackBy will look like so.

      identify(index, item){
         return item.id; // give a unique field to track.
      }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search