Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

From ng-controller to Components With Angular 1.5+

DZone's Guide to

From ng-controller to Components With Angular 1.5+

For those who still haven't migrated their Angular 1 apps past Angular 1.5, it's time to play catch-up. Review the syntax and functionality changes and improvements to upgrade your app towards Angular 2.

· Web Dev Zone
Free Resource

Try RAD Studio for FREE!  It’s the fastest way to develop cross-platform Native Apps with flexible Cloud services and broad IoT connectivity. Start Your Trial Today!

The web has moved forward and so should you. Learn how to upgrade your Angular 1 app from a more MV* architecture to a cleaner, more component oriented approach. We will learn about how to refactor your code properly and about the new features introduced in Angular 1.5+ that will help you succeed along this path.

Image title

The App

I’ve created a series of Plunks which you can use to play around with the code by yourself. So here’s our initial app, a very simple one, just enough to showcase some concepts we’re going to explore.

<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.5.6/angular.min.js" data-semver="1.5.6"></script>
    <script data-require="ui-router@*" data-semver="1.0.0-alpha.5" src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.0-alpha.5/angular-ui-router.js"></script>
    <script src="app.js"></script>
    <script src="home/home.controller.js"></script>
    <script src="about/about.controller.js"></script>
  </head>

  <body ng-app="plunker">
    <div class="navbar">
      <a href="#home">Home</a>
      <a href="#about">About</a>
    </div>
    <div ui-view></div>
  </body>

</html>

Step 1: Remove/Avoid $scope

Ok, $scope was a central element in Angular since the beginning. It’s the glue between your Angular controller and the HTML template. Unfortunately scope is going away in Angular 2. There won’t be anything similar there. That’s why the “controller as” syntax was introduced a while back in Angular 1. So let’s use it. Here’s what John Papa proposes in his popular Angular 1 styleguide.

// before
angular.module('plunker')
  .controller('HomeController', function($scope) {
    $scope.message = 'Hi from home';
  });

// after
angular.module('plunker')
  .controller('HomeController', function() {
    var vm = this;
    vm.message = 'Hi from home';
  });

The “controller as” syntax also extends to the HTML. In the router configuration of our Angular app, we add the controllerAs property and set it to vm.


// before
    $stateProvider
      .state('home', {
        url: '/',
        templateUrl: 'home/home.html',
        controller: 'HomeController'
      })
      ...

// after
    $stateProvider
      .state('home', {
        url: '/',
        templateUrl: 'home/home.html',
        controller: 'HomeController',
        controllerAs: 'vm'
      })
      ...

And consequently we also have to update our HTML code:


<!-- Before -->
<h1>Home</h1>
<p>{{ this.message }}</p>

<!-- After -->
<h1>Home</h1>
<p>{{ vm.message }}</p>

Great, so here’s our app after the change:

<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.5.6/angular.min.js" data-semver="1.5.6"></script>
    <script data-require="ui-router@*" data-semver="1.0.0-alpha.5" src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.0-alpha.5/angular-ui-router.js"></script>
    <script src="app.js"></script>
    <script src="home/home.controller.js"></script>
    <script src="about/about.controller.js"></script>
  </head>

  <body ng-app="plunker">
    <div class="navbar">
      <a href="#home">Home</a>
      <a href="#about">About</a>
    </div>
    <div ui-view></div>
  </body>

</html>

Step 2: Convert Your Controller to a Directive

How is our controller bound to the view? There are different possibilities, one is through the ng-controller tag in the HTML directly, like

<div ng-controller="HomeController as vm">
  ...
</div>

In our app it’s the router, however:

$stateProvider
    .state('home', {
      url: '/',
      templateUrl: 'home/home.html',
      controller: 'HomeController',
      controllerAs: 'vm'
    })
    ...

