Angular directive link function never runs - javascript

I followed the tutorial of ng-bbok, and at the directive definition an empty compile function is inserted and then the link function. With this the code in the link function never got executed. Finally i figured out that is because the empty compile function, when i deleted it magically the link got executed. Why is it happening like this? Im using Angular 1.3
{
compile: function() {},
link: function($scope, element, attributes) {
var size = attributes.gravatarSize || 80;
var hash = md5.digest_s($scope.email.from[0]);
$scope.gravatarImage = url + hash + '?size=' + size;
}
}

You can't define both compile property and link. If you want to use the compile function you can either return the link function:
compile: function() {
return function($scope, element, attributes) {
var size = attributes.gravatarSize || 80;
var hash = md5.digest_s($scope.email.from[0]);
$scope.gravatarImage = url + hash + '?size=' + size;
}
}
Or define both pre and post (link) functions:
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) { ... },
post: function postLink(scope, iElement, iAttrs, controller) { ... }
}
}
Check the documentation

It's happening by design. To quote the $compile docs:
link
This property is used only if the compile property is not defined.

Related

Angular directive "=" value binds to scope, but is undefined?

I have defined the following directive inside my Angular app:
(function() {
'use strict';
angular
.module('almonds')
.directive('security', ['$animate', 'AuthFactory', directive]);
function directive($animate, AuthFactory) {
var directive = {
restrict: 'EA',
scope: {
operation: "#",
clearance: "#",
project: "="
},
link: linkFunc
};
return directive;
function linkFunc($scope, $element, $attr, ctrl, $transclude) {
var block, childScope, previousElements;
console.log($scope.project);
if($scope.project) {
var projectId = $scope.project.id;
}
var operation = $scope.operation;
var clearance = $scope.clearance;
if (projectId) {
var value = AuthFactory.hasProjectAccess(projectId, clearance);
console.log('PROJECT SECURITY:', projectId, clearance, value);
} else {
var value = AuthFactory.hasAccess(operation, clearance);
console.log('ORG SECURITY:', operation, clearance, value);
}
}
}
// Controller.$inject = ['$scope'];
//
// function Controller($scope) {
// var vm = this;
//
// activate();
//
// function activate() {
//
// }
// }
})();
It is to be used as an element that receives either an operation or project value as well as a clearance value, which will then be used whether said element will render (I omitted that part but it's functionality is basically the same as ng-if).
Here's an example of it in use:
<span security project="vm.project" clearance="admin">
<a role="button" ng-click="vm.confirmDeletion();"><span class="melon-icon-md melon-icon-trash"></span></a>
</span>
What's happening though, is that even though vm.project is indeed defined, that console.log($scope.project); yields undefined. Interestingly, if I simply console.log($scope); it will contain a project property with the information I need. What am I doing wrong?
I actually only need the project's id value, so I can either pass in the entire project object and access its id within the directive, or somehow pass the id number alone.
When your directive's link function starts executing, vm.project is undefined at that point. Set a watch on $scope.project.
Inside Directive:
$scope.$watch('project', function (newValue, oldValue) {
if (newValue) {
// do stuff
}
});

Custom validators with AngularJs

I'm writing my own custom AngularJs validators which look like this:
.directive('float', function ($log) {
return {
restrict: 'A',
require: 'ngModel',
scope: {float: '='},
link: function ($scope, ele, attrs, ctrl) {
var settings = $scope.float || {};
ctrl.$validators.float = function(value) {
var valid = isTheInputValidFunction( settings );
ctrl.$setValidity('float', valid);
return valid;
};
}
};
});
I'm using the validators like so:
<input type="text"ng-model="someVar" name="preis" float="{precision: 5, scale: 2}">
However, as soon as I attach multiple validators, I get the following error:
Multiple directives [...] asking for new/isolated scope
This is, because all my validators get a settings-object which has to be passed into the scope scope: {float: '='}.
I know that I can use var settings = JSON.parse(attrs.float); in the directives, but it doesn't look right.
So my question is:
How do you correctly implement custom validators in AngularJs?
It really depends on whether you expect the settings to change.
If you think it will be constant, like in the example you've shown, then simply parsing once the value will be enough. The appropriate service to use in such a case is $parse:
link: function ($scope, ele, attrs, ctrl) {
var settings = $parse(attrs.float)($scope);
// …
}
If you think it may be used with a variable, you should watch its content:
link: function ($scope, ele, attrs, ctrl) {
var settings = undefined;
$scope.$watch(attrs.float, function (newSettings) {
settings = newSettings;
});
// …
}
Perhaps it is because you are calling $setValidity. I beleive the whole point of the $validators pipeline was to do it for you. Just return a boolean.
ctrl.$validators.float = function(value) {
return isTheInputValidFunction( settings );
};

Passing element attributes from parent to child with Angular JS 1.3

I have a problem with Angular JS. I have two directives.
angular.module('myModule', [])
.directive('myFirstDirective', function(){
return {
link: function (scope, elem, attr) {
var myAttributeToPass = attr.myFirstDirective;
scope.myAttr = myAttributeToPass;
},
controller: 'MyFirstController'
}
})
.controller('MyFirstController', function($scope){
this.returnTheParameter = function(){
return $scope.myAttr;
}
})
.directive('mySecondDirective', function(){
return {
require : ['ngModel', '^myFirstDirective'],
link : function($scope, element, attrs, ctrls) {
var ngModel = ctrls[0];
var myFirstCtrl = ctrls[1];
var theParamOfFirst = myFirstCtrl.returnTheParameter();
}
}
});
I init my first value with a string :
<div my-first-directive="foobar"> (... => my second directive is inside) </div>
My problem is in the life cycle, the returned value is always undefined because the controller is called before the link. When i do an isolated scope, with :
scope: {
"myProp": "#myFirstDirective"
}
That's works but i don't want to isolate the scope...
Any ideas ?
Thanks a lot !
The problem lies in the order in which the operations are taking place.
It sounds like you will need to compile things in a specific order. In which case I would like to refer you to this post: How to execute parent directive before child directive? so I don't borrow the full thunder of another person's explanation.
Ultimately you would want to do something along the lines of:
return {
compile: function(){
return{
pre:function (scope, elem, attr) {
var myAttributeToPass = attr.myFirstDirective;
scope.myAttr = myAttributeToPass;
},
post: angular.noop
};
},
controller: 'MyFirstController'
};
for your first directive and for the second directive:
return {
require : ['^myFirstDirective'],
compile: function(tElement, tAttrs, transclude){
return{
pre: angular.noop,
post: function($scope, element, attrs, ctrls) {
var ngModel = attrs.ngModel;
var theParamOfFirst = ctrls[0].returnTheParameter();
}
};
}
};
The angular.noop above is just an empty method returning nothing.
For a working example feel free to browse the plunk I threw together (http://plnkr.co/edit/pe07vQ1BtTc043gFZslD?p=preview).

passing defined object to another custom directive angularjs

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 angular way of binding a directive inside another directive?

I am writing a directive which will be used to establish client and server-side validation on an input. It should accept an array of validator names (e.g. aa-validate="required,unique"), loop through them, add client-side validation directives for all possible validators (e.g. required should add ngRequired), and for the rest, post to a server-side validation API.
The last part of that works well: I am watching the ngModel attribute, and posting to the server with a 100ms timeout. However, setting client-side validation directives from within the linking function of my directive does NOT cause them to be compiled and linked. In other words, they do nothing. Here is my code:
angular.module('form', [])
.directive('aaValidate', ['$http', function($http) {
return {
priority: 1,
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
var validate = attrs.aaValidate,
validators = validate.split(',');
// This is the problem!
//
// Populate possible client-side validators
for (var i = 0, len = validators.length; i < len; i++) {
var validator = validators[i];
switch (validator) {
case 'required':
attrs.$set('ngRequired', 'true'); break;
// ... and so on for ngPattern, etc.
default: break;
}
}
scope.$watch(attrs.ngModel, function(value) {
// This part works!
//
// Clear existing timeout, reset it with an
// $http.post to my validation API, the result is
// passed into ctrl.$setValidity
});
}
}
}]);
I did make an attempt to inject $compile, and re-compile the element at the end of the linking function. I ended up with infinite recursion, likely because I failed to remove some attributes, but even if I manage to do it this way, it feels rather ugly. What is the correct approach?
Any help is greatly appreciated. Thanks in advance.
EDIT: jsFiddle: http://jsfiddle.net/3nUdj/4/
My first answer was wrong - I don't think there's any way around using the $compile service. Here's how you can do it without getting infinite recursion. I basically split the directive in two directives - one adds the validation directives, removes itself and recompiles. The other does the other stuff:
angular.module('form', [])
.directive('aaValidate', ['$http', '$compile', function ($http, $compile) {
return {
link: function (scope, element, attrs) {
var validate = attrs.aaValidate,
validators = validate.split(',');
// Populate possible front-end validators
for (var i = 0, len = validators.length; i < len; i++) {
var validator = validators[i];
switch (validator) {
case 'required':
attrs.$set('ngRequired', 'true');
break;
default:
break;
}
}
attrs.$set('aaOther', '');
element.removeAttr('aa-validate');
$compile(element)(scope);
}
}
}])
.directive('aaOther', function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
scope.$watch(attrs.ngModel, function (value) {
// Server-side validation
});
}
}
});
You have to recompile the linked element for this to work in ng-repeat. I've updated the fiddle: http://jsfiddle.net/3nUdj/7/

Categories

Resources