Accessing Service Data from Directive - angularjs

I am trying to show or hide the HTML elements based on spring security roles using Angular JS.
For this, I have created a service and directive.
I am getting data back from the server but I am not able to access the data that I got in service. Here is my code
app.service('MyService', function($http, $rootScope) {
this.data = [];
var _this = this;
$http.get('permissions').then(function data(response) {
_this.data=response.data;
console.log(response);
$rootScope.$broadcast('MyServiceReady');
})
/* return {
permissionData: function(){
return _this.data;
}
}*/
})
app.directive('allowed', [ 'MyService', function(MyService) {
return function(scope, element, attr) {
scope.$on('MyServiceReady', function() {
$subScope = this;
$subScope.status = false;
$subScope.permissions=MyService.data;
console.log(MyService.data);
console.log("First:" + $subScope.status+" permission:"+attr.allowed);
angular.forEach(permissions, function(item) {
if (angular.equals(item, attr.allowed)) {
$subScope.status = true;
}
});
console.log("last:" + $subScope.status);
console.log(element);
if (!$subScope.status) {
$(element).hide();
} else {
$(element).show();
}
});
};
} ]);
I tried to write a function inside the service and access it but even then it is showing MyService.permissionData is not a function.
Can anyone explain where I am going wrong?
I am trying to perform three tasks in the above code.
Get the Permissions array from the server
Dont create the directive till you get data.
hide or show elements based on permission.
My HTML code for this is:
<button class="btn btn-primary" allowed="1002">ADD SOMETHING</button>
Please do reply if you got any suggestions.

Try removing MyService from these two lines:
return function(scope, element, attr, MyService) {
scope.$on('MyServiceReady', function(MyService) {
You've already injected MyService into your directive, you don't pass it on the link function or your event handler.
Now that you've fleshed out what it is you're trying to do in your question I think I have a better answer for you to look at. If I'm reading this right, you are getting an array of integers that correspond to the allowed attribute on your buttons. If the array doesn't contain the value in allowed then the button should not be visible.
Here is a new version of your directive:
.directive('allowed', function(MyService) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.$on('MyServiceReady', function() {
var allowed = false;
angular.forEach(MyService.data, function(item) {
if(attrs.allowed === item){
allowed = true;
}
});
if(!allowed){
element.addClass('hidden');
}
});
}
}
})
This requires a hidden class in your CSS with display: none;. Here's a working JSFiddle to illustrate the directive. I had to fake the $http call to your API. A downside to this approach is that the buttons are visible while the service is calling your API. It might be better to hide them by default and then show them if the user is allowed instead of vice versa.

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

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

Custom angularjs module. Communicate between directives

I'm relatively new to Angularjs, Till now I have resolved my problems with angularjs by searching a lot on internet but I can't resolve this. Hope anyone can help me with ideas and better knowledge of angularjs.
I'm trying to make my first custom directive and I need communicate two directives in the same module through parent controller. When I try to use the require attribute I always have an error: Error: $compile:ctreq
Missing Required Controller.
Am I making something wrong?
(function(){
'use strict';
var INTERVAL_DELAY = 100;
var SCROLL_DELTA = 50;
angular.module('vm.hidemenu',[])
.controller('vmHideMenuTopCtrl',function(){
return{
sayHello : function(){
console.log('Hello man!');
}
}
})
.directive('vmHideMenuTop',[function(){
return {
restrict: "A",
replace: true,
template: "<h1>Hello World</h1>",
link: function(scope, element, attrs){
},
controller: 'vmHideMenuTopCtrl'
}
}])
.directive('vmScrollableArea',['$window',function($window){
return {
restrict : "A",
require : "^vmHideMenuTop",
link : function(scope,element,attrs,menuCtrl){
var e = angular.element(element[0]);
var isScrolling = false;
var lastScrollPos = 0;
var scrolling; // timeout
e.bind('scroll',function(event){
var obj = event.target;
var scrollTop = obj.scrollTop;
isScrolling = true;
$window.clearTimeout(scrolling);
if(scrollTop > lastScrollPos ){
console.log('scroll Down');
}else{
console.log('scroll UP');
}
scrolling = $window.setTimeout(function(){
isScrolling = false;
lastScrollPos = obj.scrollTop;
menuCtrl.sayHello();
},INTERVAL_DELAY);
})
}
}
}]);
}());
I've tried other ways to achieve this, like firing events and try to hear those events. Thats only works if I fire the events through $rootScope. I read that this is a bad practice so i don't know how to achieve this.
The idea behind the code is to have an scrollable area that can communicate with the top navbar to hiding or showing it, just like mobile apps do.
Sorry if I can't express myself in the best way, I'm not english native.
Thanks for your consideration and help!
My advice would be to use a separate controller for each directive and a service to share the data. You could use an observer pattern like so:
app.service('myService', function () {
this.data = [];
var observers = [];
var self = this;
this.addObserver = function (callback) {
observers.push(callback);
};
var notifyObservers = function () {
angular.forEach(observers, function (callback) {
callback(self.data);
});
};
this.addData = function (data) {
this.data.push(data);
notifyObservers();
}
});
and inside the controllers:
myService.registerObserverCallback(function (data) {
$scope.myData = data;
console.log(data);
});
Or if you just wanted to use events with the $broadcast inside the service when data is updated and inside a controller use $on to request the updated data.

AngularJS Error: [$injector:unpr] Unknown provider:

