Angular does not update bound property - angularjs

Fiddle
HTML:
<div ng-controller="MyCtrl">
<button my-event-directive>Click me</button>
<div>{{secret}}</div>
</div>
JS:
var myApp = angular.module('myApp',[]);
myApp.directive('myEventDirective', function() {
return {
link: function(scope, element) {
element.on('click', function(event){
scope.$emit('myEvent', {secret: 'aaa'});
});
}
}
})
function MyCtrl($scope) {
$scope.secret = 'bbb';
$scope.$on('myEvent', function(event, data){
alert('event received!');
$scope.secret = data.secret;
});
}
After I click the button, the event is received in the controller (alert shows up). However, the {{secret}} binding does not update its value. Why?
My event creation is more sophisticated in real code, of course.

As #Cherinv replied in a comment, when changing a scope attributes outsite the Angular $apply method, you have to call it manually. #runTarm also suggested that the event dispatcher should use the $apply because listeners are freed from remember it then. So:
scope.$emit('myEvent', {secret: 'aaa'});
should be changed to:
scope.$apply(function() {
scope.$emit('myEvent', {secret: 'aaa'});
});
$apply method is described in details in the following article: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html

USE $scope.$apply(). NOW the change will be noticed, and the page is updated.
var myApp = angular.module('myApp',[]);
myApp.directive('myEventDirective', function() {
return {
link: function(scope, element) {
element.on('click', function(event){
scope.$emit('myEvent', {secret: 'aaa'});
});
}
}
})
function MyCtrl($scope) {
$scope.secret = 'bbb';
$scope.$on('myEvent', function(event, data){
alert('event received! secret is ' + data.secret);
$scope.$apply(function () {
$scope.secret = data.secret;
});
});
}

You could try changing binding to happen on object.value rather than value. Maybe it's the case when angular can not trace immutable property change.
<div ng-controller="MyCtrl">
<button my-event-directive>Click me</button>
<div>{{data.secret}}</div>
</div>
var myApp = angular.module('myApp',[]);
myApp.directive('myEventDirective', function() {
return {
link: function(scope, element) {
element.on('click', function(event){
scope.$emit('myEvent', {secret: 'aaa'});
});
}
}
})
function MyCtrl($scope) {
$scope.data = {
secret: 'bbb'
};
$scope.$on('myEvent', function(event, data){
alert('event received!');
$scope.data.secret = data.secret;
});
}
This should work.
P.S. since you always see alert being called that means that you don't need to call scope.$apply to invoke scope digest and the value IS assigned, the problem is that angular can not watch on immutable values (probably)

Related

Update href in AngularJS before navigating to the URL

In an AngularJS application I have the following code:
<a target="_blank" ng-href="{{someProperty.href}}" ng-click="someMethod($event)">Hello!</a>
Now, someMethod() and someProperty belong to the same service.
Initially, someProperty.href has a default value.
What I need to do is that when the user clicks on the link, some calculation is performed and someProperty.href gets a new value. This new value need to be reflected in the ng-href and the user should be redirected to that new href.
tried reconstructing it and it seems to work, clicking on the link opens a new tab with the new url.
https://plnkr.co/edit/gy4eIKn02uF0S8dLGNx2?p=preview
<a target="_blank" ng-href="{{someService.someProperty.href}}" ng-click="someService.someMethod()">
Hello!
<br/>
{{someService.someProperty.href}}
</a>
You can do it as like the below code
;(function(angular) {
angular.module('myApp.directives')
.directive('myExample', myExample);
myExample.$inject = ['$timeout'];
function myExample($timeout) {
return {
restrict: 'A',
scope: {
myExample: '&',
ngHref: '='
},
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.preventDefault();
$timeout(function() {
scope.myExample();
scope.$apply();
var target = attrs.target || '_blank';
var url = scope.ngHref;
angular.element('')[0].click();
});
});
}
};
}
})(angular);
In Controller
;(function(angular) {
'use strict';
angular.module('myApp.controllers').controller('HomeController', HomeController);
HomeController.$inject = ['$scope'];
function HomeController($scope) {
$scope.url = 'http://yahoo.com';
$scope.someFunction = function() {
$scope.url = 'http://google.com';
};
}
})(angular);
In HTML You can use like
<div ng-controller="HomeController">
<a ng-href="url" my-example="someFunction()" target="_blank">Click me to redirect</a>
</div>
Here instead of ng-click I have used custom directive which simulates the ng-click but not as exactly as ng-click
If the parent scope function is async you change your directive and someFunction in controller as like below
#Directive
;(function(angular) {
angular.module('myApp.directives')
.directive('myExample', myExample);
myExample.$inject = ['$timeout'];
function myExample($timeout) {
return {
restrict: 'A',
scope: {
myExample: '&',
ngHref: '='
},
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.preventDefault();
scope.myExample().then(function() {
$timeout(function() {
scope.$apply();
var target = attrs.target || '_blank';
var url = scope.ngHref;
angular.element('')[0].click();
});
});
});
}
};
}
})(angular);
#Controller
;(function(angular) {
'use strict';
angular.module('myApp.controllers').controller('HomeController', HomeController);
HomeController.$inject = ['$scope', '$q'];
function HomeController($scope, $q) {
$scope.url = 'http://yahoo.com';
$scope.someFunction = function() {
var deferred = $q.defer();
$scope.url = 'http://google.com';
deferred.resolve('');
return deferred.promise;
};
}
})(angular);
Here I just simulated the async, it may be your http call too
make sure the value in href tag is updated after you click on it. Try debugging the ng-click function. According to the official documentation:
Using AngularJS markup like {{hash}} in an href attribute will make the link go to the wrong URL if the user clicks it before AngularJS has a chance to replace the {{hash}} markup with its value. Until AngularJS replaces the markup the link will be broken and will most likely return a 404 error. The ngHref directive solves this problem.
In your case, i think the old link is not getting updated with the new values of the model. Hence, redirecting to old link.
Try calling a function on ui-sref or ng-href which will return the state name that you want to redirect. Something like this
html:
<a ui-href="{{GetUpdatedHref()}}" >Hello!</a>
controller.js
$scope.GetUpdatedHref = function(){
//perform your http call while it's being processed show a loader then finally return the desired state (page location)
return "main.home"
}
If this doesn't work for you use ng-click instead of ui-sref and then $state.go("main.home") inside function.
Hope this may resolve your problem.

