javascriptangulartypescriptinterpolationtwo-way-binding

Angular 12 - display data in an Array using an Observable


Hello Fellow Community,

I am experiencing a problem currently on my angular 12 project :

I would like to display an array named todos which contains all of my object variables. However even tho I can see on the console that my objects are being added to the array : todos, there seems to be a problem (with my observable maybe) because they are not displayed on the html page.

I have 2 components "add-todo" and "todos" as well as 1 service "todoService".

Here is my code :

//todo.service.ts :

 import { Injectable } from '@angular/core';
import {Todo} from '../models/todo.model';
import {Subject} from "rxjs"

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

  todos:Todo[]=[];
  todoSubject=new Subject <Todo[]>()

  constructor() {

    setTimeout(()=>{
    this.todos=[
      {
        firstname:"Youssef"
      },
      {
        firstname:"Yacine"
      },
      {
        firstname:"Ismail"
      },
    ];

    this.emitTodo();
  },3000)
   }

   addTodo(todo:Todo):void{
     this.todos.push(todo);
     this.emitTodo();
     console.log("Dans todoservice :"+this.todos);
   }

   emitTodo():void{
     this.todoSubject.next(this.todos);
   }
}

//todos.component.ts :

 import { Component,OnInit,OnDestroy } from '@angular/core';
import {TodoService} from '../services/todo.service';
import {Todo} from '../models/todo.model';
import {Subscription} from 'rxjs'

@Component({
  selector: 'app-todo',
  templateUrl: './todo.component.html',
  styleUrls: ['./todo.component.css']
})
export class TodoComponent implements OnInit, OnDestroy {

  todos;
  todoSub;

  constructor(private todoService:TodoService){}

  ngOnInit():void{
    this.todoSub=this.todoService.todoSubject.subscribe(
      (value:Todo[])=>{
        this.todos=value;
        console.log("Dans todo value :"+value);
      },
      (error)=>{
        console.log("Erreur "+error)
      },
      ()=>{
        console.log("Observable complete");
      }
    )
  }

  ngOnDestroy():void{
    this.todoSub.unsubscribe();
  }



}

//todos.component.html :

   <div class="form-group" *ngFor="let todo of todos;">

  {{todo|json}}


</div>
<p>todo works!</p>

//and finally,

//add-todo.component.ts :

import { Component, OnInit } from '@angular/core';
import { Todo } from '../models/todo.model';
import {TodoService} from '../services/todo.service';
import {Router} from '@angular/router';


@Component({
  selector: 'app-app-todo',
  templateUrl: './app-todo.component.html',
  styleUrls: ['./app-todo.component.css']
})
export class AppTodoComponent implements OnInit {

  todo=new Todo();

  constructor(private todoService:TodoService, private router:Router) { }

  ngOnInit(): void {
  }

  onSubmit():void{

    this.todoService.addTodo(this.todo);
    console.log("Dans add-todo :"+this.todo);
    this.router.navigate(["todos"]);
  }

}

add-todo.component.html :

<p>{{todo|json}}</p>

<form #addTodoForm="ngForm" (ngSubmit)="onSubmit()">

  <label for="name">Firstname :</label>
  <input type="text" name="firstname" [(ngModel)]="todo.firstname" required>

  <button type="submit"  class="btn btn-success" [disabled]="addTodoForm.invalid">Add Todo</button>
</form>```

This is what it shows when I "ng serve" the code :

[![todos.component.html][1]][1]

Then I add a new todo :

[![add-todo.component.html][2]][2]

Then it redirects to the todos.component.html again but it doesn't display all the todos like before :

[![todos.component.html][3]][3]


  [1]: https://i.sstatic.net/5P1NQ.png
  [2]: https://i.sstatic.net/diIDX.png
  [3]: https://i.sstatic.net/PgKxE.png

Solution

  • I think you need to alter arrays and objects in Angular (or any library or framework) immutably for change detection to take place.

    Objects and arrays are reference types so they store the location of their memory and doing a push on them does not change the location of memory for them.

    Every time you want to change arrays and objects in Angular, do them immutably (assign new values so the location in memory changes and change detection takes place). The maps might not be needed but I have added them so the objects inside of the array have new locations in memory as well.

    addTodo(todo:Todo):void{
      // this.todos.push(todo);
      this.todos = [...this.todos.map(todo => ({ ...todo })), todo];
      this.emitTodo();
      console.log("Dans todoservice :"+this.todos);
    }
    
    emitTodo():void{
      this.todoSubject.next([...this.todos.map(todo => ({ ...todo })]);
    }