I was thinking to pass data from my controller to a directive template. How can I do this?
Here is my controller:
$scope.$on('itemSelectedEvent', function (event, args) {
$scope.displayOrgs.push(data.orgName);
})
And the directive:
directive('search', function ($timeout) {
return {
restrict: 'AEC',
scope: {
selecteditemslist: "=",
searchid: "#",
selecteditemsdisplay: "=",
searchobj: '#',
displayOrgs: '=displayOrgs'
},
link: function (scope, elem, attrs, index, searchobj,searchid) {
scope.$emit('itemSelectedEvent', {
selectedId:scope.selectedId,
sortOrder:index,
searchId : searchid
}
);
}
});
This displayOrgs should pass to directive. How is it I am getting undefined?
<li class="tag" ng-repeat="list in displayOrgs track by $index" ng-class="{selected: $index==selectedIndex}" >
<span class="tag-label">{{list}}</span>
<span class="tag-cross pointer" ng-click="Delete($index,selecteditemslist[$index],list,searchobj)">x</span>
</li>
Please help me. Thanks in advance.
There is no need to generate an event scope.$emit to notify controller that variable has got changed. While you have control over the variables with two binding inside its isolate scope: { .. }
displayOrgs: '=displayOrgs' has = means two way binding with variable associated with directive
element attribute div.
As you are using displayOrgs: '=displayOrgs' (in isolated scope) in your directive, the changes in any of the variable of the which is mentioned in isolated scope, that will change automatically in applies those changes to scope variable which is associated with display-orgs attribute.
link: function (scope, elem, attrs) {
scope.displayOrgs.push(data.orgName); // this will update binding with parent scope.
});
Related
I'm creating a directive to show differences in text. For this directive, I need a couple of buttons which I've split up in directives. A simpliefied example would be:
.directive('differenceViewer', function($templateCache, $compile) {
return {
restrict: 'E',
scope: {
oldtext: '#',
newtext: '#',
template: '#',
heading: '#',
options: '=',
itemdata: '&',
method: '&'
},
replace: false,
link: (scope, element, attr) => {
element.append(angular.element($compile($templateCache.get(scope.template))(scope)));
}
};
}).directive('diffbutton', function() {
return {
restrict: 'E',
scope: {
method: '&'
},
template: '<button class="btn btn-sm btn-success" ng-click="method()">Rollback</button>',
replace: true,
terminal: true,
link: (scope, element, attr) => {
scope.directiveClick = function(){
console.log("directive method"); // is never called
}
}
}
})
I compile the html via a template script:
<script type="text/ng-template" id="differenceViewer.html">
<div class="ibox-footer">
<div class="row">
<div class="col-md-12">
<diffbutton method="clickedBtn()">Foo</diffbutton>
</div>
</div>
</div>
</script>
Where the diffbutton is created inside the html compiled by differenceViewer.
I need to call a method in the controller that is responsible for creating all the difference-views.
app.controller('MainCtrl', function($scope) {
$scope.clickedBtn = function() {
console.log('foo'); // is never called
}
})
Here's a plunker demonstrating the issue.
What do I need to change in order to be able to forward the button click on my directive in a directive to the controller method?
I've been working with the answers of this question but still can't make it work.
Note that, if I add
scope.clickedBtn = function() {console.log("bar");}
to the differenceViewer directive, it gets called - however I need to call the method in the controller instead.
Pass on a method from the parent to the child element and then trigger it on click. For example (pseudo code coming in )
<parent-directive>
<child-directive totrigger="parentClickCallback()"></child-directive>
</parent-directive>
then in your parent directive you set
$scope.parentClickCallback = function(){//do whatever you neeed}
and on your child in its scope binding you set
scope:{
totrigger:'&'
}
and on that child button you simply add
<button ng-click="totrigger()">ClickMe</button>
every time you click that button it will trigger parentClickCallback by reference attached to totrigger.
Why would you need to complicate your code so much I'm not really sure. If you not happy with scope binding just require controller in your directive and pass the controller bound function.
Hello I think I don't understand what two-way data binding is. First the code:
.directive('mupStageButtons', function() {
return {
transclude: true,
template: '<span ng-transclude></span>',
replace: true,
scope: {
property: "=",
action: "="
},
controller: function($scope) {
console.log($scope); //I can see the property of $scope defined in console
console.log($scope.property); //undefined
this.property = $scope.property;
this.changeStage = $scope.action; //anyway this is ok
},
};
})
.directive('mupStageButton', function() {
return {
transclude: true,
templateUrl: '/static/templates/directives/StageButton.html',
require: '^^mupStageButtons',
scope: {
value: "=",
btnClass: "#",
},
link: function(scope, element, attrs, mupStageButtonsCtrl, transclude) {
scope.property = mupStageButtonsCtrl.property;
scope.changeStage = mupStageButtonsCtrl.changeStage;
}
};
})
//html
<mup-stage-buttons property="company.stage" action="setStage">
<mup-stage-button value="0" btn-class="btn-default-grey">
</mup-stage-button>
</mup-stage-buttons>
//controller for that html ^^^
.controller('CompanyDetailController', function($scope, $stateParams, Company){
Company.query ({
id : $stateParams.companyId
}, function (data) {
$scope.company = new Company(data);
});
}
//template for <mup-stage-button>
<label ng-class="property === value ? 'active' : 'btn-on-hover' " class="btn {{btnClass}}" ng-click="changeStage(value)">
<div ng-transclude></div>
</label>
Does the "=" mean, that the change in outside scope will propagate thanks to data binding? Or not? Because I fetch a $resource and it is of course defined after the time it is fetched, but the "property" remains undefined. So what is wrong?
EDIT: desired behavior is that the ng-class in the template for <mup-stage-button> works
EDIT: plunker: https://plnkr.co/edit/drXxyMpd2IOhXMWFj8LP?p=preview
You are missing an important thing about the transclude option: the wrapped content is bound to the OUTER scope rather than the directive's scope.
So, here how the scope bindings will look in your case after compilation:
<div ng-controller="CompanyDetailController">
<mup-stage-buttons property="company.stage" action="setStage"> <-- even though the 'property' is bound correctly, it is not available below due to transclusion -->
<span ng-transclude>
{{company.stage}} <!-- CompanyDetailController $scope available here due to transclusion, 'property' is not available! -->
<mup-stage-button property="company.stage" value="0">
<!-- directive's scope here, binding to the outer scope's 'company.stage' can be used here -->
{{property}} - {{value}} <!-- this will work -->
<label ng-class="property === value ? 'active' : 'btn-on-hover' " class="btn {{btnClass}}" ng-click="changeStage(value)">
<div ng-transclude>
<!-- transcluded content here, bound to the CompanyDetailController $scope -->
not working ng-class 0
</div>
</label>
</mup-stage-button>
</span>
</mup-stage-buttons>
</div>
So, to make your code work (Plunk) it would be enough to map the property to the company.stage on the child directive only.
UPDATE
To avoid repetition of the property="company.stage" binding on the child directives and pass the data through the controller and link function of the parent and child directives respectively, you should use the wrapping object for you scope properties, so that you could pass the reference to that object through. Any changes to this object will be available to the child scopes as they will have a reference to that object, this is called the dot notation:
CompanyDetailController:
$scope.vars = {};
this.getCompany = function () {
$scope.vars.company = $scope.company = {stage: 0};
};
then bind the vars property to the parent directive's scope:
// ...
scope: {
vars: '=',
},
controller: function($scope) {
this.vars = $scope.vars;
}
// ...
then put the reference of vars to the child directive's scope:
// ...
link: function(scope, element, attrs, mupStageButtonsCtrl, transclude) {
scope.vars = mupStageButtonsCtrl.vars;
}
// ...
and finally have access to it in the child directive's view:
<label ng-class="vars.company.stage === value ? 'active' : 'btn-on-hover'">...</label>
This way there is no need to repeat the bindings on the child directive instances.
Plunk is updated.
In javascript
Primitives are passed by value, Objects are passed by "copy of a
reference".
good explanation stackoverflow.com/questions
Solution using $watch:
.directive('mupStageButtons', function() {
return {
transclude: true,
template: '<span ng-transclude></span>',
replace: true,
scope: {
property: "=",
action: "="
},
controller: function($scope) {
that = this;
$scope.$watch('property', function(newValue){
that.property = newValue;
/***Refresh this.property (normal assignment would only copy value,
it would not behave as a reference to desired transcluded property)***/
});
this.changeStage = $scope.action;
},
};
})
.directive('mupStageButton', function() {
return {
transclude: true,
templateUrl: '/static/templates/directives/StageButton.html',
require: '^^mupStageButtons',
scope: {
value: "=",
btnClass: "#",
},
link: function(scope, element, attrs, mupStageButtonsCtrl, transclude) {
scope.btnCtrl = mupStageButtonsCtrl;
scope.changeStage = mupStageButtonsCtrl.changeStage;
}
};
})
An important part besided the $watch is also this in link function:
scope.btnCtrl = mupStageButtonsCtrl;
We could not do
scope.property = mupStageButtonsCtrl.property;
because it would just copy the value, and when it changed in the ctrl, it wouldn't change here in the child directive.
So we assign ctrl reference to scope.btnCtrl and it works.
Template for child directive:
<label ng-class="btnCtrl.property === value ? 'active' : 'btn-on-hover' " class="btn {{btnClass}}" ng-click="changeStage(value)">
<div ng-transclude></div>
</label>
Now I can use the directives generically as I need - pass just the property like company.stage, so that the directive doesn't need to know the property name (stage).
<mup-stage-buttons property="company.stage" action="setStage">
<mup-stage-button value="0" btn-class="btn-default-grey">
Stage 0
</mup-stage-button>
</mup-stage-buttons>
I have a directive treeview which contains a nested directive (being the branches) of each item rendered.
In the scope of both directives I have declared two parameters that should be talking to the parent controller.
filter: '&' //binds the method filter within the nested directive (branch) to the method doSomething() in the tree directive attribute which is bound to the html directive that binds to the controller.
iobj: '=' is the two way binding paramter that should be passing the scoped object to the controller. (but currently isn't)
Directive:
app.directive('tree', function () {
return {
restrict: 'E',
replace: true,
scope: {
t: '=src',
filter: '&',
iobj: '='
},
controller: 'treeController',
template: '<ul><branch ng-repeat="c in t.children" iobj="object" src="c" filter="doSomething()"></branch></ul>'
};
});
app.directive('branch', function($compile) {
return {
restrict: 'E',
replace: true,
scope: {
b: '=src',
filter: '&',
iobj: '='
},
template: '<li><input type="checkbox" ng-click="innerCall()" ng-hide="visible" /><a>{{ b.name }}</a></li>',
link: function (scope, element, attrs) {
var has_children = angular.isArray(scope.b.children);
scope.visible = has_children;
if (has_children) {
element.append('<tree src="b"></tree>');
$compile(element.contents())(scope);
}
element.on('click', function(event) {
event.stopPropagation();
if (has_children) {
element.toggleClass('collapsed');
}
});
scope.innerCall = function () {
scope.iobj = scope.b;
console.log(scope.iobj);
scope.filter();
}
}
};
});
HTML:
<div ng-controller="treeController">
<tree src="myList" iobj="object" filter="doSomething()"></tree>
<a ng-click="clicked()"> link</a>
</div>
Controller:
app.controller("treeController", ['$scope', function($scope) {
var vm = this;
$scope.object = {};
$scope.doSomething = function () {
var item = $scope.object;
//alert('call from directive');
console.log(item);
}
$scope.clicked = function () {
alert('clicked');
}
...
Currently I can invoke the function $scope.doSomething from the directive to the controller. So I know that I have access to the controllers scope from the directive. What I cannot figure out is how to pass an object as a parameter from the directive back to the controller. When I run this code, $scope.object
is always an empty object.
I'd appreciate any help or suggestions on how to go about this.
The & directive binding supports parameter passing. Given your example
scope.filter({message: 'Hello', anotherMessage: 'Good'})
The message and anotherMessage become local variables in the expression bound to directive:
<tree src="myList" iobj="object" filter="doSomething(anotherMessage, message)"></tree>
Here's a sample plunker where the callback parameters are set inside a template.
The documentation clearly states that:
Often it's desirable to pass data from the isolated scope via an
expression to the parent scope, this can be done by passing a map of
local variable names and values into the expression wrapper fn. For
example, if the expression is increment(amount) then we can specify
the amount value by calling the localFn as localFn({amount: 22}).
I want to recreate nsClick behavior with my directive ( changing priority).
So this is my code:
angular.module('MyApp').directive('nsClickHack', function () {
return {
restrict: 'E',
priority: 100,
replace: true,
scope: {
key: '=',
value: '=',
accept: "&"
},
link: function ($scope, $element, $attrs, $location) {
$scope.method();
}
}
});
and the line I'm trying to bind to:
<li ng-repeat="item in items" ns-click-hack="toggle(); item.action()">
toggle and item.action are from other directives.
Can you point me where I was making mistake?
If you are trying to re-create ng-click, then it's probably better to look at the source of the ngClick directive.
For example, it does not create an isolate scope since only one isolate scope can be created on an element and it tries to be accommodating towards other directives. The alternative is to $parse the attribute value, which is what the built-in implementation is doing.
If you are just creating a "poor's man" version of ngClick, then, sure, you could use a callback function "&" defined on the scope, and invoke it when the element is clicked:
.directive("nsClickHack", function(){
return {
restrict: "A",
scope: {
clickCb: "&nsClickHack"
},
link: function(scope, element){
element.on("click", function(e){
scope.clickCb({$event: e}); // ngClick also passes the $event var
});
}
}
});
The usage is as you seem to want it:
<li ng-repeat="item in items" ns-click-hack="toggle(); item.action()">
plunker
Is there a way for not losing connection to the current controller when you are wrapping data with a directive ?
My problem is, that the directive within the wrapped template has no connection to the outside controller any more and so I can not execute the function.
Wrapping Directive:
myApp.directive('wrapContent', function() {
return {
restrict: "E",
scope: {
model: "=",
datas: "="
},
templateUrl: "./any/template.php",
link: function(scope, element, attr) {
// any
}
};
});
Directive within the wrapped Template
myApp.directive('doAction', function() {
return {
restrict: "A",
link: function(scope, elem, attrs) {
$(elem).click(function(e) {
scope.$apply(attrs.doAction);
});
}
}
});
Conroller:
lmsApp.controller('OutsideController', function ($scope){
$sope.sayHello = function() {
alert("hello");
};
});
HTML where I want to execute the function (template.php):
<div>
<do-action="sayHello()"></do-action>
</div>
How I call the wrapContent directive which is outside (Updated):
<div ng-controller="OutsideController">
<wrap-content model="any" datas="data_any"></wrap-content>
</div>
How can I execute the sayHello() function?
Thank you for your help! I would appreciate every answer.
wrapContent directive will be processed with the scope of controller.
DoAction directive will be processed with the isolateScope of wrapContent directive.
Solution1:
Get a reference to the sayHello function in wrapContent using '&' and execute it in event handler.
Solution2:
Instead of using scope in your event handler, use scope.$parent.
You should pass sayHallo function to your parent directive using &
myApp.directive('wrapContent', function() {
return {
restrict: "E",
scope: {
model: "=",
datas: "=",
sayHallo: "&"
},
templateUrl: "./any/template.php",
link: function(scope, element, attr) {
// any
}
};
});
HTML
<div ng-controller="OutsideController">
<wrap-content model="any" datas="data_any" sayHallo="sayHallo()"></wrap-content>
</div>
Then in your child directive, you will have sayHallo in your scope, to call it just do it this:
myApp.directive('doAction', function() {
return {
restrict: "A",
link: function(scope, elem, attrs) {
scope.sayHallo();
}
}
});
And you dont need pass it again. So your child directive should looks like this:
<div>
<do-action></do-action>
</div>
UPDATE
If you want to use all your parent model functions,without passing each function. In your child directive,just use scope.model to have access to model attributes and functions.
myApp.directive('doAction', function() {
return {
restrict: "A",
link: function(scope, elem, attrs) {
scope.model.sayHallo();
}
}
});