Darshit
Darshit

Reputation: 85

Angular deleting all completed not working without refresh

I am new to angular and creating a CRUD todos app, in which todos get stored to localStorage. Adding, updating, deleting, getting all working but if i delete all completed todos it works only on refresh that means on clicking it deletes from localStorage but it wont get deleted from the screen

Here is my project files

app.component.html

<div class="container">
  <div class="todo-wrapper">
    <app-todo-input></app-todo-input>
    <div *ngFor="let todo of allTodos">
        <app-todo-item [todo]="todo"></app-todo-item>
    </div>
    <app-todo-footer [style.display]="allTodos.length <= 0 ? 'none': 'inline'"></app-todo-footer>
  </div>
</div>

app.component.ts

import { Component } from '@angular/core';
import { TodoService } from './todo.service';
import { StorageService } from './storage.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
    public allTodos = [];
    constructor(private todoService: TodoService, private storageService: StorageService){
    };

    ngOnInit(){
        this.todoService.getTodos().subscribe(todos => this.allTodos = todos);
        // this.storageService.getTodos().subscribe(todos => this.allTodos = todos);
    }
}

todo.ts

export class Todo {
    id: number;
    text: string;
    completed: boolean;

    constructor(id: number, text: string, completed: boolean){
        this.id = id;
        this.text = text;
        this.completed = completed;
    }
}

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {MatButtonModule, MatCheckboxModule} from '@angular/material';

import { AppComponent } from './app.component';
import { TodoInputComponent } from './todo-input/todo-input.component';
import { TodoService } from './todo.service';
import { StorageService } from './storage.service';
import { TodoItemComponent } from './todo-item/todo-item.component';
import { TodoFooterComponent } from './todo-footer/todo-footer.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [
    AppComponent,
    TodoInputComponent,
    TodoItemComponent,
    TodoFooterComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    BrowserAnimationsModule,
    MatButtonModule,
    MatCheckboxModule,
    AppRoutingModule
  ],
  providers: [
    TodoService,
    StorageService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

storage.service.ts

import { Injectable } from '@angular/core';
import { Todo } from './todo';
import { Observable, of, BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class StorageService {

  constructor() { }

  public setTodos(todos: Todo[]): void {
    localStorage.setItem('todos', JSON.stringify({ todos: todos }))
  }

  public getTodos(): Observable<Todo[]>{
    let localStorageItem = JSON.parse(localStorage.getItem('todos'));
    if(localStorageItem == null){
        return of([]);
    }else{
        return of(localStorageItem.todos);
    }
  }
}

todo.service.ts

import { Injectable, Input } from '@angular/core';
import { StorageService } from './storage.service';
import { Observable, of, BehaviorSubject } from 'rxjs';

import { Todo } from './todo';

@Injectable({
  providedIn: 'root'
})
export class TodoService {

  @Input()
  private todo: Todo;

    public allTodos: Todo[] = [];
    private nextId: number; 

  constructor(private storageService: StorageService) {
    this.loadTodos();
  }  

  public addTodo(text: string) : void{
    let todos = this.allTodos;
    if (todos.length == 0) { 
      this.nextId = 0;
    } else {
      let maxId = todos[todos.length - 1].id;
      this.nextId = maxId + 1;
    }
    let todo = new Todo(this.nextId, text, false);
    todos.push(todo);
    this.storageService.setTodos(todos);
    this.nextId++;
    // this.lengthTodos();
  }

  // public getTodos() {
  //    return this.allTodos;
  // }

  public loadTodos (){
    return this.storageService.getTodos().subscribe(todos => this.allTodos = todos);
  }

  public getTodos(): Observable<Todo[]>{
    return of(this.allTodos)
  }

  public removeTodo(selectedTodo): void{
    let todos = this.allTodos;
    todos.splice(todos.findIndex((todo) => todo.id == selectedTodo), 1);
    this.storageService.setTodos(todos);
  }

  public deleteCompleted(){
    let todos = this.allTodos;
    let completedTodos = todos.filter(todo => todo.completed !== true);
    todos = completedTodos;
    this.storageService.setTodos(todos);
  }

  public update(id, newValue){
    let todos = this.allTodos;
    let todoToUpdate = todos.find((todo) => todo.id == id);
    todoToUpdate.text = newValue;
    this.storageService.setTodos(todos);
  } 

  public isCompleted(id: number, completed: boolean){
    let todos = this.allTodos;
    let todoToComplete = todos.find((todo) => todo.id == id);
    todoToComplete.completed = !todoToComplete.completed;
    this.storageService.setTodos(todos);
  }

  // public lengthTodos(){
  //   let todos = this.storageService.getTodos();
  //   let activeTodos = todos.filter((todo) => !todo.completed).length;
  //   return activeTodos;
  // }
}

todo-footer.component.ts

    import { Component, OnInit, Input } from '@angular/core';
    import { TodoService } from '../todo.service';
    import { Todo } from '../todo';

    @Component({
      selector: 'app-todo-footer',
      templateUrl: './todo-footer.component.html',
      styleUrls: ['./todo-footer.component.css']
    })
    export cl

ass TodoFooterComponent implements OnInit {

  public allTodos;

    private activeTasks : number = 0;

  constructor(private todoService: TodoService) {
    this.todoService.getTodos().subscribe(todos => this.allTodos = todos);
  }

  public getLength(){
    // this.activeTasks = this.todoService.lengthTodos();
  }

  private clearCompleted(){
    this.todoService.deleteCompleted(); 
  }

  ngOnInit() {
    // this.getLength();
  }

}

todo-footer.component.html

<footer class="footer">

    <button class="clear-completed" (click)="clearCompleted()">Clear completed</button> 
</footer>

In todo.service deleteCompleted method is not working that used by todo-footer.component in deleting all todos that are completed==true.

Upvotes: 0

Views: 167

Answers (1)

user184994
user184994

Reputation: 18271

You've got an Observable here, but you're not actually using it. Instead, you're holding a single instance of the array in multiple components, and relying on that instance being modified, however filter doesn't modify the instance in memory: it returns a new instance.

Create a BehaviourSubject, like so:

private _todos = new BehaviorSubject<Todo[]>(null);

We can use this to update the Observable value as needed.

Change the getTodos function like so:

public getTodos(): Observable<Todo[]>{
  return this._todos.asObservable();
}

Now, any time you change the value of the todos, you should call next on the BehaviourSubject. For example:

public deleteCompleted(){
  let todos = this.allTodos;
  let completedTodos = todos.filter(todo => todo.completed === false);
  this.allTodos = completedTodos;
  this.storageService.setTodos(this.allTodos);
  // Emit the new value from the Observable
  this._todos.next(this.allTodos);
}

Here is a fork of your Stackblitz to show it working

Upvotes: 2

Related Questions