AngularJS - Updating a variable in custom directive - angularjs

I have declared a variable in parent scope in one of my controllers.
$scope.myValue = true;
This value is fetched in the custom directive easily. But when the value is updated in the parent controller it is not reflected in the custom directive.
My question is how can we let the directive know that the "myValue" is changed?
If the info is not suffice kindly let me know.

In your parent controller, upon changing the value of the variable, you can broadcast an event like this -
$scope.$broadcast('valueChanged', {
val: $scope.myValue // pass in any object you want
});
And then in your child controllers, handle it like -
$scope.$on('valueChanged', function (event, data) {
console.log(data); // get the same object here
});
$broadcast fires the event down, so this should work in your case.

You can watch the value inside the directive, like this:
app.directive('directive', function() {
return {
scope: true,
link: function($scope){
$scope.$watch('$parent.myValue', function(newVal){
console.log(newVal);
});
}
};
});
Link demo: https://jsfiddle.net/mzrja04L/

Related

AngularJS Directive doesn't update scope for callback

I am using directives to create a component library in AngularJS 1.5. Hence, my directives need to have isolate scopes.
Some of my directives have callbacks so you can pass in a function to get invoked by the directive. However, when that callback is invoked by the directive, it doesn't seem like the changes to $scope attributes are fully updated like I would expect them to be.
Here is a Plunker that shows this behavior:
http://embed.plnkr.co/Rg15FHtHgCDExxOYNwNa/
Here is what the code looks like:
<script>
var app = angular.module('myApp', []);
app.controller('Controller', ['$scope',function($scope) {
// initialize the value to something obvious
$scope.clickersValue = "BEFORE";
// when this call back is called we would expect the value to be updated by updated by the directive
$scope.clickersCallback = function() {
//$scope.$apply(); // $apply is not allowed here
$scope.clickersValueRightAfterCall = $scope.clickersValue;
console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
};
}
]);
app.directive('clicker', [function() {
return {
restrict: 'EA',
template: '<div ng-click="clicked()">click me!</div>',
controller: ['$scope', function($scope) {
$scope.clicked = function() {
console.log("you clicked me.");
$scope.newValue = 'VALID';
$scope.myUpdate();
}
}],
scope: {
"newValue": "=",
"myUpdate": "&"
}
};
}]);
</script>
So when clickersCallback gets invoked the clickersValue attribute still has the old value. I have tried using $scope.$apply but of course it isn't allowed when another update is happening. I also tried using controller_bind but got the same effect.
Wrap the code inside clickersCallback function in a $timeout function.
$timeout(function() {
$scope.clickersValueRightAfterCall = $scope.clickersValue;
console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
});
Updated plunker
The $timeout does not generate error like „$digest already in progress“ because $timeout tells Angular that after the current cycle, there is a timeout waiting and this way it ensures that there will not any collisions between digest cycles and thus output of $timeout will execute on a new $digest cycle.
source
Edit 1: As the OP said below, the user of the directive should not have to write any "special" code in his callback function.
To achieve this behavior I changed the $timeout from de controller to the directive.
Controller callback function (without changes):
$scope.clickersCallback = function() {
$scope.clickersValueRightAfterCall = $scope.clickersValue;
console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
};
Directive code (inject $timeout in the directive):
$scope.clicked = function() {
console.log("you clicked me.");
$scope.newValue = 'VALID';
$timeout(function() {
$scope.myUpdate();
});
}
Updated plunker

angularjs - waiting for a value to be bound to a directive

I am experiencing a strange situation with angularjs. I have a value bound to a directive, and I need to be able to check on and manipulate that value from both the controller and a directive. I also have a method as a property of an object bound to the directive that I need to call from the controller. The method is expected to react accordingly to the bound value.
Here is some pseudo code to illustrate it:
.controller('ctrl', function(){
$scope.someAction = function(){
$scope.myValue = undefined;
$scope.someObject.myMethod();
};
});
.directive('myDirective', ...){
return {
...
scope: { myValue: '=', someObject: '=' },
link: function (scope) {
scope.someObject = {
myMethod: function(){
if (angular.isDefined(scope.myValue)){
// do something
}
else {
// do something else
}
}
};
}
}
}
and in controller template:
<my-directive my-value="myValue" some-object="someObject"></my-directive>
I would expect that when "someAction" is triggered, "myValue" set to undefined and the method "someObject.myMethod" is called from the controller, "myValue" in the directive is undefined, but it isn't that way. However, if I wrap the method call in a $timeout that waits for just 1 millisecond, I get the expected behaviour:
.controller('ctrl', function($timeout){
$scope.someAction = function(){
$scope.myValue = undefined;
$timeout(function() {
$scope.someObject.myMethod();
}, 1);
};
});
This hack has solved my problem, but I would prefer to understand what is going on and perhaps solve it (or avoid it) more elegantly...
Your controller's function $scope.someAction is wrapped by angular into an $apply call. So, probably, when you call $scope.someObject.myMethod(), the directive's isolated scope isn't updated because $apply didn't end. A solution would be to do something like you suggested:
$timeout(function() {
$scope.someObject.myMethod();
}, 0);
The timeout will delay to the execution of function to the next $apply call so your directive will see the updated value.

