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
I solved the problem with ngDoCheck() hook.
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.Another way is to try adding a trackBy function to the
ngFor
so that the application has another way to track the deletions/addition.The trackBy will look like so.