When to use worker and factory function in angularjs - javascript

i am learning angularjs from Pro angularjs by adam freeman.
This is an example he defined an directive in angularjs
myApp.directive("highlight", function () {
return function (scope, element, attrs) {
if (scope.day == attrs["highlight"]) {
element.css("color", "red");
}
};
});
the first function the writer calls is factory function and it returns a worker function. I' m not able to understand why he returns another function.
When i write the code below, the code returns an error.
I don't know what i'm doing wrong.
myApp.directive("highlight", function (scope, element, attrs) {
if (scope.day == attrs["highlight"]) {
return element.css("color","red")
}
});

If you take a look at the Angular docs about directives, you'll see that there are two ways you can define a directive. One is to use the directive definition object and the other is to just return a postLink function.
The example in your book shows the latter form. The reason your function doesn't work is because Angular calls your function and expects it to return a directive definition object or a postLink function. Instead the function you defined returns nothing or the result of element.css("color", "red") depending on your if statement.

Related

Worker Functions vs Factory Functions

I'm familiar with the term factory function but i'm not able to distinguish this term from 'Worker functions'...
Can someone explain the difference between both these terms?
And when we should prefer to use worker functions or in which particular situations?
Plz try to explain it with some simple example along with theory so that i can understand it better...
An Example code is here which i read:
myApp.directive("directiveName", function () {
return function (scope, element, attrs) {
if (scope.value == attrs["somevalue"]) {
element.css("color", "red");
}
}
});
In Angular JS we have different Module methods which accepts functions as arguments. We call them as factory functions because they create objects that Angular JS use to perform the actual work. These factory functions can return another function which is called worker function.
For example we have the following directive:
The 2nd argument to this directive is a factory function.
myApp.directive("myDirective", function() { // factory function
// implementation code goes here
});
The return statement in following factory function return another function which will be responsible to perform actual work:
myApp.directive("myDirective", function() { // factory function
return function(scope, element, attrs) { // worker function
// implementation code goes here
}
});
This function which is returned by the above factory function is generally called worker function. While in case of directives this function is commonly known as link function...

Angular.js call jquery function in Directive

In my app i am trying to call $('#accountTable').dataTable(); this function in my controller. But I think it doesnt work like that in angular.js. Tried to call this function in my Directive but i did not work.
My Directive:
'use strict'
app.directive('dataTableDirective', function () {
return {
restrict: "A",
link: function (scope, elem, attrs) {
$('#accountTable').dataTable();
}
}
});
Angular uses JQuery under the hood if you have JQuery referenced. If you don't then it falls back on a slimmer version of JQuery called JQuery Lite. The elem argument to the link function is already a JQuery wrapped object representing the element your directive is attached to. Just call the plugin from there and it should work fine. It is best to avoid the classic JQuery selectors to navigate the DOM and instead lean on Angular to provide the elements you need.
Make sure you have JQuery referenced before Angular in your script references.
app.directive('dataTableDirective', function () {
return {
restrict: "A",
link: function (scope, elem, attrs) {
elem.dataTable();
}
};
});
Angular needs to know about changes when they happen. If you assign any events and need to update scope variables, you'll need to make sure that Angular knows about those changes by wrapping them in scope.$apply. For example:
app.directive('dataTableDirective', function () {
return {
restrict: "A",
link: function (scope, elem, attrs) {
elem.on('order.dt', function (e) {
scope.something = 'someValue';
}).dataTable();
}
};
});
The above code will set the something property on scope, but because the event was triggered outside of an Angular digest cycle, any UI bound to the something variable may not appear to update. Angular needs to be told of the change. You can make sure the change happens during a digest cycle like this:
app.directive('dataTableDirective', function () {
return {
restrict: "A",
link: function (scope, elem, attrs) {
elem.on('order.dt', function (e) {
scope.$apply(function () {
scope.something = 'someValue';
});
}).dataTable();
}
};
});
Then in your markup:
<table data-data-table-directive>
<!-- table contents -->
</table>
#supr pointed this out in the comments. Note that the attribute is data-data-table-directive not data-table-directive. There is an HTML convention that you can begin arbitrary attributes with data- and Angular respects that and omits it. For example, you can put ng-click on an element or you can put data-ng-click on an element and they would both work the same. It also supports x-ng-click as another convention.
This is super relevant to you because it just so happens that your directive name begins with the word "data", so you'll need to double up on the data- in the beginning. Hopefully that makes sense.

Which parameters to pass into Angular JS custom directive controller?

