Multiple directives asking for template - javascript

I know this might be a common problem but i havent been able to find a viable solution to the issue.
Say i have the following directive:
angular.module('Api').directive('contentSpinner', function (responseInterceptor,$timeout) {
return {
restrict: 'AE',
templateUrl: 'js/helpers/Api/directives/content-spinner/content-spinner.html',
scope: {
boundRoute: '#',
timeout: '=',
timeoutms: '#'
},
link: function (scope, element, attr) {
scope.shouldSpin = true;
if (scope.boundRoute) {
responseInterceptor.subScribeToRoute(scope.boundRoute, function () {
//Route has been completed and the spinner is ready to stop!
scope.shouldSpin = false;
})
}
if (scope.timeout) {
$timeout(function () {
scope.shouldSpin = false;
}, scope.timeoutms);
}
}
}
});
Please do note the AE restriction.
Then i have another directive:
angular.module('LBTable').directive('lbTable', ['$state', 'alertService', 'usSpinnerService', function ($state, alertService,usSpinnerService) {
return {
restrict: 'E',
roles: 'all',
templateUrl: 'js/helpers/LBTable/directives/lb-table/lb-table.html',
scope: {
tableData: '=',
tableFields: '=',
actionElement: '=',
search: '=',
tableDataFilter: '='
},
link: function (scope, element, attrs) {
}
}
}]);
As you can see both directives has a template.
Now on all of my <lb-table> elements i wish to add the attribute content-spinner (my other directive) so that it prepends the template of contentSpinner.
However this poses the following error:
Error: [$compile:multidir] Multiple directives [contentSpinner (module: Api), lbTable] asking for template on: <lb-table
Which is theory makes sence it doesnt know which template to use.
However is there a way to come around this without wrapping one directive around the other like:
<content-spinner><lb-table></lb-table></content-spinner>

Related

Angular call controller function from nested custom directive

I could use some help.
I have a controller containing a function called "loadInformation()". Inside this controller I use a service which is doing some DOM-Manipulation using custom directives.
These are the directives:
First custom Directive:
angular.module('...').directive('ngInputString', function () {
return {
restrict: 'AE',
replace: 'true',
scope: {
savedInformation: '=',
type: '#',
values: '='
},
templateUrl: 'directives/templates/inputString.html',
link: function (scope, element, attrs) {
scope.filterOb = {ttype: scope.type};
}
}
});
HTML file:
<div>
<input ttype="{{type}}" type="text" placeholder="{{param.name}}" value='{{param.value}}'
class='clean-input form-control'/>
<ng-saved-information type="STRING" saved-information="savedInformation"></ng-saved-information>
</div>
Nested custom directive:
angular.module('semtrosApp').directive('ngSavedInformation', function () {
return {
restrict: 'AE',
replace: 'true',
scope: {
savedInformation: '=',
type: '#'
},
template: '<ul><li ng-repeat="information in savedInformation | filter:filterOb">{{information.value}}<button type="button" ng-click="..?..">Use Information</button></li></ul>',
link: function (scope, elem, attrs) {
scope.filterOb = {ttype: scope.type};
}
}
});
When I dont use nested directives, it works just fine with that piece of code:
elem.bind('click', function() {
scope.$apply(function() {
scope.loadInformation();
});
});
But when they are nested, the second custom diretive is just looking inside the scope of the parent directive. Any idea how to pass through the function call?
It looks like the ngInputString directive already takes in some data and passes it down to ngSavedInformation. Why not have it take in a loadInformation callback as well and pass it down?
angular.module('...').directive('ngInputString', function () {
return {
scope: {
savedInformation: '=',
type: '#',
values: '=',
loadInformation: '&'
},
// etc
}
});
<ng-saved-information type="STRING" saved-information="savedInformation" load-information="loadInformation()"></ng-saved-information>
angular.module('semtrosApp').directive('ngSavedInformation', function () {
return {
scope: {
savedInformation: '=',
type: '#',
loadInformation: '&'
},
// etc
}
});
Also, instead of manually creating a click handler, you could do the following in the template:
ng-click="loadInformation()"

Call method associated with directive in linking function

I have the following code:
.directive('ticketingChat', function() {
return {
restrict: 'E',
templateUrl: '/Reply/ReplyScreen',
scope: {},
controller: "TicketController",
link: function (scope, el, attr, ctrl) {
if (attr["ticketid"]) {
ctrl.loadTicketById(attr["ticketid"]);
}
}
}
})
I am trying to call the "loadTicketById" in my controller. However I can't seem to get an instance of the controller in the linking function whatever I do.
Other than that, the controller works in the directive.

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'

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

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?

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: '=',

Categories