Access functions in link section of Angular directive - angularjs

Below is a directive I am trying to test. I know how I am going to test it but I am having a hard time finding out how to call/trigger the functions in the link section in order to start testing it(ie. scope.accountClick ).
I was sure I would be able to do it like :
it('should call selectProfile()', function () {
jasmine.createSpy('selectProfile');
scope().profileClick(account, property, profile);
expect(scope.selectProfile).toHaveBeenCalledWith(account, property, profile);
});
But it does not get called. Someone suggested I needed to use element.isolateScope().profileClick(account, property, profile); but that does not work either
The directive I am testing looks like this:
angular.module('pb.webSites.directives')
.directive('pbGoogleAnalyticsManagementList', ['$window', '$document', function ($window, $document) {
return {
restrict: "E",
templateUrl: "app/webSites/directives/GoogleAnalyticsManagementList.html",
scope: {
googleAnalyticsProfile: '=pbGoogleAnalyticsProfile',
selectProfileFn: '&pbSelectProfile'
},
controller: 'GoogleAnalyticsManagementController',
link: function (scope, element, attrs) {
scope.accountClick = function (account) {
//bunch of code
scope.getProperties(account);
};
scope.propertyClick = function (account, property) {
//bunch of code
scope.getProfiles(account, property);
};
scope.profileClick = function (account, property, profile) {
//bunch of code
scope.selectProfile(account, property, profile);
};
}
};
}]);

Related

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();
});
}

where to put the functions in angularjs, service or directive

I have a grid directive and related service. The code like below:
(function (window, angular) {
'use strict';
angular.module("myModule")
.service("myGrid", [
function () {
var self = this;
this.init_grid = function (scope, config) {
scope.itemsList = []; // particular grid data
};
this.methodsList = {
add: function () {
// add logic...
},
edit: function (scope, evt, data) {
}
};
}
])
.directive("myGridView", ["$compile", "myGrid", function ($compile, myGrid) {
return {
restrict: "E",
replace: true,
transclusion: true,
templateUrl: "views/gridTemplate.html",
scope: {
config: "="
},
link: function ($scope, iElement, iAttrs, controller) {
myGrid.init_grid(scope, scope.config);
// register method in scope
angular.forEach(myGrid.methodsList, function (method, k) {
scope[k] = method;
//scope[k] = function() {method($scope)}
});
}
};
}
])
;
})(window, window.angular);
<form>
<my-grid-view config="config1"></my-grid-view>
<my-grid-view config="config2"></my-grid-view>
<my-grid-view config="config3"></my-grid-view>
</form>
My question is that:
As the form contains more than one grid, and they call the same functions in service, how can the add or edit functions know which grid calling and how to dealer with the particular data in grid?
Currently,
(1)I pass the scope to the add or edit function to implement//scope[k] = function() {method($scope)},
(2)Another way, in link function, define $scope.add = function() {}
(3)I can also define a controller in directive and defines these function.
What is the best practice to implement this purpose?

AngularJS inject $timeout to a link?

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?

Show the content of a Directive when the user clicks a button - almost working

