Part of the code in my controller deletes a DOM element:
MetrofficeApp.controller('EmployeesCtrl', function($scope) {
...
angular.element(deleteElem).remove();
$scope.$apply();
When I navigate away from this page using angular, and then come back to the same page where the deleted element is - the element is visible again.
What must I do besides $scope.$apply() to make the changes permanent (save DOM changes) between navigating pages?
I feel like you have a fundamental misunderstanding about the DOM. Every time you navigate back to a page, all code is re-invoked and templates are recreated. So, it is correct behavior that the DOM is created again.
My guess is that you have some underlying model that is visualized by the DOM. Rather than deleting components of the DOM, you should be deleting the part of the model that is visualized by the DOM (and pushing that change to the server). This way, the next time you navigate to the DOM, the model is consistent and the deleted item is no longer shown.
And a smaller point, but still important: controllers should not be manipulating the DOM directly. You should be creating directives for that.
You need to set some flag on the scope object which the angular js template can use to determine whether the show the dom element in question.
E.g.
// controller code
scope.shouldShowElement = some.flag;
// angular template
<div ng-show="shouldShowElement">...</div>
Angular templates have access to all the variables on the scope - if, at a later point, you do:
// controller code
scope.shouldShowElement = true;
scope.$apply();
Your template will update to reflect that change
Related
I am a bit of a beginner, but I would like to ask a general question. AngularJS does a watch on binded variables. My question is, when I navigate and a new html template is loaded, are the old watched values removed, or do they stay in the loop?
Hello $watch is bound to a controller ,which in turn shows the data to an HTML template.
So, If you navigate to another state or url and another controller loads , then the previous controller with all the data and the $watch will be unloaded, but new ones($watch) will load from the new loaded controller(if there are any).
$watch are never removed by itself, they have to be removed manually.
You should go through $watch lifecycle : $watch life cycle
No, the $watch list is updated as per the html which is being rendered by the browser.If you change template inside the page, the browser will have to render the new expression.
I think this youtube video would help you in getting
I am coming to you at great despair - been working on this for two days now.
We have a Single Page Application built with Angular JS. We use the $routeProvider in HTML5 mode to achieve the SPA routing. Functionality wise - all works great!
We have one global controller attached to the body element, there is one controller in the header for a quick search functionality and all other controllers are scoped to a route. There is some data shared between controllers like a currentUser object and a ViewRes object that contains string values for user's chosen language.
But, we noticed that the Chrome service takes too much RAM for our page. I used Chrome Profiles tool to see what is happening. I disabled most of our code that was using a complex directive and left out only the basics. Memory consumption was lowered a lot, but it is still obviously there. Any time I change a page, the memory increases.
In the Heap Snapshot it shows that most memory is taken by (closure) and (array). Also the Detached DOM tree is big. Please note that these snapshots are with bare elements of our application (header, footer and lightweight content). If I include our complex UI components, then memory jumps from 14MB to 50MB to 140MB... and more. Obviously we will take care for these directives, but I am concerned that our issue is global, not just to bad design of the directives.
When I open the (array) element, I notice there are bunch of them with both shallow size and retained size of 6172. Tracking the scope of that object always leads to some ng directive, like ngShow, ngIf...
As you can see from the image, the tree ends at 'cache in function()'. We use Angular 1.3.6.
EDIT: This project also includes jQuery. We were using jQuery 1.8.2 and when I switched to 1.11.2, switching between simple pages (simple ng-repeats and simple models) doesn't cause memory leaks anymore (no more detached DOM elements).
Now the complex directives are still giving me too much detached elements, so I am going to work on those now and I'll post the results here when I find out the cause.
Difficult to say what your specific problems are, but common places for memory leaks in Angular include $interval, $watches and event handlers. Each of these functions creates a closure that is not cleaned up unless you explicitly delete it upon controller tear-down.
$interval in particular is nasty, as it will continue to run until you close the browser or the Web page - even if the user moves to a different tab or application, it won't stop running!
If you create references to DOM elements within these closures, you'll soon start chewing through memory, as the references are never released and the DOM tree becomes detached as the user moves from page to page.
To resolve this, ensure you handle the $destroy event in your controller (and your directives' controllers or link functions), and explicitly clean up after any intervals, watches or event handlers have been used.
You can do this by holding a reference to each $interval, watch or event handler and simply calling it as a function in the $destroy event handler.
For example:
// eventListener to remove
var eventListener = $scope.$on('eventName', function(){…});
// remove the eventListener when the $destroy event is fired
$scope.$on('$destroy', function(){
// call the value returned from $scope.$on as a function to remove
// the event listener
eventListener();
}
// remove an event listener defined on a DOM node:
var elementEventListener = element.on('eventName', function(){…});
element.on('$destroy', function(){
elementEventListener();
}
// Stop an interval
var stop = $interval(function(){...});
$scope.$on('$destroy', function(){
stop();
}
// Finally, unbind a $watch
var watchFn = $scope.$watch('someValue', function(newVal){…}
$scope.on('$destroy', function(){
watchFn();
}
Finally, NEVER store DOM elements in the scope! (see Point #2 here for the reason why).
I have two custom element directives:
<my-directive-parent></my-directive-parent> //only one
and
<my-directive-child></my-directive-child> //variable count
The my-directive-parent directive has its own controller and templateUrl properties defined in its directive definition object. The my-directive-child directive has its own link, scope, templateUrl and require properties defined in its directive definition object. The fourth parameter passed into the link function is the parent my-directive-parent's controller. This is working as expected.
Based on user input, instances of my-directive-child are appended to or removed from the parent my-directive-parent DOM via the my-directive-parent's controller. Since I'm using ngView for the top-level page, changing pages results in Angular automatically cleaning up the various directives and their controllers of the top-level page. Upon returning, the controller for the page re-runs, but the user appended views do not show up (since they were previously wiped out automatically by Angular). I'm using a service which holds the data representing the collection of my-directive-child instances that were previously added.
My issue lies in restoring the my-directive-child directives based on this cached service data. Currently, when the my-directive-parent directive's controller is run, I'm getting the service data array that represents the instances to be added, looping through it, and adding a new my-directive-child instance to the DOM. This is working, but upon restoring each instance I need to pre-populate it with data that was previously entered by the user (of which is recorded in the service). Currently, when adding to the DOM this way each instance is in a "blank" state instead of a desired pre-populated state.
So I have the data needed to recreate the child directive instances, but I'm unable to pre-populate them with the necessary data.
I've tried to place a custom attr on the my-directive-child in an effort to forward the data to the instance's scope, but I learned from Angular's API docs that:
The new scope rule does not apply for the root of the template since the root of the template always gets a new scope
Questions:
Is my approach wrong? What should I be doing?
Is there a way to pass attrs defined on the actual element directive into the directive's scope that represents the inner template HTML?
How do I ensure a custom directive within a custom directive properly pre-populates itself?
Thank you in advance for any ideas, answers, or thoughts that might lead to a solid solution.
I have a directive (), it renders a large number (>10000) of items in a table. It is used in one controller's view.
Normally, when I first visit this controller, it will take a long time to render the directive.
When I leave this controller and visit it again, it re-renders again. Is there a way to cache the rendered directive so that it can show instantly in the second visit?
just an idea, take the generated html and store it either in a service or in the local/session storage then you can check its validity using a timestamp or a data version and just reload that existing html into the container element, this would fix dependencies issues and would help you to build a more independent directive since you don't have to rely on the dom position and also this way several directive instances can render the same dataset if you use attributes for configuring your directive.
or you could render DOM in an asynchronous manner using a recursion function with a $timeout call something like
function Draw(data,index){
if(idx==data.length)
return;
//draw element idx
$timeout(function(){
Draw(data,index+1);
})
}
this way the directive will take longer but results will appear faster in the screen and you don't take the risk of blocking your browser
Is the performance hit the actual DOM rendering or some data processing that's done in the directive? If DOM manipulation is the issue, you could try hiding the content with an ng-show/ng-hide when you navigate away from the controller rather than unloading it. A good way to do this (if you're using ui-router) would be to put the directive in a parent view and have the content hidden or shown depending on which child view is active.
If it's data processing that's the issue, you can move the logic to a service. Services aren't unloaded when you change views, so it will be able to process the data once and supply it to the controller whenever is is loaded without additional processing.
I have included a third party script in my angularJS app, which appends some HTML to the body
document.body.innerHTML += thirdPartyHTML;
Whenever that is done it seems like my ng-click events won't fire. Is the HTML by the third party script appending the HTML in the wrong way, or should I somehow refresh AngularJS view on the DOM ($scope.$apply)?
Update: It seems like I can reproduce the error, with just calling document.body.innerHTML += ''; myself. It apparently hasn't anything to do with the onclick event. If you have your own AngularJS app, you can try to call document.body.innerHTML += ''; after the document has loaded, and none of the ng-click events work.
Whenever innerHTML is changed the browser has to recreate the DOM tree. That means that all information associated with the existing DOM elements is lost.
This does not only include event listeners. Angular stores scopes and controllers on DOM elements. So basically the application is gone.
If the third party app really changes the innerHTMLof the body element, then you better let it run before angular.
As an alternative you can initialize the application manually and don't use the ng-app. The details can be found in here