AngularJS inject $timeout to a link? - angularjs

So here is what my issue is:
i have a directive:
autocompleteDirective.$inject = ['$timeout'];
function autocompleteDirective($timeout) {
return {
restrict: 'E',
scope: {
searchParam: '=ngModel',
suggestions: '=data',
onType: '=onType',
onSelect: '=onSelect',
autocompleteRequired: '='
},
controller: autocompleteController,
link: autocompleteLink,
templateUrl:'modules/components/autocomplete/templates/autocomplete.html'
};
}
my Link function looks like this:
function autocompleteLink(scope, element, attrs) {
$timeout(function() {
scope.initLock = false;
scope.$apply();
}, 250);
.... some other code
}
and my controller (not really relevant) :
autocompleteController.$inject = ['$scope'];
function autocompleteController($scope) {
//scope code
}
in my link function, I have a function that is (at the moment) using setTimeout:
if (attrs.clickActivation) {
element[0].onclick = function() {
if (!scope.searchParam) {
setTimeout(function() {
scope.completing = true;
scope.$apply();
}, 200);
}
};
}
I would like to unit test this certain block of code, but my unit tests fails:
elem.triggerHandler('click');
expect(scope.completing).to.equal(true);
even though, in the coverage report, i can see that the logic does successfully execute when the triggerHandler is clicked.
what i believe the culprit is the timeout.
digging around SO and other websites, i found using $timeout works best due to its exposure to "flush()" method.
my question is, how do I "inject" $timeout to the link function?
the previous examples i have see like injecting $timeout directly to directive, and then nesting the link function() inside the directive declaration:
function directive($timeout){
return {
link: function(scope, attrs) {
$timeout(blah!) //timeout is useable here...
}
}
The above doesn't work for me since i am not creating the function inside the directive function...so based on the model i am using, how can i use $timeout in a link?

Related

Wait for rendering after manual $compile

I have a page with dynamic content. Content depends of a lot of options, so I use manual compiling using $compile. I have a directive like this:
function compileHtml($compile, $timeout) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(function () {
return scope.$eval(attrs.compileHtml);
}, function (value) {
if (value) {
element.html(value);
$compile(element.contents())(scope);
}
});
}
};
There are a lot of DevExtreme charts in compiling HTML. They are in their own directives.
I need an event after rendering whole page including all charts. I tried to use $timeout(function () {} but it fires before rendering charts. It works with hack like this:
$timeout(function () {
$timeout(function () {
}
}, 100);
But this is not exactly what I want. Could you please suggest something instead?

Call method in controller from directive

HTML :
<div id="idOfDiv" ng-show="ngShowName">
Hello
</div>
I would like to call the function which is declared in my controller from my directive.
How can I do this? I don't receive an error when I call the function but nothing appears.
This is my directive and controller :
var d3DemoApp = angular.module('d3DemoApp', []);
d3DemoApp.controller('mainController', function AppCtrl ($scope,$http, dataService,userService,meanService,multipartForm) {
$scope.testFunc = function(){
$scope.ngShowName = true;
}
});
d3DemoApp.directive('directiveName', [function($scope) {
return {
restrict: 'EA',
transclude: true,
scope: {
testFunc : '&'
},
link: function(scope) {
node.on("click", click);
function click(d) {
scope.$apply(function () {
scope.testFunc();
});
}
};
}]);
You shouldn't really be using controllers and directives. Angularjs is meant to be used as more of a component(directive) based structure and controllers are more page centric. However if you are going to be doing it this way, there are two ways you can go about it.
First Accessing $parent:
If your directive is inside the controllers scope you can access it using scope.$parent.mainController.testFunc();
Second (Preferred Way):
Create a service factory and store your function in there.
d3DemoApp.factory('clickFactory', [..., function(...) {
var service = {}
service.testFunc = function(...) {
//do something
}
return service;
}]);
d3DemoApp.directive('directiveName', ['clickFactory', function(clickFactory) {
return {
restrict: 'EA',
transclude: true,
link: function(scope, elem) {
elem.on("click", click);
function click(d) {
scope.$apply(function () {
clickFactory.testFunc();
});
}
};
}]);
Just a tip, any time you are using a directive you don't need to add $scope to the top of it. scope and scope.$parent is all you really need, you will always have the scope context. Also if you declare scope :{} in your directive you isolate the scope from the rest of the scope, which is fine but if your just starting out could make things quite a bit more difficult for you.
In your link function you are using node, which doesn't exist. Instead you must use element which is the second parameter to link.
link: function(scope, element) {
element.on("click", click);
function click(d) {
scope.$apply(function() {
scope.testFunc();
});
}

Typeerror - Not a function in angular directive

I have defined a custom click directive as below:
(function() {
'use strict';
angular.module('myApp.core')
.directive('customClick', customClick);
customClick.$inject = ['$rootScope'];
function customClick() {
return {
restrict: 'A',
/*scope: {
customClick: '&'
},*/
link: function(scope, element, attrs){
$(element).on('click', function(e) {
scope.$apply(function() {
console.log("Hello..customClick..");
scope.customClick();
});
});
}
};
}
})();
And I get the following error on this;
Error logged by WDPR Angular Error handler service {xx.."stacktrace":"TypeError: a.customClick is not a function","cause":"unknown cause"}(anonymous function)bowerComponents.js:5745
How can I resolve this? If I add scope with '&' I get demanding isolated scope. Hence how to resolve it?
If I remove - scope.customClick();, it does not show anything on second html for custom-click, it has impact on only 1 html, and its controller. I want to use it in multiple controller + html.
customClick is a function on the directive itself. It is not a function on the scope. That's why the error has occurred.
link is used to manipulate dom/add event handlers on elements, which you have rightly done with element.bind('click', function() {
Whenever click occurs, the function binded to click automatically gets invoked. There is no need of watch and the invoking statement.
Your link can just be
link: function(scope, element){
element.bind('click', function() {
console.log("Hello..customClick..");
});
}
As you have used camel case in naming the directive, exercise caution in its usage in template.
You can use it as <p custom-click>hi</p>
I would recommend you to avoid using jQuery in angular apps. Try following
angular.module('newEngagementUI.core')
.directive('customClick', customClick);
customClick.$inject = ['$rootScope'];
function customClick() {
return {
restrict: 'A',
scope: {
customClick: '&'
},
link: function(scope, element, attrs){
element.bind('click', function () {
scope.customClick();
})
}
};
}
In your template:
<div custom-click="clickFunction"></div>
And your template controller should be like:
angular.module('myApp', []).controller(['$scope', function ($scope) {
$scope.clickFunction = function () {
alert('function passed');
}
}])
Working fiddle here: https://jsfiddle.net/xSaber/sbqavcnb/1/

Angular materializecss select directive gives me error

i tried to include http://krescruz.github.io/angular-materialize/#select and i got error in console (you can see in image). Is there any solution for this issue, here is the directive which is in angular-materialize.js
angular.module("ui.materialize.material_select", [])
.directive("materialSelect", ["$compile", "$timeout", function ($compile, $timeout) {
return {
link: function (scope, element, attrs) {
if (element.is("select")) {
$compile(element.contents())(scope);
function initSelect() {
element.siblings(".caret").remove();
element.material_select();
}
$timeout(initSelect);
if (attrs.ngModel) {
scope.$watch(attrs.ngModel, initSelect);
}
if ("watch" in attrs) {
scope.$watch(function () {
return element[0].innerHTML;
}, function (oldVal, newVal) {
if (oldVal !== newVal) {
$timeout(initSelect);
}
});
}
}
}
};
}]);
This error means that you are trying to use jQuery methods without loading jQuery. Angular comes with lightweight jQuery-like implementation (angular.element) with limited set of methods. Those methods don't include is and trigger (and many others).
So the solution is simple: if you use jQuery methods - load jQuery before Angular.

Integrating AngularJS and DagreD3 - AngularJS directive does not update on model change.

I am writing and AngularJS directive for DagreD3. I have some problems with $scope update in Angular. When I update the Model, the Directive does not re-render the graph.
A plunker can be found here.
My directive looks like this:
myApp.directive('acDagre', function() {
function link(scope, element, attrs) {
scope.$watch(scope.graph, function(value) {
alert('update'); //NOT EVEN THIS IS CALLED ON UPDATE
});
var renderer = new dagreD3.Renderer();
renderer.run(scope.graph, d3.select("svg g"));
}
return {
restrict: "A",
link: link
};
The variable $scope.graph is modified in the Controller during runtime like this:
$scope.addNode = function(){
$scope.graph.addNode("kbacon2", { label: "Kevin Bacon the second" });
}
Did I understand something wrong in Angular? Everytime the Variable $scope.graph is changed, i want the graph to update.
You can find more information in the Plunker.
Thank you for very much your help!
The watcher should look either like this:
scope.$watch('graph', function(value) {
console.log('update');
});
Or like this:
scope.$watch(function () { return scope.graph; }, function(value) {
console.log('update');
});
It will not fire when adding nodes however, cause it's comparing by reference.
You can add true as a third parameter to perform a deep watch instead (it will use angular.equals):
scope.$watch('graph', function(value) {
console.log('update');
}, true);
Note that this is more expensive.
Example:
.directive('acDagre', function() {
var renderer = new dagreD3.Renderer();
function link(scope, element, attrs) {
scope.$watch(function () { return scope.graph; }, function(value) {
render();
}, true);
var render = function() {
renderer.run(scope.graph, d3.select("svg g"));
};
}
return {
restrict: "A",
link: link
};
});
Demo: http://plnkr.co/edit/Dn1t3sMH58mDz9HhqYD5?p=preview
If you are just changing the nodes you can define the watchExpression like this instead:
scope.$watch(function () { return scope.graph._nodes; }
Deep watching large objects can have a negative effect on performance. This will of course depend on the size and complexity of the watched object and the application, but it's good to be aware of.

Resources