I am getting an unknown provider error when attempting to launch an angular bootstrap modal window from my app by clicking on an image. I launch the same type of modal successfully elsewhere in the app.
Here is a screenshot of the error in the debugger
Is there something wrong with my controller code below? I looked at several other unknown provider error posts on stack, and to my knowledge I'm doing things properly.
app.controller('ModalInstanceCtrl', function($scope, $modalInstance, items,
removeVideoFromCart, removeLiteratureFromCart, productHasItems, cartItemHasVideos,
cartItemHasLiterature, getCartMailToBody, cartMailtoLink, logSentItems) {
$scope.items = items;
$scope.ok = function() {
$modalInstance.close($scope.test);
};
$scope.removeVideoFromCart = function (video, familyIndex, index) {
removeVideoFromCart(video, familyIndex, index);
$scope.cartMailtoLink = getCartMailToBody(); //update the mailto link body to remove video links
}
$scope.removeLiteratureFromCart = function (literature, familyIndex, index) {
removeLiteratureFromCart(literature, familyIndex, index);
$scope.cartMailtoLink = getCartMailToBody(); //update the mailto link body to remove lit links
}
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
$scope.productHasItems = function(index) {
return productHasItems(index);
}
$scope.cartItemHasVideos = function(index) {
return cartItemHasVideos(index);
}
$scope.cartItemHasLiterature = function (index) {
return cartItemHasLiterature(index);
}
$scope.getCartMailToBody = function () {
getCartMailToBody();
}
$scope.cartMailtoLink = getCartMailToBody();
$scope.logSentItems = function () {
logSentItems();
}
});
Thank you very much for your time. Let me know if you need more information or if I am being unclear.
I'm going to assume that app points to a declaration of that module that is defined at the root of your app e.g. in app.js:
app = angular.module('app', []);
And that you're including each dependency within your index.html e.g. after any angular scripts and app.js
<script src="yourDependency.js"></script>
In terms of the controller code itself, you don't need to assign to $scope a property that contains a function that calls the removeVideoFromCart service within your 'ModalInstanceCtrl' controller, because then you will still need to call that wrapper function again (which it currently looks like you are not doing).
You can just call the method within the controller rather than wrap it in a function e.g.
$scope.removeVideoFromCart = removeVideoFromCart(video, familyIndex, index);
or just call the service e.g. if you don't need to bind the data to the UI like sending form data that on success just redirects elsewhere (although in your case it looks like you do want to bind the data to the UI):
removeVideoFromCart(video, familyIndex, index);
It's not clear from your code where the parameters for each service originate from. Are they within the items object? e.g.
var video, familyIndex, index
vm.items = items;
video = items.video;
familyIndex = items.familyIndex;
index = items.index;
In terms of style, I prefer not assigning the module instances to a variable and instead use the setter syntax (following [John Papa's] (https://github.com/johnpapa/angular-styleguide#modules) styleguide, but also included in Todd Motto's), like so:
angular
.module('app')
.controller('ModalInstanceCtrl', ModalInstanceCtrl);
ModalInstanceCtrl.$inject['your', 'dependencies', 'go', 'here']
function ModalInstanceCtrl(/*dependencies here as parameters e.g.*/, removeVideoFromCart) {
var vm = this; // use in place of $scope and clarifies the context of the this keyword
vm.items = items;
video = items.video;
familyIndex = items.familyIndex;
index = items.index;
$scope.removeVideoFromCart = removeVideoFromCart(video, familyIndex, index);
$scope.removeLiteratureFromCart = removeLiteratureFromCart(literature, familyIndex, index);
//etc
});
NB: I would prefer a facade into all of those methods e.g. clearCartAndCloseModal('other', 'services') to hide all of the implementation details from the controller. This also makes it easier to create one controller per view that is in turn easier to test beacuse you have pushed all logic into the services. But I'm not clear from your code whether there is any relationship between each of the services.
#Claies #ritesh I was typing a long edit with responses to the questions when I happened upon my solution. I had multiple functions that opened modal windows using ModalInstanceController. For example, here are two:
$scope.open = function(size) {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
items: function() {
return $scope.selectedVideo3;
}
}
});
modalInstance.result.then(function(selectedItem) {
$scope.selected = selectedItem;
}, function() {
$log.info('Modal dismissed at: ' + new Date());
});
};
$scope.openCart = function(size) {
var modalInstance = $modal.open({
templateUrl: 'myAttachmentModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
items: function() {
return "";
},
removeVideoFromCart: function() {
return $scope.removeVideoFromCart;
},
removeLiteratureFromCart: function() {
return $scope.removeLiteratureFromCart;
},
productHasItems: function() {
return $scope.productHasItems;
},
cartItemHasVideos: function() {
return $scope.cartItemHasVideos;
},
cartItemHasLiterature: function() {
return $scope.cartItemHasLiterature;
},
getCartMailToBody: function() {
return $scope.getCartMailToBody
},
cartMailtoLink: function() {
return $scope.cartMailtoLink
},
logSentItems: function () {
return $scope.logSentItems;
}
}
});
modalInstance.result.then(function(selectedItem) {
$scope.selected = selectedItem;
}, function() {
$log.info('Modal dismissed at: ' + new Date());
});
};
I only use most of the dependencies for ModalInstanceController in the openCart function, so I didn't add all of the dependency function declarations to my other open methods (You can see in the resolve for the $scope.open method above I only declare items and no functions).
I needed to declare all of these functions like I did in $scope.openCart and it fixed my problem.
Thank you for reaching out.
My case was UpperCase LowerCase problem in the injected service name.

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