Notice how we define a template and the corresponding controller. This is a very loose coupling. Theoretically we could use one controller for multiple HTML templates easily. That’s considered bad practice, though. Also, this hinders reusability, because one has to know which template and which controller belong together in order to be able to reuse them in another situation. So let’s change that and stick them together. How? By writing a directive and converting our controller into a directive controller. We have a few different options:

.directive('home', function() {
  return {
    restrict: 'E',
    scope: {},
    template: 'home/home.html',
    controller: HomeController,
    controllerAs: 'vm'
  }
});

function HomeController() {
  ...
}

Heads up: We’re using an isolated scope (scope: {}) for our “components”, because we want them to be fully isolated.

Our routing gets simplified, in that it doesn’t have to know the template location and/or the controller, but simply the HTML tag <home>.

$stateProvider
    .state('home', {
      url: '/',
      template: '<home></home>'
    })

That was easy, right?

<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.5.6/angular.min.js" data-semver="1.5.6"></script>
    <script data-require="ui-router@*" data-semver="1.0.0-alpha.5" src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.0-alpha.5/angular-ui-router.js"></script>
    <script src="app.js"></script>
    <script src="home/home.controller.js"></script>
    <script src="about/about.controller.js"></script>
  </head>

  <body ng-app="plunker">
    <div class="navbar">
      <a href="#home">Home</a>
      <a href="#about">About</a>
    </div>
    <div ui-view></div>
  </body>

</html>

Step 3: Go Further. Use Components!

Angular 1.5+ is the best Angular ever so far. Since v1.5 they introduced the newcomponent syntax which makes creating components super easy. Check out Todd Motto’s article on the topic: Exploring the Angular 1.5 .component() method.

So what does that mean for our app? Let’s see.

// before
.directive('home', function() {
  return {
    restrict: 'E',
    scope: {},
    template: 'home/home.html',
    controller: HomeController,
    controllerAs: 'vm'
  }
});

// after
.component('home', {
    restrict: 'E',
    scope: {},
    templateUrl: 'home/home.html',
    controller: HomeController,
    controllerAs: 'vm'
  });

We can also remove the controllerAs property. This is an optional one. Angular 1.5 components expose the controller to the view through the $ctrl property.


// after
.component('home', {
    restrict: 'E',
    scope: {},
    templateUrl: 'home/home.html',
    controller: HomeController
  });

…and then in the HTML

<h1>Home</h1>
<p>{{ $ctrl.message }}</p>


<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.5.6/angular.min.js" data-semver="1.5.6"></script>
    <script data-require="ui-router@*" data-semver="1.0.0-alpha.5" src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.0-alpha.5/angular-ui-router.js"></script>
    <script src="app.js"></script>
    <script src="home/home.controller.js"></script>
    <script src="about/about.controller.js"></script>
  </head>

  <body ng-app="plunker">
    <div class="navbar">
      <a href="#home">Home</a>
      <a href="#about">About</a>
    </div>
    <div ui-view></div>
  </body>

</html>

Was That Everything?

Not at all, Angular 1.5 components have to offer a lot more.

bindToController and bindings

When you create directives and pass data into them, you have to define a scope property, right?

.directive('myDirective', {
   ...
   scope: {
      message: '='
   }
   ...
})

The problem is that at that point, within your directive controller, you’d have to access them through the $scope variable again, which we previously said should be avoided. Thus, the bindToController has been introduced which allows us to write the above like this:

.directive('myDirective', {
   ...
   scope: {},
   bindToController: {
      message: '='
   }
   ...
})

Much better. message will now be attached directly to our controller instance. Still, we have to create the isolate scope with scope: {} which isn’t the most elegant way of doing it. It gets better . The new component syntax simplifies this into a singlebindings property, which also creates an isolate scope behind the scenes.

.component('myDirective', {
   ...
   bindings: {
      message: '='
   }
   ...
})

You can even get one way bindings

...
bindings: {
   message: '<'
}
...

For more information, check out One-way data-binding in Angular 1.5.

Lifecycle Hooks

