Can I two-way bind input field in directive template? - angularjs

Having a following template in templateUrl:
<input name="foo" ng-model="test">
directive:
app
.directive('bar', function() {
return {
link: function link(scope, element, attrs, ctrl) {
scope.$watch(scope.test, function(newVal) {
console.log(val);
});
},
restrict: 'E',
templateUrl: 'templates/foo.html'
};
});
can I two-way bind it in directive so I scope.$watch input variable?
I tried using ng-bind and ng-model, but I cannot access that variable in scope of my directive.
Edit
Added directive code.

Change:
scope.$watch(scope.test, ...
to
scope.$watch('test', ...
and it should work. The first argument to $watch is the (so called) watchExpression. It will be evaluated against the relevant scope. When using a string you can basically use everything you would also use in the views/templates.
Mind that this will break again if you start using isolated scopes.

Related

How to pass object to directive through attribute in AngularJS?

I want to pass object (reference - two way binding) through ATTRIBUTE not by isolated scope. How can I do this? Because code bellow passing string instead of object:
HTML
<tr ng-form="rowForm" myDirective="{{row.data}}">
Directive
angular.module("app").directive("myDirective", function () {
return {
require: ["^form"],
restrict: "A",
link: function (scope, element, attrs, ctrls) {
scope.$watch(function () {
return attrs.myDirective;
}, function (newValue, oldValue) {
// .....
Directives can do two-way data binding without parsing or compile anything manually, sorry for not delivering the plunker but it's rebelius and won't save for me
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.myObj = {name: 'Tibro', age: 255}
})
.directive('myDirective', function(){
return {
scope: {
'myAttribute': '='
},
template: '{{myAttribute}}',
link: function(scope){
scope.myAttribute.age= 31
}
}
})
HTML
<body ng-controller="MainCtrl">
controller: {{myObj}} <br/>
directive: <my-directive my-attribute="myObj"></my-directive>
</body>
OUTPUT
controller: {"name":"Tibro","age":31}
directive: {"name":"Tibro","age":31}
you can see from the output that passed object has been binded two-way and change made in directive is reflected on controller level
The result of {{ }} interpolation is a string. An object can't be passed like that.
Bindings are idiomatic here and thus preferable. The whole thing becomes messy when the directive is forced to use parent scope. However, it can be done by parsing scope properties manually with $parse:
$scope.$watch(function () {
var myDirectiveGetter = $parse($attrs.myDirective);
return myDirectiveGetter($scope);
}, ...);
This is a job for binding (< or =, depending on the case). If isolated scope isn't desirable, this can be done with inherited scope and bindToController:
scope: true,
bindToController: {
myDirective: '<'
},
controllerAs: `vm`,
controller: function ($scope) {
$scope.$watch('vm.myDirective', ...);
}
Notice that directive attribute is my-directive, not myDirective.
Couple of things:
myDirective in the tr should be my-directive, as per Angular's
conventions.
{{row.data}} prints the variable, you need to pass it without the
{{}} for it to go through as an object.

Unable to share controller instance via require in angular directive

I have a confirm box which I want to attach another directive to called confirmBoxToggle, but I'm unable to share the same controller instance in order for it to work. I've looked at multiple examples and also read the docs to see if I'm doing something crazy, but the only thing I can see is that I don't declare my controller inside the directive but rather giving a reference to it. But I can't see this being the issue.
I get this error when doing this:
Controller 'confirmBox', required by directive 'confirmBoxToggle', can't be found!
What am I doing wrong?
The box directive:
core.directive('confirmBox', [function() {
return {
scope: {},
controller: 'ConfirmBoxCtrl',
controllerAs: 'confirmBox',
templateUrl: 'app/views/components/core/confirmation-box.html',
link: function(scope, element, attrs, ctrl) {
}
};
}]);
The toggle directive:
core.directive('confirmBoxToggle', [function() {
return {
scope: {},
require: '^confirmBox',
link: function(scope, element, attrs, ctrl) {
element.on('click', function() {
ctrl.toggleBox();
});
}
};
}]);
The controller for both directives:
core.controller('ConfirmBoxCtrl', [function() {
var confirmBox = this;
confirmBox.toggleBox = function() {
confirmBox.isActive = !confirmBox.isActive;
};
}]);
I use the directives like this:
<confirm-box></confirm-box>
<span confirm-box-toggle>Delete</span>
Controller confirm or confirmBox can't be found?
Do you use that controller elsewhere, and does it work on it's own?
Basically you used require: '^confirmBox' that means while using confirmBoxToggle directive, it must be wrap with confirmBoxdirective(should be there in parent element as ^) so that you could access to the confirmBox link function 4th parameter.
HTML
<confirm-box>
<span confirm-box-toggle>Delete</span>
</confirm-box>
Also you can't have templateUrl inside your confirmBox directive, which will replace your <span confirm-box-toggle>Delete</span> html by the template loaded form templateUrl.
Demo Plunkr

angular directive - scope undefined inside function

I can't seem to reach the link function scope variable from inside a function in my directive. The "elem" variable is defined, but the scope isn't. Why is that??
Here's my directive:
function contextMenu($document){
return {
scope: {
target: '=',
},
restrict: 'A',
link: function(scope, elem, attr) {
elem.bind('contextmenu',handleRightClick);
function handleRightClick(event) {
// do something with scope (scope is undefined)
}
}
}
};
How can I user the scope variable?
Thanks!
Uri
EDIT1:
I found I can use this to pass the scope to the function:
Passing parameters to click() & bind() event in jquery?, but this still doesn't explain why the scope variable is undefined.
EDIT2:
For completeness sake, this is how my directive is set up:
app.js
angular
.module('myModule', [])
.directive('contextMenu', ['$document', components.contextMenu])
and in the html:
<div context-menu target="testObject">
Make sure you are using the directive correctly. Since you didn't include the use of the directive in your template, I hope you used it something like this:
<div context-menu target="something"></div>
Then I am confused about the setup of your directive. Try this:
MyDirectiveModule.directive('contextMenu', function(){
return {
restrict: 'A',
scope: {
target: '#'
},
link: function(scope, element){
console.log(scope);
// don't use $scope!
}
};
});
Make sure to use scope instead of $scope in the link: part.

Angular transcluded directive as sibling scope incompatible with child directive's require: "^parent" setting

I'm having an issue with the way AngularJS handles transcluded scopes for directives. It is known that a transclude's scope is a sibling of its directive's scope, not a child. Shown here
However, I have a situation with a child directive:
<div price-chart>
<div volume-indicator></div>
</div>
The priceChart directive has a transclude: true, template: "<div ng-transclude></div>" but the (transcluded) volumeIndicator requires the parent to be a priceChart, not the sibling.
function VolumeIndicatorDirective() {
return {
restrict: "EA",
controller: "VolumeIndicatorController",
require: ["^priceChart", "volumeIndicator"],
compile: function compile(element, attrs, transclude) {
return {
pre: function preLink($scope, element, attrs, controllers) {
var priceChart = controllers[0];
var volumeIndicator = controllers[1];
priceChart.addIndicator(volumeIndicator);
},
post: angular.noop
};
}
};
}
If Angular had a sibling selector for controllers that would solve the issue:
require: ["+priceChart", "volumeIndicator"],
However, this doesn't exist so what can I do?
As per comment from zeroflagL I tried element.parent().controller() to access the directive controller but it seems to get the nearest ng-controller specifically and skips over directive controllers.
According to the docs, require's '^' syntax will try to "locate the required controller by searching the element's parents". Scope prototypal inheritance doesn't play a role here, so your code should work as expected.
And indeed it does !

undefined scope into $watch

I have a directive named dir with:
ng-model="job.start_date"
comparison-date="job.end_date
Into scope.$watch("comparisonDate... I want to access my ng-model value. The problem is that scope is undefined into watch's callback function. The Question is: How can I get the ng-value inside this function?
.directive("dir", function() {
return {
scope: {
comparisonDate: "=",
ngModel: "="
},
link: function (scope, element, attrs, ctrl) {
var foo = scope.ngModel;
scope.$watch("comparisonDate", function(value, oldValue) {
console.log(value); //comparisonDate showing value properly
console.log(scope.ngModel); //Undefined
console.log(foo) //shows value but it's not refreshing. It shows allways the initial value
})
}
};
})
the view...
<input dir type="text" ng-model="job.start_date" comparison-date="job.end_date"/>
During the linking phase of the directive, the value may not be available. You can use $observe to observe the value change.
attrs.$observe("comparisonDate", function(a) {
console.log(scope.ngModel);
})
ng-model is built-in directive that tells Angular to do two-way data binding. http://docs.angularjs.org/api/ng.directive:ngModel
It looks like you are using the value of properties of the same object job to do comparison. If you want to stick with ng-model, you can use NgModelController: http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController
Then change the view to:
<input dir type="text" ng-model="job"/>
and change the directive to:
.directive("dir", function() {
return {
require: '?ngModel', // get a hold of NgModelController
link: function (scope, element, attrs, ngModel) {
// access the job object
ngModel.$formatters.push(function(job){
console.log(job.start_date);
console.log(job.end_date);
});
}
};
})
Or you can change the attribute name from ng-model to some words haven't reserved. For example change the view like:
<input dir type="text" comparison-start-date="job.start_date" comparison-end-date="job.end_date"/>
Try scope.$watch(attrs.comparisonDate, ...) and then use attrs.ngModel

Resources