angular-ui/ui-codemirror — pull request #54 by whitehat101 (2014-04-29 03:23:40+00:00)
Title: scope.$apply and $digest already in use

There are at least three open pull requests (#48 #46 #37) that attempt to deal with the dread `$digest already in use` error, and other issues mention it (#30).

* #48 is a decent solution, but mine is more complete
* #37 works, but it's a hack and ignores the real issue
* I hate to be mean, but #46 is just a naïve solution that shows a lack of understanding of how directives work.

My solution uses `$apply` correctly and doesn't attach the `codeMirror.on('change')` event when it doesn't need to be used. If ngModel isn't in use, then we have nothing to do with the `change` event.

From angular docs:
> `$apply([exp]);`
> `$apply()` is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life cycle of exception handling, executing watches.

`$apply` is meant to wrap an expression that may modify an angular scope, not to be called after an angular scope has been modified. Calling it after works, but then the code can't benefit from angular's exception handling.

`scope.$$phase` (and `scope.$root.$$phase`) are undocumented internal angular variables that angular uses in the scope life cycle. I think using them is hackish.

The problem is that when CM boots, it fires a change "callback" from within angular's turn. After that initial event, all change events come from outside angular. Simply checking `if(instance.getValue () !== ngModel.$viewValue)` lets us ignore that first change event. Since we already know that CM's value and ngModel's value are the same, there is no need to `$apply` any changes to ngModel.

I also moved the change event code inside the `if(ngModel)` block. If we're not implementing two-way data binding with a ngModel we have no use for the change event.