Best way to communicate between directive instances - angularjs

I have two instances of a directive. Is there a way communicate between the two instances?
Or can I set a global variable that all instances will share the same value.
I have tried to store the value in a service. When the value in one instance change, then other instances will be manually updated. But I am not sure whether this is the best way to it or not.
Thanks.

The directive factory itself is a singleton. Anything you declare outside of the definition object will be global to all instances. As each instance has it's own scope, instance-specific data should go in the scope. So, something like this:
angular.module("myApp", [])
.directive("myDir", function() {
var myGlobal = 0;
return {
template: '<div>Global: {{getGlobal()}}, Local: {{local}} -- Increment</div>',
scope: {},
link: function(scope, element, attrs) {
scope.local = 0;
scope.increment = function() {
scope.local++;
myGlobal++;
}
scope.getGlobal = function() {
return myGlobal;
}
}
}
});
http://jsfiddle.net/7YwDS/

Related

AngularJS: Best way to handle global variables ($rootScope or global service?)

I'm torn between using $rootScope and a global service.
$rootScope feels dirty and comes with all the problem of global variables, so I wanted to use a global service but the Angular FAQ says:
Of course, global state sucks and you should use $rootScope sparingly,
like you would (hopefully) use with global variables in any language.
In particular, don't use it for code, only data. If you're tempted to
put a function on $rootScope, it's almost always better to put it in a
service that can be injected where it's needed, and more easily
tested.
Conversely, don't create a service whose only purpose in life is to
store and return bits of data.
...and that's what I want to use the service for.
For example, I have a navbar that when toggled on mobile screens should add a class to the body and html elements. I handle this with a directive and a controller.
NavbarController:
function NavbarController($rootScope) {
var vm = this;
vm.toggleNav = toggleNav;
$rootScope.navCollapsed = false;
function toggleNav() {
$rootScope.navCollapsed = !$rootScope.navCollapsed;
}
}
As you can see there, I'm using rootScope for the navCollapsed value and in my directive I do this:
function navOpen($rootScope) {
return {
restrict: 'A',
link: function(scope, element) {
scope.$watch(function () {
return $rootScope.navCollapsed;
}, function () {
if ($rootScope.navCollapsed)
element.addClass('nav-open');
else
element.removeClass('nav-open');
});
}
};
}
It works fine, but using $rootScope feels dirty. I would much rather do something like this:
NavbarController:
function NavbarController(globalService) {
var vm = this;
vm.toggleNav = toggleNav;
globalService.navCollapsed = false;
function toggleNav() {
globalService.navCollapsed = !globalService.navCollapsed;
}
}
And in the directive:
function navOpen(globalService) {
return {
restrict: 'A',
link: function(scope, element) {
scope.$watch(function () {
return globalService.navCollapsed;
}, function () {
if (globalService.navCollapsed)
element.addClass('nav-open');
else
element.removeClass('nav-open');
});
}
};
}
Which does the exact same thing except now the value is in a single location and can't be unintentionally altered elsewhere which is better, but according to AngularJS team that is bad practice because it's "bits of data".
What is the best way of going about this? I have other variables with similar functionality that need to be global (able to be accessed in any controller for manipulation), without having to use $rootScope.

How to return multiple values in scope.watch?

I am trying to return multiple values from $scope.watch function.
angular.module("MainApp")
.directive('isActiveNav', [ '$location', function($location) {
return {
restrict: 'A',
link: function($scope, el, attrs) {
$scope.location = $location;
$scope.$watch(function() {
return (el.parent().parent().parent().parent().hasClass('cbp-small'),location.path());
}, function(hasClas, currentPath) {
setTimeout(function(){
console.log(hasClas, currentPath);
},0)
});
}
};
}]);
But this is giving me this error Uncaught SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode.
I am trying to watch multiple values here:
1. Current-Url of the APP
2. If a certain element has a class named "cbp-small"
I have tried $watchCollection and $watchGroup also but weren't able to make them work either. And thus am trying to return multiple values form scope.watch func.
The first argument will not accept a two values in ( ) syntax. Instead you want to store both values you want to watch and return in either an object or an array.
angular.module("MainApp")
.directive('isActiveNav', [ '$location', function($location) {
return {
restrict: 'A',
link: function($scope, el, attrs) {
$scope.location = $location;
$scope.$watch(
function() {
return {hasPath: el.parent().parent().parent().parent().hasClass('cbp-small'), currentPath: location.path()};
},
function(newPathObject, oldPathObject) {
if (!angular.equals(newPathObject, oldPathObject)) {
setTimeout(function(){
console.log(newPathObject.hasClass, newPathObject.currentPath);
},0)
};
},
true
});
}
};
}]);
You also want to add true as a third argument for objectEquality == true. According to Angular documentation:
When objectEquality == true, inequality of the watchExpression is determined according to the angular.equals function. To save the value
of the object for later comparison, the angular.copy function is used.
This therefore means that watching complex objects will have adverse
memory and performance implications.
Also, when working with $watch, you want to prevent the callback from triggering upon instansiation of the object by wrapping it in an if statement and checking if the object values have changed with angular.equals. You can reference this using this plunker.
You can concatenate values:
return el.parent().parent().parent().parent().hasClass('cbp-small').toString() + "&&&" + location.path();
So then will be generated a string like "true&&&/.../.../"
Angular will dirty check this string, if any of values will change that string will change so callback will be invoked
and in callback write
function(newVal) {
var args = newVal.split('&&&');
var hasClas = args[0]==="true", currentPath = args[1];
setTimeout(function(){
console.log(hasClas, currentPath);
},0
});