I'm trying to create a custom directive which needs to use a separate controller because it needs to have functions which can be called by child directives.
Here is my code so far:
angular.module('myDirectives').controller('SlideInMenuController', function ($scope, $element, $attrs) {
$scope.isOpen = false;
// Toggle Function
this.toggle = function(){
$scope.$apply(function(){
$scope.isOpen = !$scope.isOpen;
});
};
// Watch $scope.isOpen and open the menu
$scope.$watch('isOpen', function() {
if($scope.isOpen == true){
$element.attr('is-open', true);
}
else{
$element.attr('is-open', false);
}
return false;
});
}
angular.module('myDirectives').directive('slideInMenu', function ($swipe) {
return {
restrict: 'EA',
scope: {},
controller:'SlideInMenuController'
};
});
angular.module('myDirectives').directive('slideInMenuToggle', function ($swipe) {
return {
restrict: 'EA',
require: '^slideInMenu',
link: function ($scope, $element, $attrs, SlideInMenuCtrl) {
$element.bind('click', function(){
SlideInMenuCtrl.toggle();
});
}
};
});
(Note: I'm using ng-annotate so I don't have to write all my dependencies twice)
I need to inject the $swipe service into the directive controller but a normal controller would't have $scope, $element, $attrs as the first three parameters. This has made me wonder if I should be putting those into the link function instead and doing DOM stuff there, but if I do that what goes in the controller and what goes in to the link function.
I've read numerous blogs and SO answers that say what order compile/link/controller are run in but still can't find a clear answer as to whatin my above example should go where.
Any help would really be appreciated.
There are two kind of functions for AngularJS. Neither of which is intended to be called directly.
1) Injectables: functions that receive parameters, whose names must (with a few exceptions) be registered with dependency injection subsystem. It's the reason for ng-annotate to exist. You can also use array notation for these.
angular.module('stackOverflow').service('answer', ['myService', function(myService) {
...
}]);
Some examples are the ones you pass to angular.module() functions, like service(), factory(), directive(), controller().
2) Plain functions. These have no special handling, it's vanilla JavaScript. They are passed to link and compile slots in directive definition objects.
You can omit rightmost parameters if you have no use for them, but not others. As the order of parameters is fixed, you cannot reorder them. But you can call them whatever you want.
That's it about functions.
About conventions using $: beware! AngularJS builtin services are prefixed with $, so you should name parameters this way for injectable functions. For all other cases, don't prefix with $: your own functions and positional parameters like you see in link() and compile(). Prefix with $ in those functions is misleading and bad guidance.
To better distinguish parameters for compile() and link, you can prefix with t for template and i for instance. Nowadays I prefer to use those unprefixed. It's better for moving them around.
compile: function (tElement, tAttrs) {
return function link(scope, iElement, iAttrs, ctrls) {
};
}

Can anyone explain this small piece of code?

I was looking at one of the custom implementations of ng-blur (I know it's already available in the standard AngularJS now). The last line is what I don't understand.
.controller('formController', function($scope){
$scope.formData = {};
$scope.myFunc = function(){
alert('mew');
console.log(arguments.length);
}
})
.directive('mew', function($parse){
return function(scope, element, attr){
var fn = $parse(attr['mew']);
element.bind('blur', function(event){
scope.$apply(function(){
fn(scope);
});
});
}
});
In the view there's a simple mew="myFunc()" applied to inputs.
My question is why are we passing the scope to the function in the very last line of the directive. I tried to make it work without that but it doesn't. What's actually happening?
Also this too works scope.$apply(attr.mew). Same reason or something different?
$parse only does just that, it parses the string passed in, you need to call the resulting function with the current scope because otherwise how else would it know which function to call?
scope.$apply works in the following manner:
The expression is executed using the $eval() method.
Any exceptions from the execution of the expression are forwarded to the $exceptionHandler service.
The watch listeners are fired immediately after the expression was executed using the $digest() method.
The reason scope.$apply(attr.mew) is due to the fact that it's doing all of the above. It is parsing, and then applying the result of the parse to the scope.
Another option is to use an isolate scope to bind your directive to the mew attr.
return {
scope: {
mew: '&'
},
link: function (scope, element, attr) {
var fn = scope.mew;
element.bind('blur', function (event) {
scope.$apply(function () {
fn();
});
});
}
}
Example
For this specific example it will work, but as you said, the blur is out of the digest loop. In most of the use cases the function will change data on one scope or another, and the digest loop should run and catch those changes.

Is it safe to remove dependencies in the controller function when the directive does not seem to use them?

The following controller does not seem to use $element, $attrs, $transclude. The controller code below runs fine if these params are commented out.
myApp.directive("menu", function () {
return {
restrict: "E",
scope: {},
transclude: true,
replace: true,
template: "<div class='menu' data-ng-transclude></div>",
controller: function ($scope ,$element, $attrs, $transclude) {
$scope.submenus = [];
console.log('[$element]->', $element);
console.log('[$attrs]->', $attrs);
console.log('[$transclude]->', $transclude);
this.addSubmenu = function (submenu) {
console.log('[addToggleMenu]->');
$scope.submenus.push(submenu);
}
this.closeAllOtherPanes = function (displayedPane) {
angular.forEach($scope.submenus, function (submenu) {
if (submenu != displayedPane) {
console.log('[displayedPane]->', displayedPane);
submenu.removeDisplayClass();
}
})
}
}
}
});
Here is my working fiddle.
Since posting, I have learned that in JavaScript, a function can be called with any number of arguments no matter how many of them are listed. In some languages, a programmer may write two functions with same name but different parameter list, and the interpreter/compiler would choose the right one. That is called function polymorphism. Having used function polymorphism most of my career I expected to be told "Hey , you're not using this param". Also I did not understand that well that the controller parameters are dependencies while the link function parameters are order based. I still struggle in understanding whether $scope, $element or commonly used parameters in the directives internal controller are required and which are optional. Apparently the $ is only required in the controller and not in link because of the DI injection of angular services..whew lot to digest.
A special thanks to Esteban for explaining special pseudo-array inside each function
called arguments. This explains the link function which is half the equation. So I have rewritten the question in hopes that it may get answered. This excellent explanation,
straightened out most of my confusion.
Why are you wanting to remove the $attrs argument? The directive's arguments are in a specific order and removing any one of them will cause subsequent arguments to not be what you think they are.
Say you changed your link function to something like this:
function ($scope, $iElement, menuController) {
menuController.addSubmenu($scope); // this will throw an exception
// because menuController is actually
// $attrs
The reason for this is due to the fact that the function will be called with the same arguments passed in regardless of whether they were defined in your directive's link function.
function argTest(one, two){
console.log('argTest.arguments', arguments);
// even though I don't have three defined, it is still passed in:
console.log('three is passed in', arguments[2]);
}
argTest(1,2,3);
JSFiddle for above: http://jsfiddle.net/TwoToneBytes/NM8DK/

Categories

Resources