Accessing scope from within element.bind, within link function of directive - angularjs

I am experiencing some problems within the link function of my directive. I am starting a new timeout within mousedown event bound to the element, then clearing it on the mouseup. The timeout is not clearing and also other variables I call on the scope are not updating within the element.bind functions. When I log to console, both functions are being triggered but the $scope doesn't seem to update until after the timeout has completed?
How can I make this work? JS fiddle here: http://jsfiddle.net/xrh6dhf9/
HTML
<div ng-app="dr" ng-controller="testCtrl">
<holddelete param="myDeletedMessage" update-fn="doCallback(msg)"></test>
JavaScript
var app = angular.module('dr', []);
app.controller("testCtrl", function($scope) {
$scope.myDeletedMessage = "Deleted Succesfully";
$scope.doCallback = function(msg) {
alert(msg);
}
});
app.directive('holddelete', ['$timeout', function( $timeout) {
return {
restrict: 'E',
scope: {
param: '=',
updateFn: '&'
},
template: "<a href> <i class='fa fa-times fa-fw'></i>Delete {{message}}</a>",
replace: true,
link: function($scope, element, attrs) {
$scope.mytimeout = null;
$scope.message = ">";
element.bind('mousedown', function (e) {
console.log("mousedown");
$scope.message = "- Hold 2 Secs";
$scope.mytimeout = $timeout(function(){
$scope.updateFn({msg: $scope.param});
}, 2000)
});
element.bind('mouseup', function (e) {
console.log("mouseup");
$scope.mytimeout = null;
$scope.message = ">";
})
}
}
}]);

Instead of setting timeout as null use
$timeout.cancel($scope.mytimeout);
Also instead of setting event handlers using element.bind pass execute methods in scope with ng-mousedown and ng-mouseup
Fiddle: http://jsfiddle.net/xrh6dhf9/1/

Related

Avoid using $timeout in Angular UI Modal

In this plunk I have an Angular UI Modal wrapped in a directive. From the controller, I call a method to open the modal, but to do so I need to use $timeout, otherwise, the DOM hasn't finished rendering the directive.
This seems to work, however, what would happen if whatever needs to be completed hasn't finished after the $timeout expires? The $timeout may work in a development environment but may fail in production. Is it a bad practice to use $timeout? How to avoid using it in this example?
HTML
<div modal control="modalCtl"></div>
Javascript
var app = angular.module('app', ['ui.bootstrap']);
app.controller('myCtl', function($scope,$timeout) {
$scope.modalCtl = {};
$timeout(function(){
$scope.modalCtl.openModal();
},100);
})
.directive('modal', function ($uibModal) {
var directive = {};
directive.restrict = 'EA';
directive.scope = {
control: '='
};
directive.link = function (scope, element, attrs) {
scope.control = scope.control || {};
scope.control.openModal = function() {
scope.modalInstance = $uibModal.open({
template: '<button ng-click="close()">Close</button>',
scope: scope
})
};
scope.close = function () {
scope.modalInstance.close();
};
};
return directive;
});
To avoid using $timeout the directive can notify controller when everything is ready. Take a look:
.directive('modal', function ($uibModal) {
var directive = {};
directive.restrict = 'EA';
directive.scope = {
control: '=',
onReady: '&' // <-- bind `onReady` with `onModalReady`
};
directive.link = function (scope, element, attrs) {
scope.control = scope.control || {};
scope.control.openModal = function() {
scope.modalInstance = $uibModal.open({
template: '<button ng-click="close()">Close</button>',
scope: scope
})
};
scope.close = function () {
scope.modalInstance.close();
};
scope.onReady(); // <-- notify controller
};
return directive;
});
Out HTML:
<div modal on-ready="onModalReady()" control="modalCtl"></div>
Our controller:
$scope.onModalReady = function(){
$scope.modalCtl.openModal();
}
Changed Plunker
About comment #Eduardo La Hoz Miranda
you should be ok with the timeout. I would decrease the time to 0, tho, since timeout will send your call to the bottom of the event loop.
Generally when we initialize $timeout with 0 milliseconds or with no argument as:
$timeout(function(){
$scope.modalCtl.openModal();
});
We delay $scope.modalCtl.openModal() to run before next digest cycle a.e. last in queue. So in this case directive link will run 1st from beginning to to the end and only after you will enter to $timeout.
The $timeout may work in a development environment but may fail in production.
On Production you have the same code. It should work. I believe the problem is in something else. If you are not confident with $timeout use above mentioned way I posted.
Your Logged Plunker
When link function of directive is finished, it can emit a message that it's ready.
And controller listens to this message and displays modal when received.
Code:
var app = angular.module('app', ['ui.bootstrap']);
app.controller('myCtl', function($scope,$timeout) {
$scope.modalCtl = {};
$scope.$on("hey", function() {
$scope.modalCtl.openModal();
});
})
.directive('modal', function ($uibModal) {
var directive = {};
directive.restrict = 'EA';
directive.scope = {
control: '='
};
directive.link = function (scope, element, attrs) {
scope.control = scope.control || {};
scope.control.openModal = function() {
scope.modalInstance = $uibModal.open({
template: '<button ng-click="close()">Close</button>',
scope: scope
})
};
scope.close = function () {
scope.modalInstance.close();
};
scope.$emit("hey");
};
return directive;
});
Using timeout for any arbitrary waits on code executing is generally bad. As you state in your question, depending on the overall context of the page you are loading, you have no guarantee that the directive will be ready at the time your controller runs.
It seems like you have too many levels of abstraction here. Something is rendering a div that when it is fully rendered, shows a modal.
Wouldn't it make more sense to just have the thing that is rendering the div create and show the modal instead ?