In my html page I have a button and a directive snippet like so:
<button ng-click="showProfile();"></button>
<profile ng-if="isProfile==true"></profile>
In my controller I have initialized the $scope.isProfile variable = false and have the function called by the button:
$scope.showProfile = function(contact) {
$scope.contact = contact; // this object needs to get passed to the controller that the directive initiates, but how??
$scope.isProfile = true;
};
In my app I have a directive defined as such...
app.directive('profile', function () {
return {
templateUrl: '/contacts/profile',
restrict: 'ECMA',
controller: contactsProfileController,
link:function(scope, element, attrs) {
console.log('k');
}
};
});
Everything is working but I can't figure out how to pass the $scope.contact object to the controller that the directive references.
I've tried adding scope:scope to the return {} of the directive but with no luck. Do I need to do something in the link function? I've spent the entire day reading about directives and am exhausted so any tips would be greatly appreciated!!!
Thanks in advance for any help!
Here's what the controller that's being called from the directive looks like as well:
var contactsProfileController = function($scope,contact) {
$scope.init = function() {
console.log($scope.contact); //this should output the contact value from the showProfile function.
};
....
}
try this on your directive.
<profile ng-if="isProfile==true" contact="contact"></profile>
and add this to the scope
app.directive('profile', function () {
return {
templateUrl: '/contacts/profile',
restrict: 'ECMA',
scope: {
contact: '=contact'
}
controller: contactsProfileController,
link:function(scope, element, attrs) {
console.log('k');
}
};
});
But I see a couple of issues from your code:
- your showProfile function is expecting a "contact" argument that is not being passed from the button directive, so it will be undefined.
- you are injecting a "contact" dependency on your contactsProfileController controller. Do you have a service / factory declared with that name?
Instead of contact: '#contact', do contact: '=contact'
Since your custom directive is a "component" of sorts, it is a good idea to use an isolate scope and pass the necessary data (i.e. contact) via attributes.
E.g.:
<button ng-click="showProfile(...)"></button>
<profile contact="contact" ng-if="isProfile"></profile>
$scope.showProfile = function (contact) {
$scope.contact = contact;
$scope.isProfile = true;
};
.directive('profile', function () {
return {
restrict: 'ECMA',
scope: {contact: '='}
templateUrl: '/contacts/profile',
controller: contactsProfileController
};
});
Then, the property will be available on the scope (e.g. contactsProfileController's $scope):
var contactsProfileController = function ($scope) {
$scope.$watch('contact', function (newValue) {
// The `contact` has changed, do something...
console.log($scope.contact);
});
...
};
Both of your responses were incredibly helpful and I was able to get things working in a matter of minutes after reading your posts. Thank you so much!!!
Adding contact="contact" into the directive placeholder was key as was adding the scope object to the actual directive code.
So I ended up with:
<profile ng-if="isProfile===true" contact="contact"></profile>
and
.directive('profile', function () {
return {
templateUrl: '/contacts/profile',
restrict: 'ECMA',
controller: contactsProfileController,
scope: {contact: '='},
link:function(scope, element, attrs) {
}
};
});

AngularJS accessing an attribute in a directive from a controller

I need to pass an Id defined in the directive to the associated controller such that it can be used in a HTTP Get to retrieve some data.
The code works correctly in its current state however when trying to bind the Id dynamically, as shown in other questions, the 'undefined' error occurs.
The Id needs to be defined with the directive in HTML to meet a requirement. Code follows;
Container.html
<div ng-controller="IntroSlideController as intro">
<div intro-slide slide-id="{54BCE6D9-8710-45DD-A6E4-620563364C17}"></div>
</div>
eLearning.js
var app = angular.module('eLearning', ['ngSanitize']);
app.controller('IntroSlideController', ['$http', function ($http, $scope, $attrs) {
var eLearning = this;
this.Slide = [];
var introSlideId = '{54BCE6D9-8710-45DD-A6E4-620563364C17}'; //Id to replace
$http.get('/api/IntroSlide/getslide/', { params: { id: introSlideId } }).success(function (data) {
eLearning.Slide = data;
});
}])
.directive('introSlide', function () {
return {
restrict: 'EA',
templateUrl: '/Modules/IntroSlide.html',
controller: 'IntroSlideController',
link: function (scope, el, attrs, ctrl) {
console.log(attrs.slideId); //Id from html present here
}
};
});
Instead of defining a controller div that wraps around a directive, a more appropriate approach is to define a controller within the directive itself. Also, by defining an isolated scope for your directive, that slide-id will be available for use automatically within directive's controller (since Angular will inject $scope values for you):
.directive('introSlide', function () {
// here we define directive as 'A' (attribute only)
// and 'slideId' in a scope which links to 'slide-id' in HTML
return {
restrict: 'A',
scope: {
slideId: '#'
},
templateUrl: '/Modules/IntroSlide.html',
controller: function ($http, $scope, $attrs) {
var eLearning = this;
this.Slide = [];
// now $scope.slideId is available for use
$http.get('/api/IntroSlide/getslide/', { params: { id: $scope.slideId } }).success(function (data) {
eLearning.Slide = data;
});
}
};
});
Now your HTML is free from wrapping div:
<div intro-slide slide-id="{54BCE6D9-8710-45DD-A6E4-620563364C17}"></div>
In your IntroSlide.html, you probably have references that look like intro.* (since your original code use intro as a reference to controller's $scope). You will probably need to remove the intro. prefix to get this working.
Require your controller inside your directive, like this:
app.directive( 'directiveOne', function () {
return {
controller: 'MyCtrl',
link: function(scope, el, attr, ctrl){
ctrl.myMethodToUpdateSomething();//use this to send/get some msg from your controller
}
};
});

Resources