Why does this not work? View not updating when model changes

I know this has been asked before, but everyone posts the two workarounds without explaining why. And I love the responses that state “I suggest you read up on $scope.$apply()…”

Issue: View does not update when underlying model changes when an Ionic service event fires.

In this example $ionicPlatform.ready() is being used.

Resolution: Force the digest to update

  1. Use $scope.$apply which does not always work
  2. Use $timeout(function(){ }, 0) which has always worked for me

Question: why is this necessary.

When calling Angular services or those I create myself this is not required.

The only time I have needed to do this is from complex directives or when leveraging non-Angular JavaScript libraries; but the call has always been within the directive itself. Almost never from the controller. factory, etc.

Currently I view this has a rather significant defect with Ionic, yes there is a work around, but it should not be required.

Here is a Code Pen explaining what I am referring to.
Ionic: View not updating?

Un-comment the $timeout line and the view reflects changes to vm.notUpdating

Notice how in this case $timeout is not actually doing anytihng except forcing the digest to update.

Can someone please explain why this workaround is necessary?
If it is a defect will it be fixed?

I’ll start by explaining what $ionicPlatform.ready does.

This is a wrapper for Cordova’s device ready or if within a browser the window.load which is fired when the browser reports all the assets and the DOM have loaded and are ready to be used.

Here’s the explanation from Ionic’s documentation:

Trigger a callback once the device is ready, or immediately if the device is already ready. This method can be run from anywhere and does not need to be wrapped by any additonal methods. When the app is within a WebView (Cordova), it’ll fire the callback once the device is ready. If the app is within a web browser, it’ll fire the callback after window.load. Please remember that Cordova features (Camera, FileSystem, etc) still will not work in a web browser.

This event happens at the very beginning of the lifecycle of a web application including Cordova and Ionic. It’s purpose is to ensure the device is ready and all plugins have been loaded successfully.

It’s suggested to use this to wrap any plugin calls to make sure they aren’t called until they have been loaded successfully.

In your code pen:

var vm = this;
  vm.test = 'testController is active!';
  vm.notUpdating = "This text should change, but it doesn't. Uncomment the $timeout line and it will! Why???";
  
  
  $ionicPlatform.ready(function() {
    vm.notUpdating = 'Success!!!';
    //$timeout(function() {}, 0);
  }); 

The reason why $timeout (or as people keep asking you to use $scope.apply()) works is because this initialises the “digest” cycle which runs through each of the browser events and ensures all states have been reported to the application and also and most importantly it checks that all variables within scope have been applied, because you have wrapped vm.notUpdating in a $ionicPlatform.ready it will be executed last and will be missed by the digest cycle. $scope.apply ensures the changes are reflected correctly.

What I have noticed is you are using this and not $scope in your controllers, you should always use $scope if you are wanting to bind values to your views.

Hope that answers your question. It’s also not a defect, you aren’t using it for it’s intended purposes.

A little off topic tangent on the “controller as” syntax.

I have found Ionic to be a mixed bag of the good and the bad from Angular. The Angular best practice of “controller as” is fortunately not one of the things that I need to give up.

For me the 2 primary benefits of “controller as” over scope are:

  1. No more scope inheritance. Yay!!!
  2. View and controller syntax is now the same. {{ vm.myModel }} from the markup and vm.myModel from the controller (the enterprise architect in me, trying to keep junior and mid-level developers from making silly mistakes)

For more valid and technical reasons why you should stop using $scope consider:
Todd Motto has a good article: Digging into Angular’s “Controller as” syntax
John Papa explains vm=this: Controller As and the vm Variable
And it’s in John Papa’s best practice guide: Angular Style Guide

btw, $scope.$apply() does work in the pen I linked to. However it has not always worked for me and I will create a pen demonstrating this when I come across it again.


Back on topic.

From your response I am reading essentially that $ionicPlatform.ready() is not really “Angular” code. It wraps an external JavaScript library but does not “Angularize” it; as if it did we would not need to use $scope.$apply or similar.

Perhaps it is unfortunate that the Ionic team chose to prefix $ionicPlatform with the $; as this implies $ionicPlatform is something it is not.

Don’t take me wrong with that comment. All frameworks must be “opinionated” otherwise they would not be successful. As developers we select the frameworks whose opinions we most align with. Overall I am pleased with Ionic and this is one “opinion” it appears I will just have to live with.

Do you understand the purpose of .ready and why it’s not working as you intended?

Uh, yes. I will assume that I simply was not very clear.

Our app is written in Angular. Angular must know about all of the JavaScript events that get fired so that it can apply the underlying models to the views.

When JavaScript events fire outside of the management of Angular we must use $scope.$apply() to tell Angular that something has changed, update the views.

When consuming a 3rd party JavaScript library from Angular we wrap it in a directive/service so that we abstract all of this $scope.$apply away from the rest of our application code.

By prefixing $ionicPlatform with a $ the Ionic team is implying that this is a “Angular Service” and they have already taken care of all of this digest updating internally. But they did not. It’s up to us to take care of that ourselves.

My last comment was indicating that the $ prefix is perhaps an unfortunate reality, it implies underlying architecture that is not there.

OK, so what I think you are doing is interpreting the $ to be Angular, therefore expecting it to run the digest as well as transclude the code within the function call.

What it is, is a provider which wraps Cordova.ready and handles window.load events so that you can inject it into your application rather than include it as a global… It’s NOT suppose to handle what you expect it to by interpreting the $ as part of the Angular event cycle.

You’ve pretty much answered your own question so why bother asking it?

If you have a problem with the underlying approach why not create a PR with an explanation, if it’s agreed upon it might get approved and made part of the implementation.

However I think it’s expected to have to apply scope manually in the situation you’ve described.

I’m really glad that I found this thread as it was driving me a bit nutzo…

@delta98 I tend to agree with @treygood that it’s confusing for $ionicPlatform not to integrate with angular and trigger a $scope.apply (or whatever the appropriate terminology is called). If it doesn’t provide anything valuable on top of the ionic.Platform.ready function, then why give it the angular-standard “$” prefix?

Because it’s being injected into Angular as a provider, this where I feel @treygood is misunderstanding the $ it doesn’t mean it SHOULD be part of the angular life cycle and part of digest. It’s because a global has been wrapped into a service that angular can inject into your controllers, services and directives.

Thank you very much for a good post and it worked for me … I struggled around 4 to 5 hours