How to unit test AngularJs directive requiring parents directive controller with Jasmine? - javascript

I have AngularJs directive accordionPanel that requires controller of parent directive accordion. I need to test accordionPanel directive to see if model changes when I call foldUnfold function. How would I write unit test to see if the model changes on foldUnfold call. Thats simplified version of my directives and test I got so far is below that:
.directive("accordion", [
function() {
return {
templateUrl: "otherurl",
transclude: true,
replace: true,
scope: {
},
controller: ["$scope",function($scope) {
this.isOneOpenOnly = function() {
return $scope.oneOpenOnly;
}
}],
link: function(scope, elem, attrs, ctrl, linker) {
// some code
}
}
}
])
.directive("accordionPanel", [
function() {
return {
templateUrl: "urlblah",
transclude: true,
replace: true,
require: "^accordion",
scope: {},
link: function(scope, elem, attrs, ctrl, linker) {
scope.foldUnfold = function() {
// some logic here then
scope.changeThisModel=ctrl.isOneOpenOnly();
}
}
}
}
])
Thats my test so far:
it('Should return unfolded as true', function() {
var scope=$rootScope.$new(),
element=$compile("<div accordion><div accordion-panel></div></div>")(scope);
scope.$digest();
scope.foldUnfold(); // this is fails as scope refers to accordion but I need to access accordionPanel
expect(scope.changeThisModel).toBe(true);
});
The problem is I cannot get access to accordionPanel scope where foldUnfold sits. I think it might be possible to access it via $$childHead and such, but even if possible it doesn't seem like the right way to do. How would I test it then?

Related

AngularJS - Is this the right way to write a directive?

I was trying to declare a directive, it is apparently right but when I load it in html nothing occurs.
This is the code:
(function() {
'use strict';
var app = angular
.module('App');
app.directive('directiveFunction', directiveFunction);
function directiveFunction(){
return {
restrict: 'EA',
replace: true,
template: '<div>Example</div>',
controller: directiveController,
controllerAs: 'example',
bindToController: true,
link: linkFunction
}
}
linkFunction.$inject = ['$scope', 'elem', 'attrs', 'ctrl'];
function linkFunction(scope, element, attrs, ctrl) {}
function directiveController() {
var example = this;
}
})();
I call this in html as <directive-function></directive-function> but it does nothing.
I created a fiddle for you.. you are doing all well, I think that you are using it like
https://jsbin.com/koporel/edit?html,js,output
<directiveFunction></directiveFunction>
No, use - where case changes, like
<directive-function></directive-function>

Accessing parent formController in angular directive controller

In this question I have found how to access parent form within link in a directive. But I need it in my controller and access to form validation, so I implement this:
return {
restrict: 'A',
require: '?^form',
replace: true,
scope: {
someVariable: '='
},
link: function (scope, element, attrs, formCtrl) {
scope.formCtrl = formCtrl;
},
controller: function ($scope) {
$scope.someMethod = function () {
if ($scope.formCtrl.$valid) {
//Do something
}
}
}
};
It´s the correct way to do that? there's a better way?
EDIT: I need isolated scope and I´m actually using require: '?^form'

Update $watch using require in angular directive

as an angular newbie this is my problem
If I have two directives in HTML like this
<parent-dir param="par">
<child-dir></child-dir>
</parent-dir>
and in JS like this (in parent)
app.directive('parentDir', function(){
return {
restrict: 'E',
scope: {
param: '='
}
}
})
and in child
app.directive('childDir', function(){
return {
restrict: 'E',
require: '^parentDir',
controller: function($scope, $element){
<-- SHOULD I PUT WATCHER HERE -->
},
link: function(scope, element, attrs, parentdirCtrl){
<-- SHOULD I PUT WATCHER HERE -->
}
}
})
where in the child directive should I make an optional $watch in order to catch all changes to the param model?
Off course if I use $watch in the parent controller, all changes in the param are reflected in the parent directive but I can`t seem to find a way to pass this information to child directive.
You should place it inside the link function which have access of the parent controller using 4th parameter of link function parentdirCtrl. Actually you don't need to worry about the params variable because it uses = two way binding inside directive that does update the value in both parent controller scope & directive scope. Additionally you need define controller in your parentDir directive so that the scope of parentDir directive shared with the childDir.
Code
app.directive('childDir', function() {
return {
restrict: 'E',
require: '^parentDir',
template: '<div class="test">INner {{param}}</div>',
controller: function($scope, $element) {
},
link: function(scope, element, attrs, parentdirCtrl) {
scope.$watch('param', function(newVal, oldVal) {
console.log(newVal);
}) //true only if its object.
}
}
})
Demo Plunkr

AngularJS directive to directive communication throwing an error about controller not found

I have 2 directives, one for searching and one for pagination. The pagination directive needs to access the search directive to find out what property we're currently searching by. When I load the page though, it throws an error saying Error: [$compile:ctreq] Controller 'search', required by directive 'pagination', can't be found!. However I have a controller setup in my search directive.
Here is my search directive:
angular.module('webappApp')
.directive('search', function ($route) {
return {
templateUrl: 'views/search.html',
restrict: 'E',
scope: {
searchOptions: '=',
action: '=',
currentProperty: '=',
currentValue: '='
},
controller: function($scope) {
$scope.searchBy = $scope.searchOptions[0].text;
$scope.searchByProperty = $scope.searchOptions[0].property;
$scope.setSearchBy = function(event, property, text) {
event.preventDefault();
$scope.searchBy = text;
$scope.searchByProperty = property;
};
$scope.search = function() {
$scope.searching = true;
$scope.currentProperty = $scope.searchByProperty;
$scope.currentValue = angular.element('#searchCriteria').val();
$scope.action($scope.searchByProperty, $scope.currentValue, function() {
$scope.searching = false;
});
};
$scope.reload = function() {
$route.reload();
};
}
};
});
Here is my pagination directive:
angular.module('webappApp')
.directive('pagination', function () {
return {
templateUrl: 'views/pagination.html',
restrict: 'E',
require: '^search',
scope: {
basePath: '#',
page: '=',
sort: '='
},
link: function(scope, element, attrs, searchCtrl) {
console.debug(searchCtrl);
scope.searchByProperty = searchCtrl.searchByProperty;
}
};
});
In order for one directive to use another's controller by use of require, it needs to either share the same element as the controller containing directive, or it has to be a child of it.
You can't use require in the way you have, where the elements are siblings.
Angular docs about directives, including require
If it doesn't make sense to rearrange the DOM in the way I've described, you should inject a service into both directives which contains the data/methods you wish to share between the two.
Note: you could also experiment with the $$nextSibling / $$prevSibling properties of the directives' scopes, but this would present only a very fragile solution
You cannot use require in directive like that, however , since the only thing you need to pass between directives is a string , just bind them to the same property in parent controller (it can be parent directive controller):
...
<div ng-app='app' ng-controller='MyCtrl as ctrl'>
<my-dir-one s1='ctrl.message'></my-dir-one>
<my-dir-two s2='ctrl.message'></my-dir-two>
and first directives:
app.directive('myDirOne', function ($route) {
return {
templateUrl: 'views/my-dir-one.html',
restrict: 'E',
scope: {
s1: '=',
second directive
app.directive('myDirTwo', function ($route) {
return {
templateUrl: 'views/my-dir-one.html',
restrict: 'E',
scope: {
s2: '=',

How to set a value on a directive's scope

I have multiple AngularJS directives that are nearly identical - there are only two differences: the template URL and one single element in the linking function. Both are constant for each directive. So, for simplicity's sake, this is how it looks like:
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
scope: true,
link: function(scope, element, attrs) {
var veryImportantString = "this_string_changes";
// a few dozen lines of code identical to all directives
}
};
}]);
Now, moving the linking function to a commonly available place is obvious. What is not so obvious to me is how to set that "very important string" on the scope (or otherwise pass it to the directive) without declaring it in the HTML.
Here's what I've tried.
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
scope: {
veryImportantString: "this_string_changes"
},
link: someCommonFunction
};
}]);
Nope, apparently the scope config doesn't take values from nobody. I can bind a value coming from the HTML attribute, but this is precisely what I don't want to do.
Also tried this:
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
veryImportantString: "this_string_changes",
scope: true,
link: function(scope, element, attrs) {
var veryImportantString = this.veryImportantString;
}
};
}]);
But alas, the linking function is then called with this set to something else.
I assume this might work:
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
scope: true,
compile: function(element, attrs) {
// no access to the scope...
attrs.veryImportantString = "this_string_changes";
return someCommonFunction;
}
};
}]);
However, I am not 100% sure this is what I want either, as it reeks of being a dirty workaround.
What are my other options?
I have devised a completely different approach: using a factory-like function to spawn directives.
var factory = function(name, template, importantString) {
app.directive(name, [function() {
return {
scope: true,
templateUrl: template,
link: function(scope, element, attrs) {
var veryImportantString = importantString;
// directive logic...
}
};
}]);
};
Then, in order to create individual directives, I simply call:
factory("myDirective", "/path/to/template.html", "important");
factory("myDirective2", "/path/to/template2.html", "important2");
What about the following:
Before wherever you define someCommonFunction, add the line
var veryImportantString = "someOptionalDefault"
This then puts veryImportantString in scope of both your someCommonFunction and .directive()
Then you can change your directive code to:
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
scope: true,
link: function(args){
veryImportantString = "thatUberImportantValue";
someCommonFunction(args);
}
};
}]);
Proof of concept fiddle

Categories