AngularJS: Parent scope is not updated in directive (with isolated scope) two way binding

I have a directive with isolated scope with a value with two way binding to the parent scope. I am calling a method that changes the value in the parent scope, but the change is not applied in my directive.(two way binding is not triggered). This question is very similar:
AngularJS: Parent scope not updated in directive (with isolated scope) two way binding
but I am not changing the value from the directive, but changing it only in the parent scope. I read the solution and in point five it is said:
The watch() created by the isolated scope checks whether it's value for the bi-directional binding is in sync with the parent's value. If it isn't the parent's value is copied to the isolated scope.
Which means that when my parent value is changed to 2, a watch is triggered. It checks whether parent value and directive value are the same - and if not it copies to directive value. Ok but my directive value is still 1 ... What am I missing ?
html :
<div data-ng-app="testApp">
<div data-ng-controller="testCtrl">
<strong>{{myValue}}</strong>
<span data-test-directive data-parent-item="myValue"
data-parent-update="update()"></span>
</div>
</div>
js:
var testApp = angular.module('testApp', []);
testApp.directive('testDirective', function ($timeout) {
return {
scope: {
key: '=parentItem',
parentUpdate: '&'
},
replace: true,
template:
'<button data-ng-click="lock()">Lock</button>' +
'</div>',
controller: function ($scope, $element, $attrs) {
$scope.lock = function () {
console.log('directive :', $scope.key);
$scope.parentUpdate();
//$timeout($scope.parentUpdate); // would work.
// expecting the value to be 2, but it is 1
console.log('directive :', $scope.key);
};
}
};
});
testApp.controller('testCtrl', function ($scope) {
$scope.myValue = '1';
$scope.update = function () {
// Expecting local variable k, or $scope.pkey to have been
// updated by calls in the directive's scope.
console.log('CTRL:', $scope.myValue);
$scope.myValue = "2";
console.log('CTRL:', $scope.myValue);
};
});
Fiddle
Use $scope.$apply() after changing the $scope.myValue in your controller like:
testApp.controller('testCtrl', function ($scope) {
$scope.myValue = '1';
$scope.update = function () {
// Expecting local variable k, or $scope.pkey to have been
// updated by calls in the directive's scope.
console.log('CTRL:', $scope.myValue);
$scope.myValue = "2";
$scope.$apply();
console.log('CTRL:', $scope.myValue);
};
});
The answer Use $scope.$apply() is completely incorrect.
The only way that I have seen to update the scope in your directive is like this:
angular.module('app')
.directive('navbar', function () {
return {
templateUrl: '../../views/navbar.html',
replace: 'true',
restrict: 'E',
scope: {
email: '='
},
link: function (scope, elem, attrs) {
scope.$on('userLoggedIn', function (event, args) {
scope.email = args.email;
});
scope.$on('userLoggedOut', function (event) {
scope.email = false;
console.log(newValue);
});
}
}
});
and emitting your events in the controller like this:
$rootScope.$broadcast('userLoggedIn', user);
This feels like such a hack I hope the angular gurus can see this post and provide a better answer, but as it is the accepted answer does not even work and just gives the error $digest already in progress
Using $apply() like the accepted answer can cause all sorts of bugs and potential performance hits as well. Settings up broadcasts and whatnot is a lot of work for this. I found the simple workaround just to use the standard timeout to trigger the event in the next cycle (which will be immediately because of the timeout). Surround the parentUpdate() call like so:
$timeout(function() {
$scope.parentUpdate();
});
Works perfectly for me. (note: 0ms is the default timeout time when not specified)
One thing most people forget is that you can't just declare an isolated scope with the object notation and expect parent scope properties to be bound. These bindings only work if attributes have been declared through which the binding 'magic' works. See for more information:
https://umur.io/angularjs-directives-using-isolated-scope-with-attributes/
Instead of using $scope.$apply(), try using $scope.$applyAsync();

Angular - receiving info from controller inside directive

