Reputation: 5207
I have a child directive that is quite dynamic running in a TypeScript AngularJS application. The template and the controller are attached at runtime based on what it needs to do for a given situation and the template itself contains a number of directives. I need to be able to show multiple directives on the page, so I use an isolated scope between each one. I have another directive that's responsible for keeping track which of these child directives should be on the page at any given time (referred to as the parent directive).
If I need to add a new child, I create the template for it in that parent directive , identify the element I want to append it to and use:
var compiledDirective = this.$compile(myTemplate)(scope.$new(true, scope));
angular.element(".parent").append(compiledDirective);
_.defer(() => {
scope.$apply();
});
If I load the application up, this works fine and I can see it create each child that runs just as I'd expect it to. When I need to remove any/all of these children, this parent handles that as well in the following function:
var destroyChild = (identifier: string) {
this.$timeout(() => {
var elem = angular.element(".parent").find(`li[ident='${identifier}']`);
elem.scope().$destroy();
elem.remove();
_.defer(() => {
});
});
}
Now, all the elements remove themselves as expected and if I look at Batarang, I see the directives disappear from the available scopes. I have an event handler for the $destroy event in the child directive, so I can see a console message when the directive is destroyed and it appears as expected after this destroy function runs.
The issue here is that I need to be able to add more of these child directives based on other activities on the page within the parent. Following this destroy activity, I find that the scope doesn't load up quite properly when I do this process all over again a second time - rather, I see a number of scopes created for the child directive now, but I find that some of the bindings no longer update (e.g. I notice that an ng-hide no longer updates when the scope values change in the child directive) and thus the directive winds up being unusable.
Generally, I'd assume that there's simply a bug in the logic and that I need to go look for it and there might very well be, except that while debugging this, I found that in that parent function that handles destroying the child directives, if I comment out the line:
elem.scope().$destroy();
All the scopes remain in Batarang and all the DOM elements disappear as you'd expect, so this would seem like a bit of a memory leak, except that if I run the trigger to make the directives load up again, they load up without an issue (I just wind up with many scopes in the Batarang view now that never clear out).
What would cause this behavior, in that $destroying the directive's isolated scope breaks future uses of the directive, but in leaving it there makes it work later on, but causes the memory leak? Is there an alternative way to clear these scopes without using $destroy?
Edit: Upon further investigation, I'm finding that when the first directive is created, it has an appropriately created isolated scope, but when making subsequent directives, their isolated scopes are getting created in Batarang, but if I actually do a lookup with angular.element('lookup-their-individual-idents').scope() the ID reported is actually that of the parent, so when I'm destroying the first one, it cleans up as expected. When I destroy the second, it actually wipes out the parent scope (and thus all children), so that's why it's not placing accurately on the parent going forward.
So, I expect I just need to figure out how to appropriately bind the isolated scopes to the directives when I'm adding them to the DOM and the rest will resolve itself.
Upvotes: 4
Views: 703
Reputation: 5207
The issue became apparent once I started looking at exactly which directives were getting created and destroyed in Batarang when they were supposed to be. Where the first directive would always have an isolated scope, none of the subsequent directives would and rather, they'd all inherit the parent scope, so when they were all destroyed, the parent scope would go with them.
Rather, while the code I displayed above was used to create the new directives where none were on the page, I needed to update the code actually responsible for putting more than one directive on the page within that parent directive to have it create the isolated scopes for each of the new children, e.g.:
var child = this.templateGenerator.addChild(applicableData);
var compiledChild = this.$compile(child)(scope.$new(true));
element.append(compiledChild);
And now if I look at each of the directives, they're all pointing at the expected scopes in Batarang. What's more, when I destroy them and place new directives on the page, they load as expected as well, so this has resolved the issue.
Upvotes: 4