Wednesday, November 12, 2014

Upgrading to Angular 1.3: Global Controllers Not Supported by Default

If you recently upgraded to Angular 1.3.x, you my get the following JavaScript error when trying to run your application:

Error: [ng:areq] Argument 'MyController' is not a function, got undefined

This issue is a result of some breaking changes where AngularJS no longer supports global controllers set on the window object as of version 1.3. In reality if you have a production application using global controllers, it is not advised and would be a prime target of refactoring regardless. However you might of had a small test app or the like that upon upgrading Angular to v1.3.x it stops working unexpectedly. The intention behind this change was to prevent poor coding practices.

The actual breaking change is highlighted on GitHub here: https://github.com/angular/angular.js/blob/g3_v1_3/CHANGELOG.md#breaking-changes-13

I like how the use of global controllers according to the change was for, "examples, demos, and toy apps." I agree with the statements, so I'm OK with this change. It really is code smell to use controller functions in the global scope.

Let's look at code that would have worked in Angular versions prior with a trivial sample:

<body ng-app>
    <div ng-controller="MyController">
        <input ng-model='dataEntered' type='text' />
        <div>You entered: {{dataEntered}}</div>
    </div>
    <script src='/Scripts/angular.js'></script>
    <script type='text/javascript'>
        function MyController($scope) {
            $scope.dataEntered = null;
        };
    </script>
</body>

The breaking change requires one to register the Controller with a Module to provide scope and pull it off the global window object. The changes required are shown below:

<body ng-app="SimpleAngularApp">
    <div ng-controller="MyController">
        <input ng-model='dataEntered' type='text' />
        <div>You entered: {{dataEntered}}</div>
    </div>
    <script src='/Scripts/angular.js'></script>
    <script type='text/javascript'>
        (function () {
            function MyController($scope) {
                $scope.dataEntered = null;
            };
            angular.module("SimpleAngularApp", []).controller("MyController", ["$scope", MyController]);
        })();
    </script>
</body>

You might find this will have the biggest impact going forward when you are throwing together quick demos or examples using Plunker or a small test harness. Just remember to register the controller with a Module to prevent running into this error. 

There technically is a workaround if you must make a fix quickly, but not advised long term. You can choose to set $controllerProvider.allowGlobals(); which will allow the old code to run. You can read about it here: https://docs.angularjs.org/api/ng/provider/$controllerProvider

If your apps have previously been constructed using best practices, this should not impact you at all. For additional changes between Angular 1.2 and 1.3, see the following link: https://docs.angularjs.org/guide/migration