Skip to main content
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Nolan Erck
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Nolan Erck ( @southofshasta )

Implementing Controller-As Using A Directive In AngularJS 1.0.8

By on

After implementing the "Track-By" feature of AngularJS 1.2 using a directive in AngularJS 1.0.8, I wanted to see if I could do the same thing for the relatively new "Controller-As" feature. The "controller-as" syntax makes the controller instances available in the View-Model using the provided key. It can do this in a variety of places (including routes and directive configurations); but, for this experiment, I'm only targeting the main, ngController directive.

Run this demo in my JavaScript Demos project on GitHub.

I should point out that I have never actually used the "Controller-As" syntax in my applications (since I'm still on AngularJS 1.0.8 in production); so, forgive me if I miss some of the subtleties of the feature. But, from what I can deduce, it's doing nothing more than injecting the controller instance back into the scope using the "as" reference.

If this is the case, then we can replicate this auto-wiring by using a second directive that requires the "ngController" controller instance and stores it back into the associated scope. Since directives link from the bottom-up, it's important that this action take place in the pre-linking phase so that the controller is available on the View-Model during nested directive linking (including attribute and text interpolation).

<!doctype html>
<html ng-app="Demo">
<head>
	<meta charset="utf-8" />

	<title>
		Implementing Controller-As Using A Directive In AngularJS 1.0.8
	</title>

	<style type="text/css">

		a[ ng-click ] {
			color: red ;
			cursor: pointer ;
			text-decoration: underline ;
		}

	</style>
</head>
<body>

	<h1>
		Implementing Controller-As Using A Directive In AngularJS 1.0.8
	</h1>

	<!-- NOTE: As vm. -->
	<div ng-controller="AppController" as="vm">

		<p>
			<a ng-click="vm.toggleSections()">Toggle Sections</a>
		</p>

		<div ng-switch="vm.section">

			<!-- NOTE: As vm. -->
			<p
				ng-switch-when="firstSection"
				ng-controller="FirstController" as="vm">

				{{ vm.title }}

			</p>

			<!-- NOTE: As vm. -->
			<p
				ng-switch-when="secondSection"
				ng-controller="SecondController" as="vm">

				{{ vm.title }}

			</p>

		</div>

	</div>


	<!-- Load scripts. -->
	<script type="text/javascript" src="../../vendor/jquery/jquery-2.0.3.min.js"></script>
	<script type="text/javascript" src="../../vendor/angularjs/angular-1.0.8.min.js"></script>
	<script type="text/javascript">

		// Create an application module for our demo.
		var app = angular.module( "Demo", [] );


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I control the root of the application.
		app.controller(
			"AppController",
			function() {

				// I represent the currently-selected section.
				this.section = "firstSection";


				// I switch the currently-selected section.
				this.toggleSections = function() {

					this.section = ( this.section === "firstSection" )
						? "secondSection"
						: "firstSection"
					;

				};

			}
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I simply demonstrate that the "vm" reference works.
		app.controller(
			"FirstController",
			function() {

				this.title = "First controller, for the win!";

			}
		);

		// I simply demonstrate that the "vm" reference works.
		app.controller(
			"SecondController",
			function() {

				this.title = "Second controller, heck yeah!";

			}
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I attempt to implement (some of) the "controller-as" syntax in AngularJS
		// 1.0.8. This treats the related ngController as an item of the "view model"
		// in the HTML.
		// --
		// CAUTION: This does NOT make the scope unavailable in the view-model; it simply
		// augments the scope with a circular reference.
		app.directive(
			"as",
			function() {

				// I copy the ngController instance reference into the scope so that it
				// can be used to reference the Controller inside the View.
				function prelink( scope, element, attributes, controller ) {

					scope[ attributes.as ] = controller;

					// Injecting the controller into the scope is likely to create a
					// circular object reference: Controller -> Scope -> Controller. As
					// such, let's take extra care to try to break up the reference in
					// order to help with garbage collection.
					scope.$on(
						"$destroy",
						function handleDestroyEvent() {

							scope[ attributes.as ] = null;

							// Also, now that we've created a closure, it may be helpful to
							// clear "closed over" variable references.
							scope = element = attributes = controller = null;

						}
					);

				}


				// Return the directive configuration.
				// --
				// NOTE: We are using the "prelink" phase so that the controller reference
				// is available to the linking the phase of the nested directives.
				// --
				// NOTE: ngController runs at priority 500.
				return({
					link: {
						pre: prelink
					},
					priority: 499,
					require: "ngController",
					restrict: "A"
				});

			}
		);

	</script>

</body>
</html>

Since I don't have any real-world experience with the "Controller-As" syntax, I don't know if it's truly something that I would use. One of the nice things about using the injected $scope reference is that you never have to worry about the "this" binding. But, once you move to the "controller-as" approach, it seems that you have to start jumping through those old "self=this" kinds of hoops. And for what purpose? I don't entirely understand the problem that the "controller-as" syntax is trying to solve.

Want to use code from this post? Check out the license.

Reader Comments

10 Comments

I think I would prefer binding to "this" instead of scope, just for JS semantics vs Angular semantics. However, it also kind of makes sense to use a separate object ($scope) as the View-Model, rather than treating the entire controller as a View-Model itself -- seems like merging responsibilities. And, we can avoid the whole "self=this" by just doing using .bind(this). I don't necessarily see that as a hoop rather a required step in JS, especially dealing with asynchronous/event driven operations. I usually think of "self=this" as an anti-pattern, unless you need to retain the function in a separate context.

15,688 Comments

@Atticus,

Over the years, I have gone back and forth. Sometimes, I'm all about the "prototype" and creating really clean "classes" with shared functions. Then, sometimes, I'm all about the "revealing module pattern" and using private variables and functions. I think both have a lot of merit and are good in different context and different memory/performance requirements.

I admit that I have not made much use of .bind(), mostly because it hasn't been readily available until IE9 (so much of web development is held back by early IE!).

That said, I think I tend to lean towards one of your sentiments:

> However, it also kind of makes sense to use a separate object ($scope) as the View-Model, rather than treating the entire controller as a View-Model itself -- seems like merging responsibilities.

I do like the idea of having more "private" aspects of the Controller that are not related to the View-Model.

I'll have to do some more thinking on this; it's the first time I've really ever considered the Controller-As feature at all.

1 Comments

We were going to switch to the controller-As syntax recently as it seemed like it cleaned up the syntax quite nicely. We soon realised that it breaks most directives; which is especially problematic if you use any 3rd party directives. I'm still not sure how people reconcile this issue at the moment, but it's a deal breaker for us at the moment.

15,688 Comments

@Pete,

While I haven't tried it, I believe that with a directive there is a "controllerAs" property and a "bindToController" property. Though, admittedly, I have not used either of those in a directive (even in R&D).

Can you talk a bit more about what was breaking, for my own curiosity?

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel