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
Related
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.
I have an Angularjs directive 'ExampleDirective' which has the controller 'ExampleController'. The controller defines two Promise objects where each Promise object makes an Http GET request and returns the response.
In the directive, we get the response data from the promise objects and process them to render the directive.
ExampleDirective gets instantiated twice within the same view and each instance makes it's own Http GET requests. This causes performance issues on the front end due to two requests sent at the same time to make expensive database calls and read from the same table as well.
Controller:
angular.module('exampleModule')
.constant("EXAMPLE_URL", "{% url 'example:get_example' %}")
.controller('exampleCtrl', ['$scope', '$http', 'EXAMPLE_URL', exampleCtrl]);
function exampleCtrl($scope, $http, EXAMPLE_URL) {
$scope.examplePromise = $http.get(EXAMPLE_URL).then(function(response) {
return response.data;
});
}
Directive:
angular.module('exampleModule')
.directive('exampleDirective', ['exampleFactory', 'STATIC_URL', '$http', '$window', exampleDirective]);
function exampleDirective(exampleFactory, STATIC_URL, $http, $window) {
return {
scope: {
title:'#?',
loadingImage:'#?',
},
restrict: 'AE',
templateUrl: STATIC_URL + 'example/example-template.html',
controller: "exampleCtrl",
link: function (scope, element, attrs) {
//add default options:
if (!scope.title) {
scope.title = 'Example Title';
}
if (!scope.loadingImage) {
scope.loadingImage = '';
}
scope.examplePromise.then(function(data) {
scope.exampleData = data;
// do something
});
}
};
}
Is there a way to instantiate a directive multiple times but not have to make the Http GET requests in the controller twice?
UPDATE
This is what I did, I added a service as suggested in the answer.
Service:
angular.module('chRatingsModule')
.factory('chExampleFactory', ['$http', 'EXAMPLE_URL', chExampleFactory]);
function chExampleFactory($http, EXAMPLE_URL) {
var api = {}
var promise = null;
api.examplePromise = examplePromise;
function examplePromise() {
if (promise == null) {
promise = $http.get(EXAMPLE_URL).then(function(response) {
return response.data;
});
}
return promise;
}
return api;
}
Updated Directive:
angular.module('exampleModule')
.directive('exampleDirective', ['exampleFactory', 'STATIC_URL', '$http', '$window', exampleDirective]);
function exampleDirective(exampleFactory, STATIC_URL, $http, $window) {
return {
scope: {
title:'#?',
loadingImage:'#?',
},
restrict: 'AE',
templateUrl: STATIC_URL + 'example/example-template.html',
link: function (scope, element, attrs) {
exampleFactory.examplePromise.then(function(data) {
scope.exampleData = data;
// do something
});
}
};
}
First solution, probably the best one: don't make the call from the directive, which should just be a graphical element. Do the call from the controller, and pass the data as argument to both directives.
Second solution, use a service in the directive, and always return the same promise:
myModule.factory('myService', function($http) {
var promise = null;
var getData = function() {
if (promise == null) {
promise = $http.get(...).then(...);
}
return promise;
};
return {
getData: getData
};
});
The controller defines two Promise objects where each Promise object
makes an Http GET request and returns the response.
Change to:
The SERVICE defines two Promise objects where each Promise object
makes an Http GET request and returns the response.
The service then can remember that it has already done the GET(s) and just return their result every subsequent time it is asked for them.
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();
}
});
I have a directive which is working correctly when coded this way:
return {
restrict: 'E',
transclude: true, //transclude makes the contents of a directive with this option have access to the scope outside of the directive rather than inside. So we can access the vm calling this.
scope: { // As the name suggests, the isolate scope of the directive isolates everything except models that you've explicitly added to the scope: {} hash object. This is helpful when building reusable components because it prevents a component from changing your model state except for the models that you explicitly pass in.
priceinformation: '=priceinformation'
},
controller: ['$scope','PriceCalculatorService', '$state', function ($scope,PriceCalculatorService, $state) {
var controller={
_$state:$state,
pricinginfo: [],
};
//console.log(PriceCalculatorService);
//debugger;
PriceCalculatorService.getPricingInfo()
.success(function (result) {
controller.pricinginfo = result;
})
.error(_handleError);
function _handleError(error) {
controller.error = error;
controller._$state.go('error-404');
}
$scope.controller = controller;
return controller;
}],
templateUrl: 'app/common/directives/views/price-calculator.html',
However, I want to isolate the controller, and do it this way instead:
(function (app) {
'use strict';
// #see https://docs.angularjs.org/guide/directive
// #important: Requires an object named pricinginformation is pushed into the directive.
var PriceCalculatorControl = (function () {
// scope is an isolated part of $scope limited this control only so we don't put a $
var PriceCalculatorControl = ['scope','PriceCalculatorService', function PriceCalculatorControl(scope,PriceCalculatorService) {
var control = {
total: 0,
subscriptionUnitprice: 100,
totalcal: function totalcal() {
if (scope.priceinformation.subscriptioninfo.type == 'Daily') {
control.subscriptionUnitprice = 100;
}
else if (scope.priceinformation.subscriptioninfo.type == 'Monthly') {
control.subscriptionUnitprice = 500;
}
else {
control.subscriptionUnitprice = 100;
}
control.total = control.subscriptionUnitprice * scope.priceinformation.subscriptioninfo.period * scope.priceinformation.subscriptioninfo.subjectsselected;
}
};
//kicking off the service
PriceCalculatorService.getPricingInfo()
.success(function (result) {
controller.pricinginfo = result;
})
.error(_handleError);
function _handleError(error) {
controller.error = error;
controller._$state.go('error-404');
}
// bootstrap
/* When injecting stuff, clean them up here. Like a timer that's running needs to be stopped.
element.on('$destroy', function() {});*/
//register on scope
scope.PriceCalculatorControl.PriceCalculatorControl;
}];
PriceCalculatorControl[2]();
return {
restrict: 'E',
transclude: true, //transclude makes the contents of a directive with this option have access to the scope outside of the directive rather than inside. So we can access the vm calling this.
scope: { // As the name suggests, the isolate scope of the directive isolates everything except models that you've explicitly added to the scope: {} hash object. This is helpful when building reusable components because it prevents a component from changing your model state except for the models that you explicitly pass in.
priceinformation: '=priceinformation'
},
//controller: ['$scope','PriceCalculatorService', '$state', function ($scope,PriceCalculatorService, $state) {
// var controller={
// _$state:$state,
// pricinginfo: [],
// };
// //console.log(PriceCalculatorService);
// //debugger;
// PriceCalculatorService.getPricingInfo()
// .success(function (result) {
// controller.pricinginfo = result;
// })
// .error(_handleError);
// function _handleError(error) {
// controller.error = error;
// controller._$state.go('error-404');
// }
// $scope.controller = controller;
// return controller;
//}],
templateUrl: 'app/common/directives/views/price-calculator.html',
link: PriceCalculatorControl
};
});
// Register the directive
app.directive('priceCalculator', PriceCalculatorControl);
})(angular.module('app.common'));
This second method is not working. scope.PriceCalculatorControl.PriceCalculatorControl; says PriceCalculatorControl is not define.
PriceCalculatorService.getPricingInfo() says getPricingInfo is not defined.
Note: the service is working correctly. I guess the problem has to do with the dependency injection, but can't figure out what. Kindly enlighten me.
Thanks.
Try this:
(function (app) {
'use strict';
// #see https://docs.angularjs.org/guide/directive
// #important: Requires an object named pricinginformation is pushed into the directive.
var PriceCalculatorControl = (['PriceCalculatorService', function (PriceCalculatorService) {
// scope is an isolated part of $scope limited this control only so we don't put a $
var PriceCalculatorControl = function PriceCalculatorControl(scope) {
var control = {
total: 0,
subscriptionUnitprice: 100,
totalcal: function totalcal() {
if (scope.priceinformation.subscriptioninfo.type == 'Daily') {
control.subscriptionUnitprice = 100;
}
else if (scope.priceinformation.subscriptioninfo.type == 'Monthly') {
control.subscriptionUnitprice = 500;
}
else {
control.subscriptionUnitprice = 100;
}
control.total = control.subscriptionUnitprice * scope.priceinformation.subscriptioninfo.period * scope.priceinformation.subscriptioninfo.subjectsselected;
}
};
//kicking off the service
PriceCalculatorService.getPricingInfo()
.success(function (result) {
controller.pricinginfo = result;
})
.error(_handleError);
function _handleError(error) {
controller.error = error;
controller._$state.go('error-404');
}
// bootstrap
/* When injecting stuff, clean them up here. Like a timer that's running needs to be stopped.
element.on('$destroy', function() {});*/
//register on scope
scope.PriceCalculatorControl.PriceCalculatorControl;
};
//PriceCalculatorControl[2]();
return {
restrict: 'E',
transclude: true, //transclude makes the contents of a directive with this option have access to the scope outside of the directive rather than inside. So we can access the vm calling this.
scope: { // As the name suggests, the isolate scope of the directive isolates everything except models that you've explicitly added to the scope: {} hash object. This is helpful when building reusable components because it prevents a component from changing your model state except for the models that you explicitly pass in.
priceinformation: '=priceinformation'
},
//controller: ['$scope','PriceCalculatorService', '$state', function ($scope,PriceCalculatorService, $state) {
// var controller={
// _$state:$state,
// pricinginfo: [],
// };
// //console.log(PriceCalculatorService);
// //debugger;
// PriceCalculatorService.getPricingInfo()
// .success(function (result) {
// controller.pricinginfo = result;
// })
// .error(_handleError);
// function _handleError(error) {
// controller.error = error;
// controller._$state.go('error-404');
// }
// $scope.controller = controller;
// return controller;
//}],
templateUrl: 'app/common/directives/views/price-calculator.html',
link: PriceCalculatorControl
};
}]);
// Register the directive
app.directive('priceCalculator', PriceCalculatorControl);
})(angular.module('app.common'));
Also I'm not sure what this is supposed to achieve:
PriceCalculatorControl[2]();
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;
});
}