Lucifer Morningstar
Lucifer Morningstar

Reputation: 125

Angular: Subscribe not detecting changes therefore my view not getting updated

I have two components, one of them is a child route inside a of the main component html.

Whenever I select checkboxes of categories in the main component, I am assigning them to a variable(string array) in service. And in the child route, I am subscribing to an observable of this service variable. My goal is to update the child route's view to display the books of those categories only.

So shouldn't I be seeing the logs as and when the value of service variable gets changed (Because I have given console.log() in child component)? Doesn't subscribe get updated value everytime the value changes? And finally, does subscribe call the ngOnChanges() method? (To update the view)

My Code:- Parent Component.html:-

  <div class="ui-g-12">
    <p-checkbox
      name="categories"
      value="Science Fiction"
      label="Science Fiction"
      [(ngModel)]="selectedCategories"
      (onChange)="categoryFilterSelect()"
    ></p-checkbox>
  </div>
  <div class="ui-g-12">
    <p-checkbox
      name="categories"
      value="Horror"
      label="Horror"
      [(ngModel)]="selectedCategories"
      (onChange)="categoryFilterSelect()"
    ></p-checkbox>
  </div>
  <div class="ui-g-12">
    <p-checkbox
      name="categories"
      value="Suspense"
      label="Suspense"
      [(ngModel)]="selectedCategories"
      (onChange)="categoryFilterSelect()"
    ></p-checkbox>
  </div>
  <div class="ui-g-12">
    <p-checkbox
      name="categories"
      value="Romance"
      label="Romance"
      [(ngModel)]="selectedCategories"
      (onChange)="categoryFilterSelect()"
    ></p-checkbox>
  </div>
  <div class="ui-g-12">
    <p-checkbox
      name="categories"
      value="Action"
      label="Action"
      [(ngModel)]="selectedCategories"
      (onChange)="categoryFilterSelect()"
    ></p-checkbox>
  </div>
  <div style="margin-top: 10%">
    <router-outlet></router-outlet>
  </div>

Parent Component.ts:-

selectedCategories: string[] = [];
 //...

 categoryFilterSelect(){
    this.userService._filterByGenre = this.selectedCategories;
  }

second component's ts:-(routed into of Parent Component.html)

export class HomeComponent implements OnInit {
  allBooks: Book[] = [];
  selectedCategories: string[];
  filteredBooks: Book[];

  constructor(private userService: UserService) {}

  ngOnInit() {
    //initially display all books
    this.userService.getAllBooks().subscribe(data => {
      this.allBooks = data;
      this.filteredBooks = this.allBooks;
    });


    //This part not working
    this.userService.filterByGenre.subscribe(data => {
      this.selectedCategories = data;

      //this line not working
      console.log(this.selectedCategories);


      //perform filter logic here and update the filteredBooks array based 
      // onselected categories
    }
    );
  }
}

second component.html

<div class="container">
  <div class="child" *ngFor="let book of filteredBooks">
....
  </div>
</div>

service.ts

export class UserService {

  _filterByGenre: string[] = [];
  public get filterByGenre(): Observable<string[]> {
    return of(this._filterByGenre);
  }
}

console output:- [] (Because initially no categories are selected so this is logged).

My Git Repo:- https://github.com/Lucifer-77/ATL

Upvotes: 0

Views: 10782

Answers (2)

Eliseo
Eliseo

Reputation: 58099

@Lucifer, the things don't work as you think. Angular is not magical. Don't worry about it, it's a common mistake at first. When you subscribe to an Observable, Angular not put a "big brother" to see what happens to the data. e.g. when we make a subscription of httpClient.get(url) that call to a dbs, any change of dbs Angular don't take account of this. (it can not do it).

So you need create a service that say that Angular take account the changes: you need make use of Subject of similar. Let me show a service that work

  private _filterByGenre:string[];

  private filterByGenreSource = new Subject<any>();
  filterByGenreObservable=this.filterByGenreSource.asObservable();

  get filterByGenre()
  {
    return this._filterByGenre
  }
  set filterByGenre(value)  //when we use dataService.filterBtGenre=...
  {
    this._filterByGenre=value;           //equal to the private variable
    this.filterByGenreSource.next(value) //say to Angular that something change
  }

You can subscribe to filterByGenreObservable

this.dataService.filterByGenreObservable.subscribe(data => {
  this.selectedCategories = [...data]; //(*)
});

//(*) is we make simply this.selectedCategories=data, we can not see the data change
//so, I used the spread operator

If we make something like

this.dataService.filterByGenre = this.selectedCategories;

As we are using a setter, emit a new value

Well, I don't use p-checkbox, but you can be the code of this answer in this stackblitz

Update well, Obviously we want to make "something" when change the categories.

Imagine we has an array called allBooks, and we want filterted by categories selected. But first we are going to change the data we send to the user service to make more similar to p-checkbox, so I change my function categoryFileterSelect to send in an array not [true,false,false,true] else ["Horror","Science Fiction"]

categoryFilterSelect() {
    this.dataService.filterByGenre =
      this.options.filter((x, index) => this.selectedCategories[index]);

  }

So, We can write

this.userService.filterByGenreObservable.subscribe(
  data => {
    this.filteredBooks=this.allBooks.filter(x=>data.indexOf(x.genre)>=0)
  },
  error => null,
  () => console.log("error")
);

Well, I want to make at first observable a call with the value of this.userService.filterByGenre, so I go to use rxjs operator startWith so, finally my subscription becomes like

this.userService.filterByGenreObservable.pipe(
  startWith(this.userService.filterByGenre))
  .subscribe(
  data => {
    this.filteredBooks=data?this.allBooks.filter(x=>data.indexOf(x.genre)>=0)
                           :this.allBooks
  },
  error => null,
  () => console.log("error")
);

Upvotes: 1

Nils
Nils

Reputation: 1020

Please try with the following call of filterByGenre() in the second component.

this.userService.filterByGenre().subscribe((data:string[]) => {
  this.selectedCategories = data;

  console.log(this.selectedCategories);


  //perform filter logic here and update the filteredBooks array based 
  // onselected categories
}

Upvotes: 0

Related Questions