angular scope.$parent is null, a bug or a feature?

angularjs

Solution

You asked about best practices. To make directives more flexible and reusable, it is recommended instead of hard-coding the $scope chain you inherit from the parent scope explicitly. For example, I noticed you are querying bands and bandsPage. Instead of relying on the parent scope and inheritance to make this possible, you can instead use scope isolation to map those variables from the parent scope into an isolated scope for your directive:

return {
   scope: {
       bands: "=", 
       bandsPage: "="
   },
   link: etc. etc. 
}

This sets up a two-way data-binding so you can "map-through" like this:

<myDirective bands="bands" bandsPage="bandsPage" ...> 

This way it won't matter if you are in a parent scope or a child scope and you won't have to capture the scope value. It is available on linking but what you want is something there any time the directive is used, and the scope isolation will automatically create a scope for your directive that maps up to the inherited properties you specify while isolating your code from extraneous properties you don't need.

You can learn more in the "Isolating the Scope of the Directive" section of the page here: view-source:http://docs.angularjs.org/guide/directive#!

Problem

I wrote the following code, which is a directive that display data from the server in an infinite autoscroll, i.e. the thumbnails are automatically scrolled up, and each time a thumb goes out of view a new one is loaded from the server. If the user points on a thumb, the autoscroll stops until the user remove the mouse. This works ok (as long as the user doesn't point on a thumb), but for some reason, after the autoscroll stops & restart, it works for a few seconds and then it breaks because the scope.$parent is null ``` var wmApp = angular.module("wmApp"); wmApp.directive("wmThumb", function($rootScope, $http) { return { link: function(scope, element, attrs) { $rootScope.autoscroll = 1; w = $('.content').width(); h = $('.content').height(); eWidth = w * 0.2; eHeight = h * 0.25; growWidth = w * 0.4; element.width(eWidth); $(".bands-thumb").css("right", 0); contain = element.parents(".thumbs-container"); element.css("margin", w * 0.01); contain.height(h * 0.9); contain.width(growWidth + 20); contain.css("margin", w * 0.02); $(".thumb-body", element).height(eHeight); $(".thumb-pImg", element).height(eHeight); $(".thumb-pImg img", element).width(eWidth); $(".thumb-title", element).width(eWidth); var startAutoScroll = function(elem) { if ($rootScope.autoscroll <= 0 || elem.height() == 0 /* elem is out of view*/ ) return; var position = elem.position(); if (position.top < -elem.height()) { //debugger; // $rootScope.autoscroll--; scope.$parent.bands.splice(0,1); page = scope.$parent.bandsPage; //$http.post('ui/list/Band', page).success(function(response) { $.post('ui/list/Band', page, function (response) { scope.$parent.bandsPage.count++; scope.$parent.bands = scope.$parent.bands.concat(response); // $rootScope.autoscroll++; scope.$parent.$digest(); }); } else { elem.animate({ top: "-=5" }, 20, 'linear', function() { startAutoScroll(elem); }); } }; setTimeout(function() { ep= element.prev(); if (!ep.hasClass('node-thumb')) t = 0; else { p = ep.position(); t = p.top; t = t + element.height() + 5; } element.css("top", t + "px"); setTimeout(function() { startAutoScroll(element); }, 20); }, 100); growBody = growWidth - eWidth - 10; element.bind("mouseenter", function() { $(this).animate({ width: growWidth + 'px', }, 100); $(".thumb-body", element).show(100).animate({ width: growBody + 'px', }, 100); element.css("z-index", 100); $rootScope.autoscroll--; // fix bug when jumping from each other }); element.bind("mouseleave", function(event) { $(this).animate({ width: eWidth + 'px', }, 100); $(".thumb-body", element).hide(100).animate({ width: '0px', }, 100); element.css("z-index", 1); setTimeout(function() { $rootScope.autoscroll++; $(".node-thumb").each(function(i, e) { e = $(e); startAutoScroll(e); }); }, 200); }); setTimeout(function () { element.show(); // // fit image id H is small img = $(".thumb-pImg img", element); imgH = $(img).height(); if (imgH < eHeight) $(img).height(eHeight); },100); } }; }); ``` My questions: - Is there a better way to do it according to angular best practice? - Why does the scope.$parent node become null? is it a bug or a feature? - How should I solve this? FYI: Here is a code that works - I use the rootScope instead of the scope.$parent ``` var wmApp = angular.module("wmApp"); wmApp.directive("wmThumb", function($rootScope, $http, $timeout) { return { link: function(scope, element, attrs) { w = $('.content').width(); h = $('.content').height(); eWidth = w * 0.2; eHeight = h * 0.25; growWidth = w * 0.4; element.width(eWidth); $(".bands-thumb").css("right", 0); contain = element.parents(".thumbs-container"); element.css("margin", w * 0.01); contain.height(h * 0.9); contain.width(growWidth + 20); contain.css("margin", w * 0.02); $(".thumb-body", element).height(eHeight); $(".thumb-pImg", element).height(eHeight); $(".thumb-pImg img", element).width(eWidth); $(".thumb-title", element).width(eWidth); var startAutoScroll = function(elem) { if ($rootScope.autoscroll <= 0 || elem.height() == 0 /* elem is out of view*/ ) return; var position = elem.position(); if (position.top < -elem.height()) { $rootScope.bands.splice(0,1); page = $rootScope.bandsPage; $.post('ui/list/Band', page, function (response) { $rootScope.bandsPage.count++; $rootScope.bands = $rootScope.bands.concat(response); $rootScope.$digest(); }); } else { elem.animate({ top: "-=5" }, 20, 'linear', function() { startAutoScroll(elem); }); } }; $timeout(function() { ep= element.prev(); if (!ep.hasClass('node-thumb')) t = 0; else { p = ep.position(); t = p.top; t = t + element.height() + 5; } element.css("top", t + "px"); $timeout(function() { startAutoScroll(element); }, 20); }, 100); growBody = growWidth - eWidth - 10; element.bind("mouseenter", function() { $(this).animate({ width: growWidth + 'px', }, 100); $(".thumb-body", element).show(100).animate({ width: growBody + 'px', }, 100); element.css("z-index", 100); $rootScope.autoscroll--; // fix bug when jumping from each other }); element.bind("mouseleave", function(event) { $(this).animate({ width: eWidth + 'px', }, 100); $(".thumb-body", element).hide(100).animate({ width: '0px', }, 100); element.css("z-index", 1); $timeout(function() { $rootScope.autoscroll++; $(".node-thumb").each(function(i, e) { e = $(e); startAutoScroll(e); }); }, 200); }); $timeout(function () { element.show(); // // fit image id H is small img = $(".thumb-pImg img", element); imgH = $(img).height(); if (imgH < eHeight) $(img).height(eHeight); },100); } }; }); ```

Original source