Consider this snippet from AngularJS by Brad Green.
var directives = angular.module('guthub.directives', []);
directives.directive('butterbar', ['$rootScope',
function ($rootScope) {
return {
link: function (scope, element, attrs) {
element.addClass('hide');
$rootScope.$on('$routeChangeStart', function () {
element.removeClass('hide');
});
$rootScope.$on('$routeChangeSuccess', function () {
element.addClass('hide');
});
}
};
}]
);
directives.directive('focus', function () {
return {
link: function (scope, element, attrs) {
element[0].focus();
}
};
});
Notice that for the "butterbar" directive he passes in an array where the first item is just a string with the dependency name "$rootScope", and the second item is a function. That function declares a dependency on $rootScope. Why do we repeat ourselves here? Especially when it appears to be possible to just do this:
directives.directive('butterbar', function ($rootScope) {
return {
link: function (scope, element, attrs) {
element.addClass('hide');
$rootScope.$on('$routeChangeStart', function () {
element.removeClass('hide');
});
$rootScope.$on('$routeChangeSuccess', function () {
element.addClass('hide');
});
}
};
});
I'm guessing that the dependency name being a string has some sort of significance. Can anyone tell me why they do this throughout the book (and not just for directives)?
When JavaScript is minified, the names of parameters are often changed to something shorter such as a. This would break dependency injection.
If you use an array, Angular knows what to inject and where to inject it. This works with the array because the string elements of the array are not modified by minification.
In this example:
app.controller('test', function( $scope, $someProvider ) {
});
minified code might look something like this:
app.controller('test',function(a,b){});
This would no longer work since Angular will not know what to inject, whereas with this:
app.controller('test', ['$scope', '$someProvider', function( $scope, $someProvider) {
}]);
the minified code might end up like this:
app.controller('test',['$scope','$someProvider',function(a,b) {}]);
This would still work because Angular still knows what to inject. See the note on minification in the Angular tutorial.
Usually I just add the array style when I'm ready for production.
Related
I'm looking for a good design pattern to provide angular directives render acording to some global specified parametrs.
For example, I have some factory called "Settings", that holds the value "directiveColor: red".
When i do the link in my directive, I ask the Settings about my directiveColor value. Everything is working fine - I got red and put element on the page. But I have hundreds of this elements on the page, and every directive before render ask for settings... I think it's not very good way.
What will you recomend?
UPD
factory
app.factory('Settings', function() {
var data = {
//...
directiveColor: red //set by user
}
//...
GetSettings : function () {return data}
}
directve
app.directive('wdMyDirective', ['Settings', function(Settings) {
return {
restrict: 'E',
link: function(scope, elem, attr) {
scope.data = {
//...
color: Settings.GetSettings().directiveColor
};
};
}]);
//later "color" used in template through the scope
That's how it works for now (works fine). But every time, when I render directive (many many times on the page, ngRepeat for table data), my directive ask for Settings to choose its color. I think, it is not good. Or maybe not?
There are two considerations here. First, you are right that it is not optimal, and directive actually provides a way to do that call once, read about Compile-PreLink-PostLink in angular directives. Basically you want this call in Compile step if it is the same for all directives in your app.
Second consideration is that Settings.GetSettings().directiveColor will give really really small overhead if GetSettings() returns just an object that you only create once ( and that is what happened as angular factories are singletons )
In your case you can do
app.factory('Settings', function() {
var data = {
directiveColor: 'red' //set by user
}
return {
GetSettings : function () {return data}
}
})
app.directive('wdMyDirective', ['Settings', function(Settings) {
return {
restrict: 'E',
compile: function(elem, attrs) {
var color = Settings.GetSettings().directiveColor
return function postLink(scope, elem, attr) {
scope.data = {
color: color
};
}
}
}
}])
instead of declaring link property on directive.
I've created some directives which have some functions, something like this:
myCtrl.directive('myDirective', function () {
return {
restrict: 'E',
scope: {
'something': '='
},
link: function (scope, el, attr) {
function func1(){
//some stuff
scope.$apply();
}
function func2(){
//some stuff
scope.$apply();
}
function func3(){
//some stuff
scope.$apply();
}
//...
}
}
});
I have to call scope.$apply() in all the functions to update the view. In addition I don't want to define them in the controller. I don't know if there is a trick to avoid this kind of pattern. It is working but I think it's not a good solution.
UPDATE
There are more than 10 directives which we've created for some shapes, e.g. rectangle, circle, square etc. In these functions, which I called $apply in them, some methods like drag and scale are implemented. So I need to call $apply to modify changes in model and consequently the view will be updated. I don't know how can I make them able to aware scope automatically unless I write about 100 functions in a controller!
In addition, I'm using some 3rd party libraries like d3.js. Some event like clicked are bounded for calling these functions.
The real truth is that you need to call $scope.$apply() whenever you're changing something outside angular digest cycle. Angular detects the changes by dirty checking during the digest cycle.
$timeout is also doing $apply ($rootScope.$apply())
Check Angular source code.
What I can suggest is create helper function which will handle that. Example:
var ngAware = function (fnCallback) {
return function () {
var result = fnCallback.apply(this, arguments);
$scope.$apply();
return result;
};
};
// usage
function func1 () {
// impl...
}
var ngFunc1 = ngAware(func1);
// or
var func2 = ngAware(function func2 () {
// impl...
});
Sure you need apply()?
apply() is used when integrating 3rd party libraries (such as jQuery). In most cases, if you are not hooking up 3rd party javascript libraries, you can avoid $apply() by using the build in binding mechanism
For instance, the following snippet will call func1 using a 3 seconds timeout callback. then, func1 will manipulate the scope variable something and the view will be updated without the need to trigger $apply()
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.value = 'Hello';
});
app.directive('myDirective', function ($timeout) {
return {
restrict: 'E',
template:'<h1>{{something}}</h1>',
scope: {
'something': '='
},
link: function (scope, el, attr) {
function func1(){
scope.something += ' ' + scope.something;
}
$timeout(func1, 3000);
}
}
});
html
<body ng-controller="MainCtrl">
<my-directive something="value"></my-directive>
</body>
http://plnkr.co/edit/42VYs0W2BC5mjpaVWQg3?p=preview
perhaps if you explain more about your use case i could expand my answer to your specific needs
wraps the function within $scope.$apply()
check this :
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.value = 'Hello';
});
app.directive('myDirective', function ($timeout) {
return {
restrict: 'E',
template:'<h1>{{something}}</h1>',
scope: {
'something': '='
},
link: function (scope, el, attr) {
scope.$apply(function(){
function func1(){
//some stuff
}
function func2(){
//some stuff
}
function func3(){
//some stuff
}
})
}
}
});
If you have common functionality in you directives (circle, square, etc) you should group it. By example, we could have a directive to drag
.directive('ngDraggable', [function () {
return {
link: function ($scope) {
var stopHandler = function () {
$scope.apply(function () {
// Code to handler the end of the dag event
// Notify the other directive.
$scope.$emit('dragStopped', data);
});
};
// Initialization of the drag functionality.
}
}
}])
You can used as
<div ng-circle ng-draggable></div>
So, you earn three thing. First, the circle directive can do changes in its scope without call to $apply, because $emit already is in the digest cycle. Second, you only use $apply where is necessary. And third, you improve the performance by use $apply in scopes most small and with less children.
You could merge this answer with the answer of #sasko_dh
Maybe the Observer Pattern could be you useful.
so I have this custom directives that you could see below.
myApp.directive('myDirective', function (testService) {
return {
restrict:'EA',
link:function (scope, element, attr) {
//defined the object
var object = new object();
testService.setObject(object);
}
}
});
myApp.directive('mySecondDirective', function (testService) {
return {
restrict:'EA',
link:function (scope, element, attr) {
//call the variable from previous custom directive
console.log(testService.getobject()); -> always return undefined
}
}
});
and this is the html structure where I used the directives above.
<my-directive></my-directive>
<my-second-directive></my-second-directive>
there I want to retreive the object that contains new object() from previous custom directive, but it always return an undefined I wonder how could I do this without using require nor isolated scope as well.. could you guys help me ?
UPDATE
I create a service to provide the facility to set and retreive the object and apparently it returned undefined because I set the custom direcitve this way
<my-second-directive></my-second-directive>
<my-directive></my-directive>
and this is the service
define(
['services/services'],
function(services)
{
'use strict';
services.factory('testService', [function() {
var me = this;
var testObject = '';
return {
setObject: function(object) {
me.testObject = object;
},
getObject: function() {
return me.testObject;
}
}
}
]);
}
);
the thing is that I actually set the html markup like I already mentioned above which is
<my-second-directive></my-second-directive>
<my-directive></my-directive>
so could you give me some advice on how should I do this ?
note* that the passing object actually worked I prefer using services because it will easy to mantain latter. The question is how do I make the object accessible from another directive even though the initiate install of the object (set the object) in the directive that I defined at the html markup, as the last position of the html it self ?
UPDATE this is the PLUNKER that I've been made for you to understand the question it self
You could achieve this by firing a custom event and then listening for it in the second directive. Here's an updated plunker: http://plnkr.co/edit/512gi6mfepyc04JKfiep?p=info
Broadcast the event from the first directive:
app.directive('myDirective', function(testService) {
return {
restrict: 'EA',
link: function(scope, elm, attr) {
var object = {};
testService.setObject(object);
console.log('setting object');
scope.$broadcast('objectSet');
}
}
});
... and then listen for it on the second:
app.directive('mySecondDirective', function(testService) {
return {
restrict: 'EA',
link: function(scope, elm, attr) {
scope.$on('objectSet', function(){
console.log('retrieving object', testService.getObject());
})
}
}
});
You could also pass data along with the event, if you wanted to emit a specific piece of data to be picked up by the second directive.
1). Scope. Since you don't want to use controllers and isolated scope, then you can simply set this object as scope property.
myApp.directive('myDirective', function() {
return {
restrict: 'EA',
link: function(scope, element, attr) {
var object = {};
object.test = 21;
// set object as scope property
scope.object = object;
}
}
});
myApp.directive('mySecondDirective', function() {
return {
priority: 1, // priority is important here
restrict: 'EA',
link: function(scope, element, attr) {
console.log('Retrieve: ', scope.object);
}
}
});
Just make sure you are also defining priority on the second directive (only if both directive a applied to the same element) to make sure it's evaluated in proper turn (should be bigger then the one of myDirective, if omitted it's 0).
2). Service. Another simple solution is to use service. You can inject custom service into both directives and use it storage for you shared object.
Expanding from what #gmartellino said.
Anything that you wanted to do after listening to the event in second directive, can have a callBack method and use it.
app.directive('mySecondDirective', function(testService) {
return {
restrict: 'EA',
link: function(scope, elm, attr) {
// what if I created like this ?
// define the test variable
var test;
scope.$on('objectSet', function(){
//set the variable
test = testService.getObject();
console.log('retrieving object : ', testService.getObject());
//Anything that you wanted to do
//after listening to the event
//Write a callBack method and call it
codeToExecuteAsCallBack();
})
//then use this method to make a call back from the event
//and outside the event too.
var codeToExecuteAsCallBack = function(){
console.log(test);
}
codeToExecuteAsCallBack();
}
}
});
Updated plnkr link
What is the functional difference between the following code (in Widget Uno) using a directive definition object (I think it's called..?)...
angular.module("app").
directive("widgetUno", ["$http", function ($http) {
return {
// A whole bunch of crap going on here
},
templateUrl: "widgetUno.html"
};
}]);
...And this code in Widget Dos?
angular.module("app").directive('widgetDos', function($http) {
return function(scope, element, attrs) {
// A whole bunch of crap going on here
};
});
I'm trying to convert a directive that's like Widget Uno into Widget Dos, but where do I reference the templateUrl? Is this possible in Widget Dos?
Returning only a function in a directive is just a shorthand for the link function in the full definition.
If you are specifying something other than a link function (like templateUrl) then you need to write it the long way:
angular.module("app").
directive("widgetUno", ["$http", function ($http) {
return {
link: function(scope, element, attrs) {
// A whole bunch of crap going on here
},
templateUrl: "widgetUno.html"
};
}]);
This difference is actually documented here - http://docs.angularjs.org/guide/directive
The one returning the function is actually the shortcut for:
angular.module("app").directive('widgetDos', function($http) {
return {
link: function(scope, element, attrs) {
//...
};
}
});
Use it in case your directive doesn't need template, controller etc. Other than that, there's absolutely no functional difference between those two calling approaches.
It should work like this:
angular.module("app").directive('widgetDos', function($http) {
return {
templateUrl: "....",
link: function(scope, element, attrs) {
// A whole bunch of crap going on here
};
}
});
See also http://docs.angularjs.org/guide/directive (long version). There is an example.
So I have this directive called say, mySave, it's pretty much just this
app.directive('mySave', function($http) {
return function(scope, element, attrs) {
element.bind("click", function() {
$http.post('/save', scope.data).success(returnedData) {
// callback defined on my utils service here
// user defined callback here, from my-save-callback perhaps?
}
});
}
});
the element itself looks like this
<button my-save my-save-callback="callbackFunctionInController()">save</button>
callbackFunctionInController is for now just
$scope.callbackFunctionInController = function() {
alert("callback");
}
when I console.log() attrs.mySaveCallback inside my-save directive, it just gives me a string callbackFunctionInController(), I read somewhere that I should $parse this and it would be fine, so I tried to $parse(attrs.mySaveCallback) which gave me back some function, but hardly the one I was looking for, it gave me back
function (a,b){return m(a,b)}
What am I doing wrong? Is this approach flawed from the beginning?
So what seems like the best way is using the isolated scope as suggested by ProLoser
app.directive('mySave', function($http) {
return {
scope: {
callback: '&mySaveCallback'
}
link: function(scope, element, attrs) {
element.on("click", function() {
$http.post('/save', scope.$parent.data).success(returnedData) {
// callback defined on my utils service here
scope.callback(); // fires alert
}
});
}
}
});
For passing parameters back to controller do this
[11:28] <revolunet> you have to send named parameters
[11:28] <revolunet> eg my-attr="callback(a, b)"
[11:29] <revolunet> in the directive: scope.callback({a:xxx, b:yyy})
There are a lot of ways to go about what you're doing. The FIRST thing you should know is that the $http.post() is going to be called as soon as that DOM element is rendered out by the template engine, and that's it. If you put it inside a repeat, the call will be done for each new item in the repeater, so my guess is this is definitely not what you want. And if it is then you really aren't designing things correctly because the existence of DOM alone should not dictate queries to the backend.
Anyway, directly answering your question; if you read the albeit crappy docs on $parse, it returns you an evaluation expression. When you execute this function by passing the scope to evaluate on, the current state of that expression on the scope you passed will be returned, this means your function will be executed.
var expression = $parse(attrs.mySave);
results = expression($scope); // call on demand when needed
expression.assign($scope, 'newValu'); // the major reason to leverage $parse, setting vals
Yes, it's a little confusing at first, but you must understand that a $scope changes constantly in asynchronous apps and it's all about WHEN you want the value determined, not just how. $parse is more useful for a reference to a model that you want to be able to assign a value to, not just read from.
Of course, you may want to read up on creating an isolate scope or on how to $eval() an expression.
$scope.$eval(attrs.mySave);
You can use .$eval to execute a statement in the given scope
app.directive('mySave', function($http) {
return function(scope, element, attrs) {
$http.post('/save', scope.data).success(returnedData) {
// callback defined on my utils service here
// user defined callback here, from my-save-callback perhaps?
scope.$eval(attrs.mySaveCallback)
}
}
});
TD: Demo
If you want to share data between a directive and a controller you can use the two way binding
app.controller('AppController', function ($scope) {
$scope.callbackFunctionInController = function() {
console.log('do something')
};
$scope.$watch('somedata', function(data) {
console.log('controller', data);
}, true);
});
app.directive('mySave', function($http, $parse) {
return {
scope: {
data: '=mySaveData',
callback: '&mySaveCallback' //the callback
},
link: function(scope, element, attrs) {
$http.get('data.json').success(function(data) {
console.log('data', data);
scope.data = data;
scope.callback(); //calling callback, this may not be required
});
}
};
});
Demo: Fiddle
scope: {
callback: '&mySaveCallback'
}
Setting the scope explicitly could be a good solution but if you want the reach other parts of the original scope you can't because you have just overwritten it. For some reason, I needed to reach other parts of the scope too so I used the same implementation as ng-click do.
The use of my directive in HTML:
<div my-data-table my-source="dataSource" refresh="refresh(data)">
Inside the directive (without setting the scope explicitly):
var refreshHandler = $parse(attrs.refresh);
scope.$apply(function () {
refreshHandler( {data : conditions}, scope, { $event: event });
});
With this I can call the function in controller and pass parameters to it.
In the controller:
$scope.refresh= function(data){
console.log(data);
}
And it prints the conditions correctly out.
This worked for me
Inside the view script
<tag mycallbackattrib="scopemethod">
Inside the directive
$scope[attrs.mycallbackattrib](params....);
It is correctly called and params are passed, but maybe is not a best 'angular way' to work.
You should be using ng-click instead of creating your own directive.
app.directive('mySave', function($http, $parse) {
return {
scope: {
data: '=mySaveData',
callback: '&' //the callback
},
link: function(scope, element, attrs) {
$http.get('data.json').success(function(data) {
console.log('data', data);
if (scope.callback()) scope.callback().apply(data);
});
}
};
});