Angular's controllers are super-simple which is fantastic for getting started (they're just functions). Because they are just functions, there's lots of different ways to write Angular controllers. This is powerful but can also lead to a lack of structure, especially when many people (with many different coding styles) are working on the same codebase.

It can be helpful to have something a little more structured and prescriptive. Enter Angular Classy!

The code to the right is a Classy controller for a simple Todo application. Take a look, Classy keeps your controllers clean and manageable.

You can play with a live example on plunker (controllerAs example here).

Hover your cursor over the code on the right for comments and explanations.

Injecting Dependencies

Angular veterans will know that if you want your Angular code to work with minifiers then you have to annotate your dependencies (i.e. list your dependencies twice), like so (without Classy):

app.controller 'AppCtrl' , [ '$scope' , '$location' , '$http' , ($scope, $location, $http) -> ]

app.controller( 'AppCtrl' , [ '$scope' , '$location' , '$http' , function ($scope, $location, $http) { }]);

If you want to add/remove a dependancy then you need to remember to do it in two places.

In Classy you don't need to do that, it works with minifiers and your code remains DRY. Here's what it looks like with Classy:

app.classy.controller name: 'AppCtrl' inject: [ '$scope' , '$location' , '$http' ]

app.classy.controller({ name: 'AppCtrl' , inject: [ '$scope' , '$location' , '$http' ], });

Accessing $scope and dependencies

Dependencies are available using @DependencyName this.DependencyName .

To access the $scope You can simply write @$.foo = 'bar' this.$.foo = 'bar'; instead of @$scope.foo = 'bar' this.$scope.foo = 'bar'; . Although you can use still use @$scope this.$scope if you prefer.

Angular Classy works with controllerAs out-of-the-box, you don't need to do anything. If you want to make things a bit cleaner you can prevent data/methods from being added to the $scope by simply changing the config. You can do this on a per-module basis: app.classy.options.controller = { addToScope: false }; or on a per-controller basis: app.classy.controller({ name: 'TodoCtrl' , inject: [ '$scope' , 'filterFilter' ], __options: { addToScope: false } });

Initialisation

Classy provides a canonical place to init things. It's an init method!

At a glance you can quickly see what your controller does when it initialises, no more searching through your controller. Simples!

Data Initialisation

There is also a data property available for defining initial data properties. This will enable you to remove a lot of boilerplate assignment out of your init method.

The data object allows you to use the full power of Angular expressions when defining data properties.

data: { todos: 'todoStorage.get()' , editedTodo: null }

data: todos: 'todoStorage.get()' editedTodo: null

If you prefer you can use a function that returns an object, this allows you to reference other class properties directly (rather than through an angular expression). data: function () { return { todos: this .todoStorage.get(), editedTodo: null } } data: -> todos: this .todoStorage.get() editedTodo: null

Watching Properties

Instead of polluting your init method with lots of calls to $scope.$watch , you can put them in a watch object instead:

watch: 'location.path()' : (newValue, oldValue) -> '{object}todos' : (newValue, oldValue) ->

watch: { 'location.path()' : function (newValue, oldValue) { }, '{object}todos' : function (newValue, oldValue) { } }

Notice the {object} keyword in the second listener above. This allows you to easily specify the type of watcher to use. This is much more explicit than Angular's approach. Here is a table of the available keywords:

Methods

Most of the time when you add a method to a controller, you want it available on the $scope . This is so that you can easily call it in your html using directives like ng-click . Here is how methods look with Classy:

methods: editTodo: (todo) -> _clearCompletedTodos: () ->

methods: { editTodo: function (todo) { }, _clearCompletedTodos: function () { } }

If you don't want the function to be on the $scope then just prefix it with an underscore character ( _ ).

Method Expressions

You can now define methods using angular expressions. Whenever the method is called it will evaluate the expression and return the expression's result. Often, an expression will be much more concise and readable than a full method definition.

methods: getLast5CompletedTodos: 'todos | filter:{completed: true} | orderBy:"timestamp" | limitTo:5'

methods: { getLast5CompletedTodos: 'todos | filter:{completed: true} | orderBy:"timestamp" | limitTo:5' }

Plugins

Angular Classy now supports plugins so you can extend Classy with useful features that allow you to write computed properties and even extend controller classes.

If you want to write your own plugins then head on over to the classy-plugins repo for more information. This will be expanded with more detail over time, if you have a question then raise an issue.

You can see a list of the current Classy plugins on libraries.io.

Only 2KB (gzipped and minified)

it's super tiny so you don't have to worry about it adding weight to your application.

FAQs

Click the questions below to expand the answers.

You use them the same way you normally would, except you don't need to give the controller a name because the controller does not need to be registered outside of Angular. app.directive( 'classyDirective' , function () { return { controller: app.classy.controller({ inject: [ '$scope' ], init: function () { this .$.testing = 'worked' ; } }) }; });

Classy controllers are registered just like normal controllers in Angular so you can reference them the same way (it works in ui-router too). .when( '/classy' , { controller: 'myClassyController' , templateUrl: 'classy.html' });

Angular Classy works with controllerAs out-of-the-box, you don't need to do anything. If you want to make things a bit cleaner you can prevent data/methods from being added to the $scope by simply changing the config. You can do this on a per-module basis: app.classy.options.controller = { addToScope: false }; or on a per-controller basis: app.classy.controller({ name: 'TodoCtrl' , inject: [ '$scope' , 'filterFilter' ], __options: { addToScope: false } });

Earlier versions of Angular Classy didn't allow chaining (of services, routes and factories etc.) with Classy controllers. This was done to allow you to use Classy controllers inline with directives. We are maintaining support for directives but also introducing a new syntax: classy.controllers (notice the 's' at the end) that takes an array of controllers and supports chaining so now you can do: angular.module( 'app' , [ 'classy' ]) .classy.controllers([{ name: 'BarController' , inject: [ '$scope' ], init: function () { this .$.foo = 'bar' ; } }, { name: 'BazController' , inject: [ '$scope' ], init: function () { this .$.foo = 'baz' ; } }]) .service( ) .config( ); angular. module ( "app" , [ "classy" ]) .classy.controllers([ name: "BarController" inject: [ "$scope" ] init: -> @$ .foo = "bar" , name: "BazController" inject: [ "$scope" ] init: -> @$ .foo = "baz" ]).service( ).config( )

I've done some profiling using the timeline in Chrome Developer Tools and performance seems to be in the same ballpark as a vanilla AngularJS controller. I've also done some synthethic benchmarking, and it turns out that method call performance is as good as vanilla Angular controllers. By default, Classy follows best practices like putting methods on the prototype (not all Angular programmers do this) so you might even find that Classy performs better in some instances. But more performance testing is always a good thing so I hope to do more benchmarking in the future.

Next up I want to take a look at Classy services and directives.

No need to apologise. I created the library for myself first and foremost, personally I find it helps me write really structured and readable controllers. It's definitely opinionated and it won't suit everybody but I'm ok with that. :)