Injecting data objects into a directive factory in AngularJS

So I have a directive that takes in data objects as an argument into the scope. The problem is that I handle all my data in my service layer.
So this is some normal non-directive code:
angular.module('app').factory('appFactory', ['appValues', function(appValues) {
var getStuff = function() { return appValues.stuff; };
}]);
But if want to reuse the factory inside a directive and get appValues as an argument:
angular.module('app').directive('myDir', [function() {
return {
...
scope: {
values: '='
}
....
};
}]);
But this puts it on the scope and not into my data layer. So now I need to send the values object to every function call in my directive factory:
angular.module('app').factory('myDirFactory', [function() {
var getStuff = function(values) { return values.stuff; };
}]);
Is there any good pattern to solve this and keep data in the data-layer and bypass the scope/controller?
Also, the factory will be a singleton shared amongst instances of the directive? How should I solve that then? Create a new injector somehow? Submit to putting lots of data object logic into the controller (which I've been thought not to do)?
It was a while ago, and I guess that a simple soultion is simply to provide an function initialize(value) {... return {...};} and then the returned object has access to the value argument without providing it as a parameter everywhere:
angular.module('myDir').factory('myDirFactory', [function() {
var initialize = function(values) {
var getStuff = function() {
return values;
};
return {
getStuff: getstuff;
};
};
return {
initialize: initialize
};
}]);

Angular call a service on run

I have a service for handle the menu of my application I dont want call from any controller, where is the best place for call my service
my service has a register method
// sample
menuService.register({name: "Person", label: "Person", url: "/persons"});
menuService.register({name: "Company", label: "Companies", url: "/companies"});
is defined like
app.service('MenuService', ['$rootScope', function($r) { /*...*/ }
Note: my service $rootScope.$emit and is listen by a directive and depends of $rootScope and $location
you should use .run block for that, but keep in mind you cannot inject .provider to run block
yourApp.run(function ($rootScope, $location) {
// your code goes herer
});
from docs
Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application. It is executed after all of the services have been configured and the injector has been created. Run blocks typically contain code which is hard to unit-test, and for this reason should be declared in isolated modules, so that they can be ignored in the unit-tests.
You could expose the menus as a model in the MenuService.
app.service('MenuService', ['$rootScope', function($r) {
var svcModel = {
menus: []
};
var registerMenu = function(menu) {
svcModel.menus.push(menu);
};
/*...*/
return {
model: svcModel,
register: registerMenu
/*...*/
};
}
And then access that model directly on the directive:
app.directive('menuDirective', ['MenuService', function(MenuService) {
return {
/*...*/
link: function(scope, element, attrs) {
/*...*/
scope.menus = MenuService.model.menus;
}
}]);

Using both controller and require option inside angular directive

I'm looking for a way to access both controllers inside the directive.
Currently it's not working and I don't know why or if it's even possible.
If I use both require and controller option, inside the link function ctrl property refers to whatever I requested via the require option.
I can't think of a way to access the controller inside the link function when the require option is present.
It seems that these two properties are mutually exclusive ?
angular.module('moduleName').directive('directiveName', [function () {
return {
controller: 'MediaController',
require:'ngController',
link: function (scope, element, attributes, ctrl) {
// I need access to both controllers here
}
}
}]);
If you want both controllers, then require both controllers:
angular.module('moduleName').directive('directiveName', [function () {
return {
controller: MediaController,
require:['directiveName', 'ngController'],
In this case ctrl is an array containing the two controllers.
Without really knowing why you need to access both controllers, I can only offer minimal advice here. My suggestion would be to create a service to handle cross controller needs. Services are singletons and they support data binding. Services are my preference for cross controller work every day. For example:
App.controller('Ctrl1', function Ctrl1 ($scope, TestService) {
$scope.someValue = TestService.getValue();
});
App.controller('Ctrl2', function Ctrl2 ($scope, TestService) {
$scope.someValue = TestService.getValue();
});
App.factory('TestService', function() {
var myVal = "I Bound";
return {
getValue: function() {
return myVal;
}
}
});
This method allows you to abstract a controllers need to directly access another controller. Your services can be pulled into these directives or other services too. I hope this helps a bit.
Thanks,
Jordan

Resources