How can I access $event inside a directive?

I can't seem to pass it as an attribute!
I can add an ng-click="function($event)" and pass $event that way to a controller function, but I would like to access it inside a directive.
The ultimate purpose is to stopPropagation() and/or preventDefault() of an element inside a directive instead of in a controller.
EDIT: I will post code accordingly
<div ng-app="myApp" ng-controller="MyCtrl">
<button stop-toggle ng-click="testFunction($event)">
click me
</button>
Hello, {{name}}!
</div>
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Superhero';
$scope.testFunction = function($event){
console.log("you can access the event here:", $event);
$event.stopPropagation();
}
}
myApp.directive('stopToggle', function(){
return {
link: function(scope, elem, attrs){
elem.bind('click', function(){
console.log("how do i access the $event here??");
// elem.stopPropagation(); // invalid function
// elem.preventDefault(); // invalid function
})
}
}
});
http://jsfiddle.net/Lvc0u55v/10656/
var myApp = angular.module('myApp',[]);
myApp.directive('stopToggle', function(){
return {
link: function(scope, elem, attrs){
elem.on('click', function(e){
e.stopPropagation();
e.preventDefault();
})
}
}
});
Just pass event as an argument to the function inside .on()

service only works after `$rootScope.$appy()` applied

I am loading the template from angular-service but that's not updating the template unless i use the $rootScope.$appy(). but my question is, doing this way this the correct approach to update the templates?
here is my code :
var app = angular.module('plunker', []);
app.service('modalService', function( $rootScope ) {
this.hide = function () {
this.show = false;
}
this.showIt = function () {
this.show = true;
}
this.setCategory = function ( category ) {
return this.showPath = category+'.html'
}
this.showCategory = function (category) {
this.setCategory( category )
$rootScope.$apply(); //is this correct?
}
})
app.controller('header', function($scope) {
$scope.view = "home view";
});
app.controller('home', function($scope, modalService) {
$scope.name = 'World';
$scope.service = modalService;
});
//header directive
app.directive('headerDir', function( modalService) {
return {
restrict : "E",
replace:true,
templateUrl:'header.html',
scope:{},
link : function (scope, element, attrs) {
element.on('click', '.edit', function () {
modalService.showIt();
modalService.showCategory('edit');
});
element.on('click', '.service', function () {
modalService.showIt();
modalService.showCategory('service');
})
}
}
});
app.directive('popUpDir', function () {
return {
replace:true,
restrict:"E",
templateUrl : "popup.html"
}
})
Any one please advice me if i am wrong here? or can any one show me the correct way to do this?
click on links on top to get appropriate template to load. and click on the background screen to close.
Live Demo
If you don't use Angular's error handling, and you know your changes shouldn't propagate to any other scopes (root, controllers or directives), and you need to optimize for performance, you could call $digest on specifically your controller's $scope. This way the dirty-checking doesn't propagate. Otherwise, if you don't want errors to be caught by Angular, but need the dirty-checking to propagate to other controllers/directives/rootScope, you can, instead of wrapping with $apply, just calling $rootScope.$apply() after you made your changes.
Refer this link also Angular - Websocket and $rootScope.apply()
Use ng-click for handling the click events.
Template:
<div ng-repeat="item in items">
<div ng-click="showEdit(item)">Edit</div>
<div ng-click="delete(item)">Edit</div>
</div>
Controller:
....
$scope.showEdit = function(item){
....
}
$scope.delete = function(item){
....
}
If you use jquery or any other external library and modify the $scope, angular has no way of knowing if something has changed. Instead if you use ng-click, you let angular track/detect change after you ng-click handler completes.
Also it is the angular way of doing it. Use jquery only if there is no other way to save the world.

angularjs: how to call a function without scope?

Here is my model
<html ng-app="myApp">
<button id="rainBtn" ng-click="makeItRain()">Make it rain ! </button>
<div class="tab-pane active" id="lobbyTab" ng-controller="chatController"></div>
And here is my myApp.js
var myApp = angular.module('myApp',[]);
makeItRain = function() {
alert("ok");
}
makeItRain is never called:
How to call the makeItRain() function ?
Make a Controller
var myApp = angular.module('myApp',[]);
myApp.controller("sky", ["$scope", function($scope) {
$scope.makeItRain = function() {
alert("ok");
}
}]);
Set the controller
<button id="rainBtn" ng-controller="sky" ng-click="makeItRain()">
You can't get a reference from a global function as an angular expression. One possible way to do this without using a controller is to use $rootScope and attach the function during the .run() phase, although it is not recommended:
myApp.run(function($rootScope) {
$rootScope.makeItRain = function() {
alert('Raining!');
};
});
Another way is to abandon the idea of attaching an event handler via ng-click directive and simply create a directive. This solution assumes that the event handler you are trying to attach will perform dom manipulations.
myApp.directive('makeItRain', function() {
return function(scope, elem, attr) {
elem.on('click', function() {
makeItRain();
});
};
});
and then attach it in the HTML:
<button id="rainBtn" make-it-rain>Make it rain ! </button>

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

Resources