AngularJS $digest already in progress at onScroll event - angularjs

I have this directive as also known from other threads.
Now I was running into an AngularJS apply exception:
Error: $rootScope:inprog
Action Already In Progress
$digest already in progress
Does anyone know how I can prevent this exception?
I tried it with a trigger but with no success.
(function() {
'use strict';
angular
.module('myProject.common')
.directive('asScrollTop', asScrollTop);
asScrollTop.$inject = ['chatService'];
function asScrollTop(chatService) {
var directive = {
restrict: 'A',
scope: {
chatMessagesOfUser: '=',
currentUser: '=',
messagesPage: '='
},
link: link
};
return directive;
////////////
function link(scope, element, attr) {
console.log(element);
var trigger = false;
element.on('scroll', function() {
if(element[0].scrollTop <= 0 && trigger == false) {
trigger = true;
var page = scope.messagesPage + 1;
chatService.getChatMessagesOfUser(scope.currentUser.id, page).success(function(response) {
scope.chatMessagesOfUser = response;
scope.$apply();
trigger = false;
}).error(function(data) {
console.log('error in asScroll.directive.js#link');
});
}
});
}
}
})();

That just means that you don't need to call scope.$apply(); because a digest is already occurring, so whatever you were trying to achieve with it is already happening. Just remove the line and things should work fine.

Related

Custom asyncvalidator is not getting executed

Hi I am trying to create an angular asyncValidator but I think I am doing something wrong becausse it does not get executed.Here is my code:
(function () {
'use strict';
angular
.module('project')
.directive('imageValidator', imageValidator);
imageValidator.$inject = ['utilitiesService', '$q'];
function imageValidator(utilitiesService, $q) {
var directive = {
restrict: 'A',
require: 'ngModel',
link: link,
};
return directive;
function link(scope, element, attrs, ngModel) {
ngModel.$asyncValidators.imageValidator = function (url) {
var deferred = $q.defer();
if (url) {
var image = new Image();
if (utilitiesService.isNotExternalFile(url)) {
image.src = utilitiesService.getUrl(url)
} else {
image.src = url;
}
image.onload = function () {
deferred.resolve(true);
scope.$apply();
}
image.onerror = function () {
deferred.reject(false);
scope.$apply();
}
} else {
deferred.resolve(true);
}
return deferred.promise;
}
}
}
})();
The code gets executed oance but it does not get executed when I change things in the input.
When it first gets executed it sets the class ng-valid-image-validator which is correct I want it to be valid if there is nothing in the input.
But when I write something in the input ng-valid-image-validator gets removed but it does not add the ng-invalid-image-validator and I asume this happens because the custom validator code does not get executed.
Does anyone have any ideeas what I am doing wrong?
EDIT : I just noticed that the validator gets executed again if emptying the input.

Angular directive remove itself

I have a gallery of images which each pull in related data. I've made a directive to lazy load the images once they are in view. It works well, but each of the directives continues watching for the scroll event, with around 200 of them, it's a lot of events being fired unnecessarily. Is there a way to remove the directive, or disable it?
app.directive('lazyLoadGallery', function(resourceService, $rootScope){
return{
link: function (scope, element, attrs) {
var isLoaded = false;
$('.issue-gallery').on('scroll', function(){
console.log($rootScope.number);
if((attrs.lazyLoadGallery/10) % 1 === 0 && !isLoaded) {
if($(element).visible()){
isLoaded = true;
resourceService.issueListPages.list({number:$rootScope.number}).$promise.then(function(data){
$rootScope.issueList = $rootScope.issueList.concat(data.results);
$rootScope.number ++;
$(element).removeAttr('lazy-load-gallery');
});
};
}else{
$(element).removeAttr('lazy-load-gallery');
}
})
}
}
});
my attempt was to remove the attribute from the DOM. Even though it is removed the directive still is watching for scroll events and working as if it wasn't removed.
I was unable to $destroy listeners on the parent object without eliminating it's event for all directives. I came up with a name spaced event which cleans up listeners.
app.directive('lazyLoadGallery', function(resourceService, $rootScope, $compile){
return{
controller: function($scope, $element, $attrs) {
var isLoaded = false;
angular.element('.issue-gallery').on('scroll.'+ $scope.$id, function () {
if (($attrs.lazyLoadGallery / 10) % 1 === 0 && !isLoaded) {
if ($($element).visible()) {
isLoaded = true;
resourceService.issueListPages.list({number: $rootScope.number}).$promise.then(function (data) {
$rootScope.issueList = $rootScope.issueList.concat(data.results);
$rootScope.number++;
angular.element('.issue-gallery').off('scroll.'+ $scope.$id);
});
}
;
} else {
angular.element('.issue-gallery').off('scroll.'+ $scope.$id);
}
})
}
}
});
The angular documentation states that the return of $on function is
Returns function() Returns a deregistration function for this
listener.
So in your case just assign it to a variable and call it when you don't need it anymore.
var deregister = $scope.$on('something', function() {
if (success) {
deregister();
}
});

