Angular directive remove itself - angularjs

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();
}
});

Related

Is it possible to call controller if ng-if condition is true?

I have two controllers on same page. What I am trying to achieve is if specific value is 1 call controller 1 and if 0 then controller 2. But the problem is that I am getting value inside controllers which is not accessible outside the controller, so how can I achieve this?
This case calls for a service, which can be shared between controllers.
All you have to do is inject the service into both controllers and update the variable in the service.
angular.module('app').service('myService', function() {
var self = this;
self.isActive;
self.setActive = function(val) {
self.isActive = val;
};
self.getActive = function() {
return self.isActive;
})
. controller('first', function(myService) {
var isActive = myService.getActive();
})
. controller('second', function(myService) {
var isActive = myService.getActive();
})
Both controllers would have the same value, you would just have to handle managing them during the life cycle of your app via the controllers.
Note that you can do anything you want in a service in terms of logic, you just have to share the service with the required controllers. It would be more likely that a function would handle the logic which you are splitting into different controllers.
// Inside the service
self.handleActive = function(isActive) {
if (isActive) {
// Do something
} else {
// Do other
}
};
Use directive for this purpose
global.directive('dynamicCtrl', ['$compile', '$parse',function($compile,
$parse) {
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-ctrl'))(scope);
elem.removeAttr('dynamic-ctrl');
// add your condition here
if(condition == true){
elem.attr('ng-controller',"first");
}
else{
elem.attr('ng-controller',"second");
}
$compile(elem)(scope);
}
};
}]);
use in html as

AngularJS $digest already in progress at onScroll event

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.

InAppBrowser passing a callback function

I have a InAppBrowser working fine in my app
$scope.openInAppBrowser = function (url) {
var ref = window.open(encodeURI(url), '_blank', 'location=yes');
ref.addEventListener('loadstop', function (event) {
if (event.url.match("close")) {
$scope.refreshGamePage = 1; // this variable is under watch in a directive
ref.close();
}
});
}
here is the directive written to refresh the page
module.directive('reloadPage', ['$http', function ($http) {
return {
restrict: 'A',
link: function ($scope, element, attrs) {
$scope.refreshPage = function () {
if($scope.refreshGamePage ==1)
return true;
else
return false;
};
$scope.$watch($scope.refreshPage, function (v) {
if (v) {
$scope.setGamePage(); // this function will contains code to refresh game page
}
});
}
};
}]);
but it seems like loadstop event listener is unable to update scope variable.
Can anyone help me with this?
Basically idea is I want to refresh current page (one from which InAppBrowser opened) in my app as soon as InAppBrowser closes itself.
Any better way to achieve this will be appreciated.
Just use angular $apply() method to change the value of angular
variables because it is changed out of angularjs turn.
$scope.openInAppBrowser = function (url) {
var ref = window.open(encodeURI(url), '_blank', 'location=yes');
ref.addEventListener('loadstop', function (event) {
if (event.url.match("close")) {
$scope.$apply(function () {
$scope.refreshGamePage = 1;
});
ref.close();
}
});
}

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;
});
}

Resources