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
Related
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()"
I have a directive where i bind the tabInfo to tabdata which is an array.
angular.module('widgets')
.directive('tabs', function() {
return {
restrict: 'E',
require: 'info',
transclude: true,
scope: {},
controllerAs: 'tabs',
bindToController: {
tabInfo: '=tabdata'
},
templateUrl: 'Template.html',
link: function(scope, element, attrs, tabs) {
scope.$watch('tabs.tabInfo', function() {
tabs.populateDataProvider();
}, true);
},
controller: ['$filter', '$state', function($filter, $state) {
}]
};
});
I need to watch on the "tabInfo" for any change and populate some data. However , my watch is not called at all. Is there something wrong here , in the way that I am refering to tabs.tabInfo?
A better solution to this would be to have an ng-if along with the directive so as to make sure that the directive is rendered only when there is data to populate . This avoids the need of having a watcher inside the directive .
So you could use ,
<tabs ng-if="$ctrl.tableData.length" tab-info="$ctrl.tableData"></tabs>
For your question above , you need to bind this to a variable to tabs
to get it work .
var tabs = this;
EDIT:
There is also a correction on your code . It should be ,
angular.module('widgets')
.directive('tabs', function() {
return {
restrict: 'E',
require: 'info',
transclude: true,
bindToController: true,
controllerAs: 'tabs',
scope: {
tabInfo: '=tabdata'
},
templateUrl: 'Template.html',
link: function(scope, element, attrs, tabs) {
scope.$watch('tabs.tabInfo', function() {
tabs.populateDataProvider();
}, true);
},
controller: ['$filter', '$state', function($filter, $state) {
}]
};
});
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);
};
};
};
})
I have the following code in angular.
<div vh-accordion-group id="{{$index}}" panel-class="panel-info">
<div vh-accordion-header> </div>
<div vh-accordion-body> </div>
</div>
The HTML for the accordion-group is :
<div class="panel panel-info" ng-transclude>
</div>
Also, the js for the same is :
home.directive("vhAccordionGroup", ['version', function (version) {
return {
restrict: 'EA',
replace: true,
transclude: true,
scope: {
panelClass: '#'
},
templateUrl: "JS/HomeModule/Directives/vhAccordion/vhAccordionGroup.html?v=" + version,
controller: ['$scope', '$element', function ($scope, $element) {
$scope.accordionId = $element.attr("id") + "vhAcc";
this.getAccordionId = function () {
return $scope.accordionId;
};
}],
link: function (scope, element, attrs, vhAccordionGroupController) {
element.addClass(scope.panelClass);
scope.accordionId = element.attr("id") + "vhAcc";
}
};
}]);
These accordions are called in a loop using ng-repeat.
The problem is the {{$index}} which I am passing in the Id is being taken as a string rather then an expression.
I need each of the accordions to have a unique Id so that they can open and close correctly.
Please let me know what I am missing, as I am a newbie to angular.
Simply add id to the isolated scope:
home.directive("vhAccordionGroup", ['version', function (version) {
return {
restrict: 'EA',
replace: true,
transclude: true,
scope: {
panelClass: '#',
id: '#'
},
templateUrl: "JS/HomeModule/Directives/vhAccordion/vhAccordionGroup.html?v=" + version,
controller: ['$scope', '$element', function ($scope, $element) {
$scope.accordionId = $scope.id + "vhAcc";
this.getAccordionId = function () {
return $scope.accordionId;
};
}],
link: function (scope, element, attrs, vhAccordionGroupController) {
element.addClass(scope.panelClass);
scope.accordionId = scope.id + "vhAcc";
}
};
}]);
Then, use $scope.id (and scope.id) instead of $element.attr. $element.attr gives access to the actual DOM element and so it makes sense that the value would not be interpolated.
I wrote two directives in angularjs, one is embedded in the other one.
below is the scripts for the directives:
module.directive('foo', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div id="test" ng-transclude></div>',
controller: function($scope) {
this.scope = $scope;
return $scope.panes = [];
},
link: function(scope, element, attrs) {
return $log.log('test1', scope.panes);
}
};
}
]);
module.directive('bar', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '^?foo',
controller: function($scope) {
return this.x = 1;
},
template: '<div ng-transclude></div>',
link: function(scope, element, attrs, fooCtrl) {
return $log.log('test2', fooCtrl);
}
};
}
]);
below is the piece of html:
<foo ng-controller="IndexController">
<bar></bar>
</foo>
below is the element generated, inspected from the chrome developer tools
<div id="test" ng-transclude="" ng-controller="IndexController" class="ng-scope">
<div ng-transclude="" class="ng-scope"></div>
</div>
below is chrome console output:
test2
Array[0]
length: 0
__proto__: Array[0]
angular.js:5930
test1
Array[0]
length: 0
__proto__: Array[0]
Question:
The child directive can not get the parent directive's controller, so the fourth parameter "fooCtrl" for link function of "bar" is an empty array. what's the thing that I do it wrong?
update and answer:
At last I found the reason that made the weird result. It's just a stupid mistake that I have made:
// in directive "foo"
controller: function($scope) {
this.scope = $scope;
// This line below is wrong. It is here
// because I transcompiled coffeescript to js.
// return $scope.panes = [];
// It should be like below:
$scope.panes = []
// I should have written .coffee like below
// controller: ($scope) ->
// #scope = $scope
// $scope.panes = []
// return # prevent coffeescript returning the above expressions.
// # I should rather have added the above line
}
After correcting the mistake, I tried and found there's nothing to prevent using controller or providing empty content in child directives.
AFAIK, you cannot have a controller in the child directive.
Demo: http://plnkr.co/edit/kv9udk4eB5B2y8SBLGQd?p=preview
app.directive('foo', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div id="test" ng-transclude></div>',
controller: function($scope) {
$scope.panes = ['Item1','Item2','Item3']
return {
getPanes: function() { return $scope.panes; }
};
},
link: function(scope, element, attrs, ctrl) {
$log.log('test1', ctrl, ctrl.getPanes(), scope.panes);
}
};
}
]);
I removed the child controller.
app.directive('bar', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '^?foo',
template: '<div ng-transclude></div>',
link: function(scope, element, attrs, ctrl) {
scope.x = 1;
$log.log('test2', ctrl, ctrl.getPanes(), scope.panes, scope.x);
}
};
}
]);