Angular call controller function from nested custom directive - javascript

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()"

Related

Multiple directives asking for template

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>

AngularJS - follow scope in custom directive

I have custom directive and want to send some parameters(objects) to this directive.
In directive I have select list based on one of this parameters and I need to follow this parameter.
My directive:
<custom-directive rows="users">
</custom-directive>
and template:
<ui-select ng-model="value">
<ui-select-match allow-clear="true">
{{$select.selected}}
</ui-select-match>
<ui-select-choices repeat="row in rows | filter: $select.search">
<div>
<span ng-bind-html="row | highlight: $select.search"></span>
</div>
</ui-select-choices>
</ui-select>
normally if rows change ui-select will automatically update select list. But if rows is variable from outside of this directive, it wont.
Can I have the same variable outside and inside directive?
I think I can use ng-model, but I'm not sure it is good solution.
EDIT:
My directive code:
.directive('custom-directive', function () {
return {
restrict: 'E',
replace: true,
scope: {
rows: '='
},
templateUrl: 'template.html',
controller: ['$scope', function($scope) {
console.log($scope);
}]
};
})
You can modify your custom directive to work with ng-model in the following way:
directive('customDirective', function() {
return {
// ...
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if (!ngModel) return;
// Write data to the model
// Call it when necessary
scope.setValue = function(value) {
ngModel.$setViewValue(value);
}
}
};
});
Initial state of ng-model value can be retrieved using that method:
ngModel.$render = function() {
var value = ngModel.$viewValue || [];
};
You can use link instead of the controller to handle functionality with rows. Setting rows : '=' should already take care of the two-way binding.
.directive('custom-directive', function () {
return {
restrict: 'E',
replace: true,
scope: {
rows: '='
},
templateUrl: 'template.html',
link: function(scope, element, attr) {
console.log(scope.rows);
};
};
})
EDIT:
Add a watch to the value that you need to do behavior on external change.
.directive('custom-directive', function () {
return {
restrict: 'E',
replace: true,
scope: {
rows: '='
},
templateUrl: 'template.html',
link: function(scope, element, attr) {
scope.$watch('rows', function(oldValue, newValue) {
console.log('This is the newValue : ' + newValue);
};
};
};
})

angularjs: multiple binded functions in directive

I have a function in my directive that is bound to a function in my controller as follows:
HTML:
<div graph-visualization data="graphdata" type="graphtype" data-method="watchMonth" timespan="timespan" id="graph" ng-if="!loading">
</div>
Directive:
app.directive('graphVisualization', function() {
return {
restrict: 'A',
scope: {
data: '=',
type: '=',
timespan: '=',
monthFilter: '&method'
},
link: function(scope, element, attrs) {
scope.updateMonth = function() {
var func = scope.monthFilter();
func(scope.month)
}
scope.$watchGroup(['data', 'timespan', 'type'], function(newval, oldval) {
scope.month = 'something'
scope.updateMonth()
})
})
Controller:
$scope.watchMonth = function(value) {
//handle value passed from directive
}
As you can see I have a binded function 'watchMonth' in my controller which is called from the directive.
Now, I want to add another function in my directive which is binded to some other function in my controller. How do I go about it? Can't seem to get my head around it.
What I want is that I am handling a click in my directive and getting a value based on that click. Now, I want to pass this value to the controller which will modify 'graphdata'(on which I have supplied a watchgroup) in the controller.
In the html add the methods as shown:
month-filter="watchMonth()" second-function="function()"
Be sure to add parentheses at the end of the function name
<div graph-visualization data="graphdata" type="graphtype" month-filter="watchMonth()" second-function="secondFunction()" timespan="timespan" id="graph" ng-if="!loading">
app.directive('graphVisualization', function() {
return {
restrict: 'A',
scope: {
data: '=',
type: '=',
timespan: '=',
monthFilter: '&',
secondFunction: '&'
},
link: function(scope, element, attrs) {
scope.updateMonth = function() {
var func = scope.monthFilter();
func(scope.month)
}
scope.$watchGroup(['data', 'timespan', 'type'], function(newval, oldval) {
scope.month = 'something'
scope.updateMonth()
})
})
added a plunker, maybe that'll help
http://plnkr.co/edit/cISSlQpQF6lFQrDdbTg4?p=preview

How to make two way binding between angular directives without using "dots"

Suppose, I have controller:
angular.module('tf').controller('Ctrl', function($scope){
$scope.params = {
orderBy: null
};});
And a directive "common":
angular.module('tf').directive("common", function() {
return {
restrict: 'E',
replace: true,
template: '<div><outer order-by="orderBy"><inner order-by-field="name1"></inner><inner order-by-field="name2"></inner></outer></div>',
controller: function ($scope) {
},
scope: {
orderBy: '='
},
link: function (scope, element, attrs) {
}
}});
Controller is using directive within it's template:
<div ng-app="tf">
<div ng-controller="Ctrl">
<common order-by="params.orderBy"></common>
<div style="color:red">{{params.orderBy}}</div>
</div>
This directive is using directive "outer":
angular.module('tf').directive("outer", function() {
return {
restrict: 'E',
transclude: true,
replace: true,
template: '<div ng-transclude></div>',
controller: function ($scope) {
this.order = function (by) {
$scope.orderBy = by
};
},
scope: {
orderBy: '=',
}
}});
Which is parent for the directive "inner":
angular.module('tf').directive("inner", function() {
return {
require: '^outer',
restrict: 'E',
transclude: true,
replace: true,
template: '<div ng-click="onClicked()">{{orderByField}}</div>',
controller: function ($scope) {
$scope.onClicked = function () {
$scope.outer.order($scope.orderByField);
}
},
scope: {
orderByField: '#'
},
link: function (scope, element, attrs, outer) {
scope.outer = outer;
}
}});
The directive "outer" shares "order" method with directive "inner" by it's controller. The directive "inner" is accessing it by using "require" mechanism.
For some reason, this is not working as expected (Property of the controller isn't updated each time it's changed by directive). If I place "orderBy" into object (e.g. {"order": {"by": null }} ) and use object instead of string value, everything is working as expected ( controller scope is properly updated by the directive). I know about "always use dots" best practices principle, but I don't wanna use it here, because it would make my directive's API less intuitive.
Here is jsfiddle:
http://jsfiddle.net/A8Vgk/1254/
Thanks

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