codelord.net

Code, Angular, iOS and more by Aviv Ben-Yosef

Angular's .component - what is it good for?

| Comments

Note: This post was updated for the official release of 1.5

In my Angular 1.5 sneak peek I mentioned the new .component() method. A lot of people are quite excited about this method. But, as with anything new, there are open questions:

When should you use it?
Why should you use it?
What’s the difference between it and .directive()?

Today we’ll understand what exactly it does and whether it’s good for you.

Syntax sugar

First things first: this method is truly just syntax sugar for the good old .directive() method you know (and hate?).

There’s nothing you can do with .component() that you can’t do with .directive().

So what is it good for?

It aims to simplify the way we create “components” – which roughly means UI directives. It also pushes the community to use the defaults that have become best practices:

  • Components have isolated scopes by default
  • They automatically use controllerAs syntax
  • They use controllers instead of link functions
  • The bindToController option is on by default

If this sounds familiar, you might remember this is basically how I’ve recommended we write directive controllers in this post.

Show me the code: before and after

Here’s an example component directive:

1
2
3
4
5
6
7
8
9
10
11
app.directive('list', function() {
  return {
    scope: {
      items: '='
    },
    templateUrl: 'list.html',
    controller: function ListCtrl() {},
    controllerAs: '$ctrl',
    bindToController: true
  }
});

It’s a simple component directive, with an isolated scope, binding, and a controller.

Here’s how you’ll write it with .component:

1
2
3
4
5
6
7
app.component('list', {
  bindings: {
    items: '='
  },
  templateUrl: 'list.html',
  controller: function ListCtrl() {}
});

As you can see, not much has changed.
But, things are simpler and more straightforward.
Also, we get to enjoy some default and save on boilerplate: bindToController is the default, controllerAs is on and defaults to $ctrl (I would have prefered a nicer looking default, but it’ll do).
Another nice point is that we don’t need to write a stupid function that always returns the same object.
We just define that object right here.

When can/should you use it?

Clearly there are a few cases where you can’t/shouldn’t use it:

  • If you need the link function (though you rarely should)
  • If you want a template-less directive, e.g. ng-click that doesn’t have a template or separate scope

For all your other directives, this should work. And because it saves on boilerplate and less error-prone it’s nicer to use.

I usually prefer to go with whatever would work everywhere.
And here it goes against .component() that it can’t fully replace .directive().

But, using it saves so much boilerplate.
And, non-component directives should be rare, which means they’ll stand out even more.

That’s why, in my opinion, using this new syntax is worthwhile. You can read the full docs about it here.

Lots of new goodies

$onInit

It has been pretty common to group initialization code of your controllers inside some function. For example, John Papa’s style guide recommends an activate() function to fire promises.

With 1.5 there’s official support for this much-needed concept. If the controller has a $onInit function, it will be called once all bindings are initialized:

1
2
3
4
5
6
7
8
app.component('foo', {
  templateUrl: 'foo.html',
  controller: function() {
    this.$onInit = function() {
        console.log('Foo component initialized');
    };
  }
});

This will also make transitioning components to ng2 easier (since it has the equivalent ngOnInit function).

You can require other components/directives

In the first RC releases of 1.5, there was no (nice) way for using the awesome require property directives have. It meant that making a structure of components that communicate using nice APIs on their controllers wasn’t really possible.

But, with the final release we got it all sorted out:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.component('parent', {
  templateUrl: 'parent.html',
  controller: function() {
    this.helperFunc = function() {};
  }
});

app.component('child', {
    templateUrl: 'child.html',
    require: {
        parent: '^parent'
    },
    controller: function() {
        this.$onInit = function() {
            this.parent.helperFunc();
        };
    }
});

This greatly helps in writing maintainble Angular, as I’ve recommended (2 years ago!) here.

One way bindings

First, make sure you don’t confuse this with 1.3’s one-time bindings (or “bind once”).

One-way binding does half of what two-way binding does (surprisingly). Previously, we could pass objects to child directives/components with the = binding:

1
2
3
4
5
6
7
app.component('foo', {
  templateUrl: 'foo.html',
  bindings: {
    bar: '='
  },
  controller: function() {}
});

This would have created a two-way binding with the component’s parent. Whenever the parent would assign a new value, it would be propagated to the child. And vice-versa, if the component assigns a new value it will be copied to the parent.

This, while helpful, isn’t a very common scenario in my experience. That’s why Angular has finally introduced one-way bindings.

These create just a single watch, that watches for changes in the parent and syncs them to the child. We gain performance (by cutting in half the amount of watches created) and things become less error prone. This also is a step towards the way things will usually behave in ng2.

The syntax is similar:

1
2
3
4
5
6
7
app.component('foo', {
  templateUrl: 'foo.html',
  bindings: {
    bar: '<'
  },
  controller: function() {}
});

Yeah, we just change = to <.

All in all, .component() is a great addition to Angular. It’s a real upgrade for code quality and helps you preper for ng2.

So, upgrade to 1.5 and start using .component(): you have unlocked a new skill!

“Maintaining AngularJS feels like Cobol 🤷…”

You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete. Components? Lifecycle hooks? Controllers are dead?

It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines! Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.

Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.

Get the modernization email course!

Comments