justinledouxweb
justinledouxweb

Reputation: 1357

AngularJS $destroy dynamically inserted component

I am building a modal that provides a service to toggle it on and off. That modal has a small controller which controls the close button, and the $compile of the template that goes into the content of the modal.

That template is a component, and of course, that component has a controller.

How do I destroy that component, after hiding the modal? Technically, when ng-if takes care of removing my modal from the DOM, it still has that component loaded into it.

Here is the code:

modal.controller.js

class ModalController {
  constructor($scope, modalService, $compile, $timeout) {
    this.$scope = $scope;
    this.modalService = modalService;
    this.$compile = $compile;
    this.$timeout = $timeout;
  }

  $onInit() {
    this.$scope.$watch(this.modalService.getConfig(), (newVal) => {
      this.config = newVal.isDisplayed
        ? angular.extend(this.config, newVal)
        : {};

      // I wait for ng-if to put the modal into the DOM then I 
      // compile the component.
      this.$timeout(() => {
        if (this.config.template) {
          const component = this.$compile(this.config.template)(this.$scope);
          angular.element('#modal-content').html(component);
          this.config.isRendered = true;
        }
      }, 0);
    }, true);
  }

  close() {
    this.modalService.close();
  }
}

ModalController.$inject = [
  '$scope',
  'modalService',
  '$compile',
  '$timeout',
];

export default ModalController;

modal.service.js

class ModalService {
  constructor($timeout) {
    this.config = {};
    this.$timeout = $timeout;
  }

  open(newConfig) {
    this.config = newConfig;
    this.config.isDisplayed = true;
  }

  close() {
    this.config.template = null;
    this.config.isRendered = false;

    // the reason there is timeout here is to run my CSS animation
    // before ng-if removes the modal from the DOM.
    this.$timeout(() => {
      this.config.isDisplayed = false;
      this.config = {};
    }, 310);
  }

  getConfig() {
    return () => this.config;
  }
}

ModalService.$inject = [
  '$timeout',
];

export default ModalService;

BarController where I call the modal:

class BarController {
  constructor(modalService) {
    this.modalService = modalService;
  }

  openModal() {
    this.modalService.open({
      title: 'Add a document',
      template: '<foo-component></foo-component>',
    });
  }
}

BarController.$inject = [
  'modalService',
];

export default BarController;

Upvotes: 3

Views: 6998

Answers (1)

justinledouxweb
justinledouxweb

Reputation: 1357

Aha! It turns out that it's not that complicated. You just need to store the scope of the component, and then call $destroy inside the close function of the modal controller.

class ModalController {
  constructor($scope, modalService, $compile, $timeout) {
    this.$scope = $scope;
    this.modalService = modalService;
    this.$compile = $compile;
    this.$timeout = $timeout;
  }

  $onInit() {
    this.childScope = null;

    this.$scope.$watch(this.modalService.getConfig(), (newVal) => {
      this.config = newVal.isDisplayed
        ? angular.extend(this.config, newVal)
        : {};

      this.$timeout(() => {
        if (this.config.template) {
          this.childScope = this.$scope.$new();

          angular
            .element('#modal-content')
            .html(this.$compile(this.config.template)(this.childScope));

          this.config.isRendered = true;
        }
      }, 0);
    }, true);
  }

  close() {
    this.config.isRendered = false;

    // this timout is used to make sure the CSS transition is executed
    // before the component is removed from the DOM.
    this.$timeout(() => {
      this.childScope.$destroy();
      angular.element('#modal-content').empty();
      this.modalService.close();
    }, 310);
  }
}

ModalController.$inject = [
  '$scope',
  'modalService',
  '$compile',
  '$timeout',
];

export default ModalController;

Upvotes: 2

Related Questions