5

I have the following use case - I provide a dialog service where I put a different content depending on the context. In the service method i manually compile a dom element and use it to display dialog using jquery ui. The code is the following:

var _view = jQuery('<div id="config-dialog"><span ng-include="\'' +  $scope.configView + '\'" ng-controller="' + $scope.configController + '"></span></div>');
var _compiled = $compile(_view.contents())($scope);

And then I fire a scope event that should be handled by a scope function defined in the controller

$scope.$broadcast('config-open', $scope.config);

then I open the dialog and the user performs something and closes the dialog. When the dialog gets closed i remove the "config-dialog" element from the DOM. Like this:

$(this).dialog("destroy");
jQuery('#config-dialog').remove();

However the next time the dialog gets opened and a new controller gets instantiated i see that the 'config-open' gets handled twice, when open the dialog again it gets handled 3 times. That means the scope attached to the ng-include that I dynamically create is not destroyed. I've debugged with Batarang and saw that really the child scope created by ng-include is not cleaned. AFAIK AngularJS scopes are associated to dom elements and when I remove the element, the scope should be garbage collected but this doesn't happen. My question is - is AngularJS supposed to clean up the scope in my case. What am I doing wrong and is there a more proper way to implement my use case?

Adrian Mitev
  • 4,722
  • 3
  • 31
  • 53

3 Answers3

4

When your dialog is closing, you should actually destroy the scope manually.

As an example, let's say you have a dom element in your dialog that has an ng-click:

<div class="dialog">
    ....
    <a data-ng-click="closeDialog()">Close Me!</a>
    ....
</div>

Then in your controller you would wire up that ng click like so:

function myController($scope, ....){
    ....
    $scope.closeDialog = function(){
        $scope.$destroy();
        //not to sure about the use of the word "this" here, but you should be able to figure out what element it is somehow
        $(this).dialog("destroy");
        jQuery('#config-dialog').remove();
    }
    ....
}

The problem I think you're encountering is that you think by destroy the element the scope is destroyed. That's not true. You have to destroy the scope manually.

Mathew Berg
  • 28,625
  • 11
  • 69
  • 90
  • This doesn't work for me because the controller is only for the dialog content. OK and cancel buttons for the dialog are handled outside of the dialog content. Is there a documentation describing when a scopoe gets destryoed and best practices about controlling scope lifecycle? – Adrian Mitev Oct 01 '13 at 20:17
  • 1
    Care to put a js fiddle together? I'd like to see how you've constructed the close portion. – Mathew Berg Oct 01 '13 at 20:38
4

The controller is only for the dialog content. OK and cancel buttons for the dialog are handled outside of the dialog content

I suppose your HTML looks like this:

<div class="dialog">
    <div class="dialog-content" ng-controller="yourcontroller">
       ...your content here 
    </div>

    <button id="btnClose">Close</button>  //your button is outside your controller      
</div>

Try: angular.element(domElement).scope() Like this (using jquery with delegated event because you're creating your DOM dynamically):

$(document).on("click","#btnClose",function(){
    var dialog = $(this).closest(".dialog");
    //call this to destroy the scope.
    angular.element(dialog.find(".dialog-content")[0]).scope().$destroy();
    //or angular.element(dialog[0]).scope().$destroy(); depending on where you attach your scope.
    //Destroy dialog
    dialog.dialog("destroy");
    dialog.remove();
});
Khanh TO
  • 48,509
  • 13
  • 99
  • 115
  • That's correct, but is there a tutorial/blog post describing the scopes and how to manage them properly, even for dynamic content. – Adrian Mitev Oct 06 '13 at 06:54
  • @AdrianMitev: I learn all about angular js on its official site at http://angularjs.org/.(I don't know if there is a better site). There are a lot of information you can find. Like http://docs.angularjs.org/guide/, http://docs.angularjs.org/api/, http://blog.angularjs.org/,...In your case, you could find the information at http://docs.angularjs.org/api/ng.$compile and http://docs.angularjs.org/guide/scope – Khanh TO Oct 06 '13 at 07:50
1

I'm going to do something I usually don't do and suggest you take a different approach altogether. Your problem most likely arises due to the combination of JQuery and Angular (haven't tried your code, but it seems like the most likely reason). Angular will loose track of stuff done outside the framework which is one of the reasons not to mix unless you really have to.

In this specific case, as far as I understand it, there is nothing that you couldn't do in Angular instead.

If I would do this, I would probably start by putting my dialog inside an ng-switch (which adds and removes things from the DOM just like your JQuery would), and then have my opening broadcast and the close button trigger the ng-switch criteria instead. Then you have a dialog that is added and removed form the DOM like you have it and this way you don't have to worry about syncing between Angular and Jquery.

And read the second part of this! I used to use Jquery all over the place in Angular before, then I read this post and stopped. Now everything tends to go much more smoothly :-)


Edit: A small example html. "dialog.template" in this example is a variable in the scope of DialogController that is set by the invoker.

<div ng-switch on="dialog_status">
    <div ng-switch-when="open">
        <div class="dialog" ng-controller="DialogController" ng-include="dialog.template">
           Dialog will appear here 
        </div>
    </div>
</div>
Community
  • 1
  • 1
Erik Honn
  • 7,576
  • 5
  • 33
  • 42
  • My dialog has to display different kind of views with specific controller. How do you recommend to do that using ng-switch? – Adrian Mitev Oct 07 '13 at 09:25
  • Assuming that you have existing templates that you can just include (it looks like you have from your emaple) you can use ng-include in pretty much the same way you do now. The content that you switch in can contain a div with ng-include={{some_scope_parameter}}. – Erik Honn Oct 07 '13 at 09:39