Angular $watch not working on controller variable updated by directive

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);
});
};

Variable in directive's view is not always updated

When I call notificationService.showNotification("Test from controller") from a controller the message in the directive's view is updated but when I then call notificationService.showNotification("Test from another service") from another service the message in the directive's view doesn't get updated.
How should I do to always update currentNotification.message in the directive's view when currentNotification is updated in the notificationService?
Update:
The problem seems to occur when calling notificationService.showNotification("Test from another service") from a setTimeout function. See plunker: http://plnkr.co/edit/qD6OjamFjzOFbg43vj7R
Clicking on "Update from Other Service needs to be done twice for the message to update.
angular.module('myApp').service('notificationService',function() {
var notificationService = {
currentNotification : {
message: "",
},
showNotification : function(text){
this.currentNotification.message = text;
},
};
return notificationService;
});
angular.module('myApp').directive('notificationDirective', function(notificationService) {
return {
restrict: 'E',
replace: true,
scope: {
},
templateUrl: 'directive/notification-directive/notification-directive.html',
link: function(scope, element, attrs, fn) {
scope.currentNotification = notificationService.currentNotification;
}
};
});
directive/notification-directive/notification-directive.html:
<div>
<div ng-if="currentNotification.message.length > 0" id="notificationWrapper">
<div class="notification">
<div class="message">
{{currentNotification.message}}
</div>
</div>
</div>
</div>
You need to use $timeout which calls $scope.$apply, or you need to call $scope.$apply on your own :
myApp.controller('otherCtrl', function($scope, $timeout, notificationService, otherService) {
$scope.updateFromService = function() {
console.log("updateFromService");
$timeout(function(){
otherService.updateNotification();
}, 200);
};
});

AngularJS directive not being updated with parent scope variable changes

