59

I'm trying to making some custom elements with AngularJS's and bind some events to it, then I notice $scope.var won't update UI when used in a binding function.

Here is a simplified example that describing the probelm:

HTML:

<!doctype html>
<html ng-app="test">
  <head>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
    <script src="script.js"></script>

  </head>
  <body>

<div ng-controller="Ctrl2">
  <span>{{result}}</span>
  <br />
  <button ng-click="a()">A</button>
  <button my-button>B</button>

</div>


  </body>
</html>

JS:

function Ctrl2($scope) {
    $scope.result = 'Click Button to change this string';
    $scope.a = function (e) {
        $scope.result = 'A';
    }
    $scope.b = function (e) {
        $scope.result = 'B';
    }
}

var mod = angular.module('test', []);

mod.directive('myButton', function () {
    return function (scope, element, attrs) {
        //change scope.result from here works
        //But not in bind functions
        //scope.result = 'B';
        element.bind('click', scope.b);
    }
});

DEMO : http://plnkr.co/edit/g3S56xez6Q90mjbFogkL?p=preview

Basicly, I bind click event to my-button and want to change $scope.result when user clicked button B (similar to ng-click:a() on button A). But the view won't update to the new $scope.result if I do this way.

What did I do wrong? Thanks.

Mark Ni
  • 2,383
  • 1
  • 27
  • 33

3 Answers3

176

Event handlers are called "outside" Angular, so although your $scope properties will be updated, the view will not update because Angular doesn't know about these changes.

Call $scope.$apply() at the bottom of your event handler. This will cause a digest cycle to run, and Angular will notice the changes you made to the $scope (because of the $watches that Angular set up due to using {{ ... }} in your HTML) and update the view.

Brian Cline
  • 20,012
  • 6
  • 26
  • 25
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • 2
    @ogc-nick, if you are already in a digest cycle, you shouldn't have to call `$apply()`. If your view is not updating, you have something different going on from the OP example. – Mark Rajcok Jan 03 '14 at 20:15
  • 3
    I get $digest already in progress, and my view only responds seconds after updating my scope. https://docs.angularjs.org/error/$rootScope/inprog?p0=$digest – Jorre Nov 14 '14 at 19:47
  • @Jorre, sounds like you might have too many bindings: see http://stackoverflow.com/questions/15183095/angularjs-really-slow-at-rendering-with-about-2000-elements – Mark Rajcok Nov 14 '14 at 20:30
  • For those still wondering, you can resolve the "$apply already in progress" issue using Angular's $timeout() function https://stackoverflow.com/questions/18996042 – Michael Zalla Jul 20 '15 at 04:49
  • @MarkRajcok Abit off but related to scope — what is that `top scope` that your [app](http://i.imgur.com/2nn57Qa.png) draw? (Is there a father of rootscope?) – Royi Namir Dec 09 '15 at 13:52
  • @RoyiNamir, yes, `$rootScope` inherits from a `Scope` object. – Mark Rajcok Dec 09 '15 at 15:30
  • @MarkRajcok (Can I access that top most scope? ) If that's so , then this [question](http://stackoverflow.com/questions/13639854/is-rootscope-the-parent-of-the-topmost-scope) should get a new answer – Royi Namir Dec 09 '15 at 15:32
  • @RoyiNamir, there is no instance of the `Scope` object, so no you can't access it. Really, `Scope` is a constructor function that `$rootScope` references in its `__proto__` property. That's why I show it in my diagrams. See http://jonathancreamer.com/adding-clarity-to-scope-inheritance-in-angular/ for more info. – Mark Rajcok Dec 09 '15 at 19:17
  • I am working directive and it's have scope and ctrl function also defined but when i update any value in view that time scope variable value had been updating but it will not change in view. i also try with $scope.$apply() that's throwing infdig error. any other solution you have please help me.... – VjyV Apr 25 '17 at 12:32
  • Could anyone see this link. I am stuck like same as....https://stackoverflow.com/questions/46299999/datatable-is-not-generating-after-call-function-in-angular-js/46301296#46301296 – Varun Sharma Sep 19 '17 at 13:39
4

This might be also a result of different problem but with the same symptoms.

If you destroy a parent scope of the one that is assigned to the view, its changes will not affect the view in any way even after $apply() call. See the example - you can change the view value through the text input, but when you click Destroy parent scope!, model is not updated anymore.

I do not consider this as a bug. It is rather result of too hacky code in application :-)

I faced this problem when using Angular Bootstrap's modal. I tried to open second modal with scope of the first one. Then, I immediately closed the first modal which caused the parent scope to be destroyed.

fracz
  • 20,536
  • 18
  • 103
  • 149
  • 1
    I had a issue in IE9 where I was using `ng-if` which destroys the scope. So when i was selecting an item from a list of items and populating properties depending on the type of item, I was ensuring that there wasn't any redundant scopes for properties that didn't exist for the selected item type. But in IE 9 the destruction of the scope meant that I was seeing odd behaviour, with the view references `{{}}` not being rendered and dropdowns only showing the first letter of the text. I replaced `ng-if`with `ng-show` and it all worked. This answer lead me to the discovery so thanks – Corporalis Sep 16 '16 at 09:19
  • 1
    Yeah ng-show instead of ng-if also fixed my problem – Anonymous Dodo Jul 21 '17 at 13:50
2

use timeout

$timeout(function () { code.... }, 0);

Muhammad Bilal
  • 359
  • 3
  • 5