1

I have a simple setup where a recipe item can trigger the display of a modal component with the recipe item's data. Here is the code.

// modal.service.ts

import { RecipeModel } from './recipe.model'
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable()
export class ModalService {
    selectedRecipe = new Subject<RecipeModel>();
    constructor() {}
}

// recipe-item.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { RecipeModel } from '../../recipe.model';
import { ModalService } from '../../modal.service';

@Component({
  selector: 'app-recipe-item',
  templateUrl: './recipe-item.component.html',
  styleUrls: ['./recipe-item.component.css']
})
export class RecipeItemComponent implements OnInit {
  isFavourite: boolean = false;
  @Input() recipeInstance: RecipeModel;
  cardText: string;
  constructor(private modalService: ModalService) { }

  ngOnInit(): void {
    this.cardText = this.recipeInstance.ingredients.slice(0, 3).map(elem => elem.name).join(', ');
    this.cardText = this.cardText + '...and ' + (this.recipeInstance.ingredients.length - 3) + ' more';
  }

  showRecipeDetail() {
    this.modalService.selectedRecipe.next(this.recipeInstance);
  }
}

// recipe-detail.component.ts

import { Component, OnInit, Input, ViewChild } from '@angular/core';

import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; 
import { RecipeModel } from '../recipe.model';
import { ModalService } from '../modal.service';

@Component({
  selector: 'app-recipe-detail',
  templateUrl: './recipe-detail.component.html',
  styleUrls: ['./recipe-detail.component.css']
})
export class RecipeDetailComponent implements OnInit {
  @Input() selectedRecipe: RecipeModel;
  @ViewChild('mymodal') modal: any;
  
  constructor(private ngModalService: NgbModal, private modalService: ModalService) {}

  ngOnInit(): void {
    this.modalService.selectedRecipe.subscribe(val => {
      this.selectedRecipe = val;
      if (val) {
        this.ngModalService.open(this.modal, {size: 'lg', scrollable: true});
      }
    })
  }
}

When I go to /recipes for the first time, I can click read more on the recipe item cards shown there, and the recipe detail modal component pops up only once, as usual. When I change the route, and then come back, the number of modals gets doubled, and then tripled if I route again. This happens because I am calling the modal popup inside the ngOnInit() which gets called every time the /recipes is reinitialized.

How can I fix this problem?

Edit: Here is the Github link if you want to take a look at the project structure.

Shiladitya Bose
  • 893
  • 2
  • 13
  • 31

1 Answers1

1

What is happening is that each time RecipeDetailComponent is created it calls ngOnInit() therefore you are subscribing callbacks each time this happens. So you will end with N callbacks registered for that event.

Easier solution is to unsubscribe each time the component is destroyed ngOnDestroy(). https://angular.io/api/core/OnDestroy

Find here a better explanation an another options: Angular/RxJs When should I unsubscribe from `Subscription`

kterry
  • 116
  • 2
  • 3
  • I previously tried to do unsubscribing by `this.modalService.selectedRecipe.unsubscribe()` in the `ngOnDestroy()`, which did not work as intended, basically had some `ObjectUnsubscribedErrorImplmessage` Now, initializing the subscriber to `this.sub = this.modalService.selectedRecipe.subscribe(...)` and `this.sub.unsubscribe()` works fine. Can you tell me what is the difference? – Shiladitya Bose Sep 02 '20 at 13:50
  • this.sub = this.modalService.selectedRecipe.subscribe(...) -> here you are saving the Subscription object that is returned by subscribe(), so you can unsubscribe later using that reference this.modalService.selectedRecipe.unsubscribe() is not unsubscribing your callback subscription because this.modalService.selectedRecipe is a Subject object not the Subscription one that is generated each time subscribe is called https://rxjs-dev.firebaseapp.com/guide/subject – kterry Sep 02 '20 at 14:03