Angular $watch not working on controller variable updated by directive - angularjs

I am trying to place a watch on controller variable which gets updated from a directive using function mapping. variable is getting updated and logged in console but watch on it not working.
Code Snippet :
index.html
<body ng-app="myApp" ng-controller="myCtrl">
<div>
<test on-click="update()"></test>
</div>
app.js
var myApp = angular.module('myApp', []);
myApp.controller('myCtrl', function($scope){
$scope.test = {
value: false
};
$scope.update = function() {
$scope.test.value = !$scope.test.value;
console.log("Update: " + $scope.test.value);
};
$scope.$watch('test', function(newVal){
console.log("Watch: " + newVal.value);
}, true);
});
myApp.directive('test', function($compile){
return {
restrict: 'E',
transclude: true,
replace: true,
scope: {
onClick: '&'
},
template: '<div ng-transclude=""></div>',
link: function(scope, element, attrs) {
var $buttonElem = $('<button>Test</button>').appendTo(element);
$buttonElem.click(function(){
scope.onClick();
});
}
}
});
Plunker Link is : https://plnkr.co/edit/41WVLTNCE8GdoCdHHuFO?p=preview

The problem is that the directive is raising the event using code that is not apart of AngularJS instead of using an ng-click in its template. If you can't modify the directive, then wrap your event handler in $scope.$apply instead.
$scope.update = function() {
$scope.$apply(function(){
$scope.test.value = !$scope.test.value;
console.log("Update: " + $scope.test.value);
});
};

Related

How to make my own ng-change and ng-model in a directive?

I am trying to implement a directive with its own model and change attribute (as an overlay for ng-model and ng-change). It works apparently fine but when the function of the father scope is executed and some variable of the scope is modified in it, it is delayed, the current change is not seen if not the one executed in the previous step.
I have tried adding timeouts, $apply, $digest ... but I can not get it synchronized
angular.module('plunker', []);
//Parent controller
function MainCtrl($scope) {
$scope.directiveValue = true;
$scope.textValue = "init";
$scope.myFunction =
function(){
if($scope.directiveValue === true){
$scope.textValue = "AAAA";
}else{
$scope.textValue = "BBBB";
}
}
}
//Directive
angular.module('plunker').directive('myDirective', function(){
return {
restrict: 'E',
replace: true,
scope: {
myModel: '=model',
myChange: '&change'
},
template: '<span>Check<input ng-model="myModel" ng-change="myChange()"
type="checkbox"/></span>',
controller: function($scope) {
},
link: function(scope, elem, attr) {
var myChangeAux = scope.myChange;
scope.myChange = function () {
setTimeout(function() {
myChangeAux();
}, 0);
};
}
});
// Html
<body ng-controller="MainCtrl">
<my-directive model="directiveValue" change="myFunction()"></my-directive>
<div>Valor model: {{directiveValue}}</div>
<div>Valor texto: {{textValue}}</div>
</body>
The correct result would be that the "myFunction" function runs correctly
Example: https://plnkr.co/edit/q3IqRCIhwLChlGrkDxyO?p=preview
You should use AngularJS' $timeout which is a wrapper for the browser default setTimeout and internally calls setTimeout as well as $digest, all at the right time in the execution.
Your directive code should change as such:
angular.module('plunker').directive('myDirective', function($timeout){
return {
restrict: 'E',
replace: true,
scope: {
myModel: '=model',
myChange: '&change'
},
template: '<span>Check<input ng-model="myModel" ng-change="myChange()" type="checkbox"/></span>',
controller: function($scope) {
},
link: function(scope, elem, attr) {
var myChangeAux = scope.myChange;
scope.myChange = function () {
$timeout(myChangeAux, 0);
};
}
};
});
Docs for AngularJS $timeout

How to handle ng-click inside directive Angular JS?

I have the following directive:
.directive("feedList", function() {
return {
restrict: 'E',
scope: {
feeds: '=feedData'
},
templateUrl: 'jstemplates/feed-list.html',
link: function(scope) {
angular.forEach(scope.feeds, function(value, key) {
if(value.who.fullname == " "){
scope.feeds[key].fullname = "email";
}
console.log(value.who.fullname);
});
}
}
})
Inside my template there is an event: ng-click="do()". How to handle this event in directive ot in parent controller?
As it's your isolated scope directive, so pass the callback function and then call that function directly from template or from controller or link function.
Working fiddle
var app = angular.module('myApp', []);
app.controller('AppCtrl', function($scope){
$scope.testFunction = function(){
alert("Called from isolated scope directive");
};
});
app.directive("isolatedScopeDirective", function(){
return{
scope:{
go:"&"
},
template : `<button ng-click='go()'>Test Button</button>`
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="AppCtrl">
<isolated-scope-directive go="testFunction()"></isolated-scope-directive>
</div>

Rebinding this on angular callback from inside link funtion

I have a directive similar to this this:
app.directive('example', function() {
return {
restrict: 'E',
transclude: true,
scope: {
callback: '&'
},
template: '<span ng-click="example.callback()">Click Me</span>',
bindToController: true,
controllerAs: 'example',
controller: function() {
this.counter = 0;
this.incrementCount = function() {
this.counter++;
};
this.getCount = function() {
return this.counter;
};
},
link: function(scope, el, attrs, ctrl) {
var oldCallback = scope.callback;
ctrl.callback = function() {
console.log(ctrl);
return oldCallback.call(ctrl); // I want to be able to use `this` as the controller to access the API from within the callback
};
}
};
});
with a controller
app.controller("ctrl", ["$scope", function(s) {
s.callback = function() {
this.incrementCount();
console.log("Value: " + this.getCount());
};
}]);
And view
<div ng-app="app">
<div class="container" ng-controller="ctrl">
<example callback="callback()"></example>
</div>
</div>
(codepen)
When I log ctrl in within the ctrl.callback in the link function it logs the example controller as I expect but when oldCallback is called, it doesn't get ctrl rebound to this as I want. Is there any way to access the API defined in the directive's controller from within the callback on the scope while still using an isolate scope for the directive?
You could pass the directives controller out through the callback. e.g.
example html
<span ng-click="example.callback({$exampleCtrl:example})">Click Me</span>
index html
<example callback="callback($exampleCtrl)"></example>
controller
$scope.callback = function($exampleCtrl) {
$exampleCtrl.incrementCount();
console.log("Value: " + $exampleCtrl.getCount());
};
http://codepen.io/anon/pen/BzqqzV
Also note that bindToController is only supported in AngularJs 1.3+ and your code pen was using 1.2

Calling controller function from directive using bindToController

I wrote a plunker to see how to use bindToDirective to isolate scopes and using directive controller to call main controller function, but, I am doing something wrong. Could you suggest?
This is the plunker: http://plnkr.co/edit/UJLjTmIiHydHr8qRzAsX?p=preview
Code sample:
.controller('Ctrl', function() {
var self = this;
self.func = function() {
console.log('In the controller function');
};
})
.directive('myDirective', [ function() {
var self = {};
self.link = function (scope, elem, attrs, ctrl) {
elem.bind('click', function () {
ctrl.ctrlFunc();
});
elem.addClass('fa fa-file-excel-o fa-lg');
};
return {
restrict: 'E',
scope: {},
controller: function () {
},
controllerAs: 'DirCtrl',
bindToController: {
ctrlFunc: '&'
},
link: self.link
};
}])
html sample to associate main controller function to directive:
<div ng-controller="Ctrl">
<my-directive ctrlfunc="Ctrl.func()"></my-directive>
</div>
You have a number of issues:
You need a hyphen in your directive argument name and you should be passing the function reference, not calling the function directly (with params):
<my-directive ctrl-func="ctrl.func"></my-directive>
Second, you are using alias syntax in your controller (var self = this;), but not in your template. You need to update it to the following:
<div ng-controller="Ctrl as ctrl">
<my-directive ctrl-func="ctrl.func"></my-directive>
</div>
Finally, pass down the function reference with two-way binding instead of with & since that passes down values for implicit evaluation.
bindToController: {
ctrlFunc: '='
},
See working plunkr
I'm not sure you need bindToController...
This version calls your Ctrl's function: http://plnkr.co/edit/Rxu5ZmmUAU8p63hR2Qge?p=preview
JS
angular.module('plunker', [])
.controller('Ctrl', function($scope) {
$scope.func = function() {
console.log('In the controller function');
};
}) angular.module('plunker', [])
.controller('Ctrl', function($scope) {
$scope.func = function() {
console.log('In the controller function');
};
})
.directive('myDirective', [ function() {
return {
template: '<pre>[clickme]</pre>',
replace: true,
restrict: 'E',
scope: {
target: '&'
},
link: function (scope, elem, attrs) {
elem.bind('click', function () {
var fn = scope.target && scope.target(scope);
fn && fn();
});
elem.addClass('fa fa-file-excel-o fa-lg');
}
};
}])
HTML
<div ng-controller="Ctrl">
<my-directive target="func"></my-directive>
</div>

2 way binding not working on directive in isolated scope

I'm just trying to do a simple directive, but for some reason the 2 way data binding isn't working in my directive. From my code you can see that a console log in the directive that will read the correct information I have in the $scope.displayMaintenance variable, but I can't change it in my directive.
HTML:
<maintenance-banner display-maintenance="displayMaintenance"></maintenance-banner>
Controller:
$scope.displayMaintenance = false;
$scope.$watch('displayMaintenance', function(data) {
console.log("i changed!: " + data);
});
Directive:
.directive('maintenanceBanner', function() {
return {
restrict: 'E',
replace: true,
scope: {
displayMaintenance: '='
},
templateUrl: '/partials/navbar/maintenance-banner.html',
link: function(scope) {
console.log(scope.displayMaintenance);
scope.displayMaintenance = true;
}
};
})
Any suggestions?
The issue may be that you use your directive inside another isolated scope.
I have created a sample: http://jsfiddle.net/2063n7te/
changing the model value using assignment replaces the model object which may not be reflected in the parent scope.
in short: do not bind primitives directly to the scope.
instead of
$scope.text = "foo";
use
$scope.input = {
text: "foo"
};
a good read is: http://www.thinkingmedia.ca/2015/01/learn-how-to-use-scopes-properly-in-angularjs/
point #4 applies specifically to the behaviour you are seeing.
Check that your directive can find the template URL.
Works for me:
var app = angular.module('app',[]);
app.controller('ctrl', function($scope) {
$scope.displayMaintenance = false;
$scope.$watch('displayMaintenance', function(data) {
alert("i changed!: " + data);
});
});
app.directive('maintenanceBanner', function() {
return {
restrict: 'E',
replace: true,
scope: {
displayMaintenance: '='
},
template: '<div>{{displayMaintenance}}</div>',
link: function(scope) {
console.log(scope.displayMaintenance);
scope.displayMaintenance = true;
}
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<maintenance-banner display-maintenance="displayMaintenance"></maintenance-banner>
</div>

Resources