I have a directive and a controller in my AngularJS app as shown below, where I need the directive to be updated with the controller scope variable changes.
Problem I am facing is that any change to the controller scope variable do not update the directive. I've tried using {scope: false}, tried making an isolated scope and one-way binding with the controller scope variable as shown below but none worked, so can someone please check my code and let me know what I am missing / doing wrong here? Thanks
First trial using isolated scope in directive
.directive('loginPanelDir', function() {
return {
restrict: 'A',
scope: {
loginStatus: "&userLoginStatus"
},
link: function(scope, element, attrs) {
console.log(scope.loginStatus()); //will always print 0 despite of changes to the scope var in controller
}
};
});
.controller('LoginController', function ($scope, $location) {
$scope.LoginStatus = "0";
$scope.clickMe = function(){
$scope.LoginStatus = "1";
};
});
<div id="login" login-panel-dir user-login-status="LoginStatus">
<button id="btnLogin" type="submit" ng-click="clickMe()">Login</button>
Second trial using {scope:false} in directive
.directive('loginPanelDir', function() {
return {
restrict: 'A',
scope: false,
link: function(scope, element, attrs) {
console.log(scope.LoginStatus()); //will always print 0 despite of changes to the scope var in controller
scope.$watch(function(){ scope.LoginStatus }, function(){
console.log('Login status : '+scope.LoginStatus); //will always return 0...
});
}
};
});
.controller('LoginController', function ($scope, $location) {
$scope.LoginStatus = "0";
$scope.clickMe = function(){
$scope.LoginStatus = "1";
};
});
<div id="login" login-panel-dir>
<button id="btnLogin" type="submit" ng-click="clickMe()">Login</button>
You don't have to use $timeouts or $intervals to watch changes for certain scope values. Inside your directive you can watch for the changes of your login status via watching the user-login-status attribute.
DEMO
Something like this:
JAVASCRIPT
.controller('LoginController', function($scope) {
$scope.LoginStatus = "0";
$scope.clickMe = function(){
$scope.LoginStatus = "1";
};
})
.directive('loginPanelDir', function() {
return function(scope, elem, attr) {
scope.$watch(attr.userLoginStatus, function(value) {
console.log(value);
});
}
});
HTML
<div id="login" login-panel-dir user-login-status="LoginStatus">
<button id="btnLogin" type="submit" ng-click="clickMe()">Login</button>
</div>
Working plunk.
Use $timeout not setTimeout:
setTimeout(function(){
$scope.LoginStatus = "1";
}, 3000);
should be:
$timeout(function(){
$scope.LoginStatus = "1";
}, 3000);
setTimeout is native Javascript, so it will not run a digest and angular won't be notified of the changes hence no updates to the bindings, $timeout runs a digest when it completes, forcing an update to the binding.
Well its working here, just realized your $watch is also wrong:
it should be:
scope.$watch("LoginStatus", function(){
console.log('Login status : '+scope.LoginStatus); //will always return 0...
});

Angular - function inside directive scope gets called when it shouldn't

Hi I am struggling with the following:
http://jsfiddle.net/uqZrB/9/
HTML
<div ng-controller="MyController">
<p>Button Clicked {{ClickCount}} Times </p>
<my-clicker on-click="ButtonClicked($event)">
</my-clicker>
</div>
JS
var MyApp = angular.module('MyApp',[]);
MyApp.directive('myClicker', function() {
return {
restrict: 'E',
scope: {
onClick: "="
},
link: function($scope, element, attrs) {
var button = angular.element("<button>Click Me</button>");
button.bind("mousedown", $scope.onClick);
element.append(button);
}
};
});
MyApp.controller("MyController", function ($scope) {
$scope.ButtonClicked = function($event) {
$scope.ClickCount++;
};
$scope.ClickCount = 0;
});
(using angular1.2 rc : https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.js)
The custom directive "myClicker" should insert a button into the tag, and bind its mousedown event to a function supplied in the directive scope...
I.e. i can pass the a function from the controller, to execute when the button is clicked.
As you can see, when you run the fiddle, the bound event gets run 11 times, on load.... i.e. before the button has event been clicked.
Running its 11 times causes the "10 $digest() iterations reached. Aborting!" error.
Then, when I click the button I get "Cannot call method 'call' of undefined", as if the method was not declared in the scope.
Why does angular try and run the method on loading?
Why is the "onClick" method not available in the scope?
I think I am misunderstanding something about the directive's isolated scope.
Thanks in advance!
The onClick: "=" in your scope definition expects a two-way data-binding, use onClick: "&" to bind executable expressions into an isolate scope. http://docs.angularjs.org/guide/directive
please changes your code in controller as below
var MyApp = angular.module('MyApp',[]);
MyApp.directive('myClicker', function() {
return {
restrict: 'E',
scope: {
onClick: "&"
},
link: function($scope, element, attrs) {
var button = angular.element("<button>Click Me</button>");
button.bind("mousedown", $scope.onClick);
element.append(button);
}
};
});
MyApp.controller("MyController", function ($scope) {
$scope.ButtonClicked = function($event) {
$scope.ClickCount++;
};
$scope.ClickCount = 0;
});

Resources