AngularJS directives require parent not working [duplicate]

This question already has answers here:
Controller Required By Directive Can't Be Found
(2 answers)
Closed 7 years ago.
I have 2 directives that I want to share functions between, so I figured I would create a parent directive.
I did it like this:
.directive('kdAlert', function () {
return {
restrict: 'A',
scope: {
},
link: function (scope, element) {
// Set our dismiss to false
scope.dismiss = false;
// Have to use a watch because of issues with other directives
scope.$watch(function () {
// Watch the dismiss
return scope.dismiss;
// If the value changes
}, function (dismiss) {
// If our value is false
if (dismiss === false || dismiss === 'false') {
// Remove the class from the element
element.removeClass('ng-hide');
// Else, if the value is true (or anything else)
} else {
// Add the class to the element
element.addClass('ng-hide');
}
});
// Get our buttons
var buttons = element.find('button');
// Binds our close button
self.bindCloseButton = function (callback) {
// If we have a button
for (var i = 0; i < buttons.length; i++) {
// Get our current button
var button = angular.element(buttons[i]);
// If our button is the close button
if (button.hasClass('close')) {
// If the button is clicked
button.on('click', function (e) {
console.log('clicked');
// Prevent any default actions
e.preventDefault();
// Callback
callback()
// Remove our element
element.remove();
});
}
}
};
}
};
})
.directive('cookie', function () {
return {
restrict: 'A',
require: 'kdAlert',
templateUrl: '/assets/tpl/directives/cookie.html',
link: function (scope, element, attr, controller) {
// Dismiss the alert
scope.dismissAlert = function () {
// Set our cookie
scope.dismiss = $cookies.dismissCookieAlert = true;
};
// Bind our close button
scope.bindCloseButton(scope.dismissAlert);
}
};
})
.directive('newsletter', function () {
return {
restrict: 'A',
require: 'kdAlert',
controller: 'NewsletterController',
templateUrl: '/assets/tpl/directives/newsletter.html',
link: function (scope, element, attr, controller) {
// Saves our email address
scope.subscribe = function (valid) {
// If we are not valid
if (!valid) {
// Return from the function
return;
}
// Subscribe
controller.subscribe(scope.email);
};
// Dismiss the alert
self.dismissAlert = function () {
// Set our cookie
self.dismiss = $cookies.dismissNewsletterAlert = true;
};
// Bind our close button
scope.bindCloseButton(scope.dismissAlert);
}
};
})
The newsletter has a controller also, which just looks like this:
.controller('NewsletterController', ['$q', '$cookies', 'SubscriberService', 'toastr', function ($q, $cookies, service, toastr) {
var self = this;
// Saves our email address
self.subscribe = function (email) {
// Create our deferred promise
var deferred = $q.defer();
// Subscribe
service.subscribe(email).success(function () {
// If we succeed, display a message
toastr.success('You will now recieve occasional newsletters.');
// Resolve our promise
deferred.resolve();
});
// Return our promise
return deferred.promise;
};
}])
but when I run my application I get an error:
Controller 'kdAlert', required by directive 'newsletter', can't be found!
does anyone know how I can get it to work?
You have to modify require: 'kdAlert', to require: '^kdAlert', and add a controller to your kdAlert in order to make the require work, require will try to find the controller of the kdAlert directive and pass it as a parameter to the directive link function.