Most remarkably a new set of lifecycle hooks have been introduced. Remember the following pattern suggested by John Papa’s famous styleguide?

.component('home', {
    ...
    controller: HomeController
  });

function HomeController() {
  var vm = this;
  vm.message = '';

  activate();

  /////////////////////////

  function activate() {
    vm.message = 'Hi from home';
  }
}

The activate method can be seen like the constructor, a place where to group your controller’s initialization code. Well with the new component syntax, you can make use of the $onInit hook function.

function HomeController() {
  ...
  vm.$onInit = activate;

  /////////////////////////

  function activate() {
    vm.message = 'Hi from home';
  }
}

There are other hooks, like $onChange$onDestroy and so on. Rather than going into those details by myself, check out Todd Motto’s awesome article on the matter: Comprehensive dive into Angular 1.5 lifecycle hooks.

Component Architecture

What we’ve seen so far are the technical details about how you implement components. It’s however important to also understand the concept behind a component oriented development approach.

The main concept is to define isolated and autonomous components, with a given responsibility and clearly defined contracts in terms of which data flows in and out. Generally speaking, there are two main types of components you usually create, and different people give name them differently:

  • smart components / stateful components - These are components that coordinate a set of “dumb component”. They connect with Angular services, fetch data or get invoked through routings.
  • dumb components / stateless components - These are responsible for the immediate visual feedback. They define input bindings and callbacks and render the data they receive. Usually they’re not necessarily coupled to the application and are highly reusable.

What you end with, is a so-called component tree, a set of nested components, starting from a top-level app component or root component.

So a “dumb component” (in every sense) in our sample app could look as follows:
angular.module('plunker')
  .component('message', {
    bindings: {
      from: '<',
      msg: '<'
    },
    controller: MessageController,
    template: [
      '<p><strong>A message from :</strong></p>',
      '<p></p>'
    ].join('')
  });

  function MessageController() {}

And be used within our “smart components” <home> and <about> (big words here ) like

<message from="'Home'" msg="$ctrl.message"></message>


<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.5.6/angular.min.js" data-semver="1.5.6"></script>
    <script data-require="ui-router@*" data-semver="1.0.0-alpha.5" src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.0-alpha.5/angular-ui-router.js"></script>
    <script src="app.js"></script>
    <script src="home/home.controller.js"></script>
    <script src="about/about.controller.js"></script>
    <script src="shared/message.component.js"></script>
  </head>

  <body ng-app="plunker">
    <div class="navbar">
      <a href="#home">Home</a>
      <a href="#about">About</a>
    </div>
    <div ui-view></div>
  </body>

</html>

For reference:

Interested What Role Components Play in Angular 2?

Then check out my video course with PacktPub which focuses on “Learning Angular 2 directives”.

Also, for a quick, 20 minute intro to Angular 2 (especially for beginners), you may want to check out my other article: Angular 2 - A Getting Started Guide for Beginners.

Conclusion

What we’ve seen in this article:

  • How to refactor $scope to the controllerAs syntax
  • How to convert an ng-controller stepwise towards directives and ultimately to components
  • We learned about the new .component syntax introduced in Angular 1.5 and all the benefits we get from it
  • How to convert a MV* pattern like approach into a more component oriented approach

Summarizing, try to migrate your Angular frontend architecture towards a more component oriented approach. Regardless whether you plan to upgrade to Angular 2 at some point or not, it’ll help you anyway create much cleaner applications.

While this article didn’t deep dive into this topic, but is rather intended to give you a first overview, Tero Parviainen has written an in-depth version some time ago: Refactoring Angular apps to Component Style

Thanks to Gerard Sans for reviewing this article!

If you enjoyed this post you might want to follow me on Twitter for more news around JavaScript and Angular 2. :)

Get Your Apps to Customers 5X Faster with RAD Studio. Brought to you in partnership with Embarcadero.

Topics:
angularjs ,html ,components ,fluentd

Published at DZone with permission of Juri Strumpflohner, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}