I have a directive which is responsible to render audio and video items on a page:
myApp.directive('videoitem', function($compile){
return {
replace: true,
templateUrl: '/templates/directives/videoitem.html',
scope: {
videoChunkItem: "=",
videostartedCallback: "&videostarted",
videoendedCallback: "&videoended",
},
link: function($scope, $element, $attributes){
var startVideo = function(){
$element[0].load();
$element[0].play();
$scope.videostartedCallback();
};
$element.bind("ended", function(){
$scope.videoendedCallback();
$scope.$apply();
});
/**
* Here I am using on click event to start the video
* but the real case is that the trigger for video playing should come from controller.
* Any idea how to do it?
*/
$element.on('click', function(){
startVideo();
});
}
};
});
Is it possible from a route controller to send some event or something else to communicate from controller to directive to call startVideo method?
The flow of communication is from route controller to directive ... When in a controller occure some event I want to invoke directive's startVideo method.
Without knowing your exact use-case, my approach would be to define a new scope property, e.g. play : "=" (to set it to stop when the video ends), and $observe attrs.play or $scope.$watch "play" it. In your template you bind it to a $scope variable that is triggered by the controller.
<video-item play="videoControls.play"></video-item>
Here's a plunker where you use two-way binding, note how you don't have to do anything:
http://plnkr.co/edit/3OI801KcvYVoySDvcm8i?p=preview
If you want to allow expressions, you have to observe the attribute to get the interpolated value:
http://plnkr.co/edit/gi6FSPPlN599CtDjKBOb?p=preview

Sharing scope between controller & directive in AngularJS

I've created a directive to wrap a jQuery plugin, and I pass a config object for the plugin from the controller to the directive. (works)
In the config object is a callback that I want to call on an event. (works)
In the callback, I want to modify a property on the controller's $scope, which does not work. Angular does not recognize that the property has changed for some reason, which leads me to believe that the $scope in the callback is different than the controller's $scope. My problem is I just don't why.
Can anybody point me in the right direction?
Click here for Fiddle
app.js
var app = angular.module('app', [])
.directive('datepicker', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
// Uncommenting the line below causes
// the "date changed!" text to appear,
// as I expect it would.
// scope.dateChanged = true;
var dateInput = angular.element('.datepicker')
dateInput.datepicker(scope.datepickerOpts);
// The datepicker fires a changeDate event
// when a date is chosen. I want to execute the
// callback defined in a controller.
// ---
// PROBLEM:
// Angular does not recognize that $scope.dateChanged
// is changed in the callback. The view does not update.
dateInput.bind('changeDate', scope.onDateChange);
}
};
});
var myModule = angular.module('myModule', ['app'])
.controller('MyCtrl', ['$scope', function ($scope) {
$scope.dateChanged = false;
$scope.datepickerOpts = {
autoclose: true,
format: 'mm-dd-yyyy'
};
$scope.onDateChange = function () {
alert('onDateChange called!');
// ------------------
// PROBLEM AREA:
// This doesnt cause the "date changed!" text to show.
// ------------------
$scope.dateChanged = true;
setTimeout(function () {
$scope.dateChanged = false;
}, 5000);
};
}]);
html
<div ng-controller="MyCtrl">
<p ng-show="dateChanged">date changed!</p>
<input type="text" value="02-16-2012" class="datepicker" datepicker="">
</div>
There are a number of scope issues at work in your demo. First , within the dateChange callback, even though the function itself is declared inside the controller, the context of this within the callback is the bootstrap element since it is within a bootstrap handler.
Whenever you change angular scope values from within third party code , angular needs to know about it by using $apply. Generally best to keep all third party scopes inside the directive.
A more angular apprroach is to use ng-model on the input. Then use $.watch for changes to the model. This helps keep all the code inside the controller within angular context. Is rare in any angular application not to use ng-model on any form controls
<input type="text" class="datepicker" datepicker="" ng-model="myDate">
Within directive:
dateInput.bind('changeDate',function(){
scope.$apply(function(){
scope[attrs.ngModel] = element.val()
});
});
Then in Controller:
$scope.$watch('myDate',function(oldVal,newVal){
if(oldVal !=newVal){
/* since this code is in angular context will work for the hide/show now*/
$scope.dateChanged=true;
$timeout(function(){
$scope.dateChanged=false;
},5000);
}
});
Demo: http://jsfiddle.net/qxjck/10/
EDIT One more item that should change is remove var dateInput = angular.element('.datepicker') if you want to use this directive on more than one element in page. It is redundant being used in directive where element is one of the arguments in the link callback already, and is instance specific. Replace dateInput with element
The changeDate event bound to the input seems to be set up to fire outside of the Angular framework. To show the paragraph, call $scope.$apply() after setting dateChanged to true. To hide the paragraph after the delay, you can use $apply() again inside the function passed to setTimeout, but you're likely to keep out of further trouble using Angular's $timeout() instead.
Fiddle

Resources