AngularJS calling service from custom directive

I have the following directive, service and controller inside my AngularJS app. The service is common between the directive and this controller (as well as other controllers using the same service in my app). As shown inside the controller I watch the service for changes, while in directive I communicate with the service to update it. For some reason which I don't know the directive is not updated my service, so can someone please tell me what I am doing wrong here? Thanks
Controller:
myapp.controller('ClientsCtrl', function ($scope, UserSvc) {
$scope.showForm = UserSvc.frmOpened;
$scope.$watch(function () {
return UserSvc.frmOpened;
}, function () {
$scope.showForm = UserSvc.frmOpened;
console.log('Changed... ' + $scope.showForm);
});
});
Service
myapp.factory('UserSvc', function ($log, $q, $http) {
return {
frmOpened: false
};
});
Directive:
myapp.directive('myDirective', ['UserSvc', function (UserSvc) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
angular.element(element).on("click", function () {
var parentElement = $(this).parent();
if (parentElement.hasClass('sample')) UserSvc.frmOpened = true; //This code never update the service
} else {
UserSvc.frmOpened = false; //This code never update the service
}
return false;
});
}
};
}]);
.on() is a jQuery method (also included in Angular's jqLite). The code inside the attached event handler lives outside of Angular, so you need to use $apply:
$apply() is used to execute an expression in angular from outside of
the angular framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the angular framework we need to perform proper scope life cycle of
exception handling, executing watches.
For example:
element.on("click", function() {
var parentElement = $(this).parent();
scope.$apply(function() {
if (parentElement.hasClass('sample')) {
UserSvc.frmOpened = true;
} else {
UserSvc.frmOpened = false;
}
});
return false;
});
Demo: http://plnkr.co/edit/mCl0jFwzdKW9UgwPYSQ9?p=preview
Also, the element in the link function is already a jqLite/jQuery-wrapped element, no need to perform angular.element() on it again.
It looks like you have some brackets where they shouldn't be in your directive. You don't have brackets surrounding the first part of your if statement, but you have a closing bracket before your else. I think this messes up things.
Try using this as your link-function and see if it changes anything:
link: function (scope, element, attrs) {
angular.element(element).on("click", function () {
var parentElement = $(this).parent();
if (parentElement.hasClass('sample')) {
UserSvc.frmOpened = true; //Surrounded this with brackets
} else {
UserSvc.frmOpened = false;
}
return false;
});
}

Changes event does not trigger in angular directive

I'm writing an angular directive. Here is the code:
'use strict';
app.directive('mcategory', function (MainCategory, $rootScope) {
return {
restrict: 'E',
replace: true,
template: '<select data-ng-model="selectedMainCategory" data-ng-options="mainCategory.mainCategoryId as mainCategory.mainCategoryName ' +
'for mainCategory in mainCategories" data-ng-change="mainCategoryChanged()"></select>',
link: function(scope, element, attr, controller) {
var elm = angular.element(element);
elm.attr('id', attr['id']);
elm.attr('name', attr['name']);
elm.attr('class', attr['class']);
MainCategory.get().then(function (mainCategories) {
scope.mainCategories = mainCategories;
});
scope.selectedMainCategory = false;
scope.mainCategoryChanged = function () {
if (!scope.selectedMainCategory) {
return;
}
$rootScope.$broadcast("mainCategoryChanged", { mainCategory: scope.selectedMainCategory });
};
}
};
})
MainCategory is a service and it works ok.
The problem is that selectedMainCategory is always undefined and mainCategoryChanged() event is not triggered when user selects another category in the select element. I think I have made a silly mistake but it is more than 2 hours that I can't solve the problem. Any help is appreciated in advance.
UPDATE
Here is the service:
'use strict';
app.factory('MainCategory', function ($http, $q) {
return {
get: function() {
var deferred = $q.defer();
$http.get('/MainCategory/GetMainCategories').success(deferred.resolve).error(deferred.reject );
return deferred.promise;
}
};
})
When I '/MainCategory/GetMainCategories' url in the browser I get all the categories in the correct json format.

Resources