Developing a zippy component in Angular

by Pascal Precht on Mar 27, 2015, last updated on Dec 16, 2016
16 minute read

Angular Master Class in Houston

Join our upcoming public training!

Get a ticket

Contents are based on Angular version >= 2.x unless explicitly stated differently.

We are following the development of Angular 2.0.0 since the beginning on and are also contributing to the project. Just recently we’ve built a simple zippy component in Angular and in this article we want to show you how.

Want to see things in action first?

code View Demos

Getting started with Angular 2.0.0

There are several options today to get started with Angular. For instance, we can go to angular.io and use the quickstart guide. Or, we can install the Angular CLI, which takes care of scaffolding, building and serving Angular applications. In this article we will use Pawel Kozlowski's ng2-play repository the Angular CLI, but again, you can use whatever suits you.

We start by installing Angular CLI as a global command on our local machine using npm.

$ npm install -g angular-cli

Once that is done, we can scaffold a new Angular project by running ng new <PROJECT_NAME>. Note that the project is scaffolded in the directory where we’re in at this moment.

$ ng new zippy-app

Next, we navigate into the project and run ng serve, which will essentially build and serve a hello world app on http://localhost:4200.

$ cd zippy-app
$ ng serve

We open a browser tab on localhost://4200 and what we see is the text “zippy-app works!”. Cool, we’re all set up to build a zippy component in Angular!

Building the zippy component

Before we start building the zippy component with Angular, we need to clarify what we’re talking about when using the term “zippy”. It turns out that a lot of people think they don’t know what a zippy is, even if they do, just because of the naming.

Long story short: this, is a zippy.

Also known as “accordion”. You can click the summary text and the actual content toggles accordingly. If you take a look at this particular plunk, you’ll see that we actually don’t need to do any special implementation to get this working. We have the <details> element that does the job for us. But how can we implement such a thing in Angular?

We start off by adding a new file src/app/my-zippy.component.ts and creating a class in ES2015 that we export, so it can be imported by other consumers of this class, by using the ES2015 module system. If you’re not familiar with modules in ES2015 you might want to read our article on using ES2015 with Angular today.

Special Tip: We would normaly use Angular CLI to generate a component for us, instead of creating the files manually, but this articles focuses on understanding the building blocks of creating a custom component.

export class ZippyComponent {

}

The next thing we want to do, is to make our ZippyComponent class an actual component and give it a template so that we can see that it is ready to be used. In order to tell Angular that this particular class is a component, we use something called “Decorators”.

Decorators are a way to add metadata to our existing code. Those decorators are actually not supported by ES2015 but have been developed as language extension of the TypeScript transpiler, which is used in this project. We’re not required to use decorators though. As mentioned, those are just transpiled to ES5 and then simply used by the framework. However, for simplicity sake we’ll use them in this article.

Angular provides us with a couple of decorators so we can express our code in a much more elegant way. In order to build a component, we need the @Component() decorator. Decorators can be imported just like classes or other symbols, by using ES2015 module syntax. If you heard about annotations in traceur before and wonder how they relate to decorators, you might want to read our article on the difference between annotations and decorators.

import { Component } from [email protected]/core';

export class ZippyComponent {

}

The Component decorator adds information about what our component’s element name will be, what input properties it has and more. We can also add information about the component’s view and template.

We want our zippy component to be usable as <my-zippy> element. So all we need to do, is to add a @Component() decorator with that particular information. To specify the element name, or rather CSS selector, we need to add a selector property that matches a CSS selector.

import { Component } from [email protected]/core';

@Component({
  selector: 'my-zippy'
})
export class ZippyComponent {

}

Next, our component needs a template. We add information about the component’s view. templateUrl tells Angular where to load the component template from. To make templateUrl work with relative paths, we add another property moduleId with a value module.id. To get more information on moduleId, make sure to check out our article on Component-Relative Paths in Angular

import { Component } from [email protected]/core';

@Component({
  moduleId: module.id,
  selector: 'my-zippy',
  templateUrl: 'my-zippy.component.html'
})
export class ZippyComponent {

}

Later at runtime, when Angular compiles this component, it’ll fetch my-zippy.component.html asynchronously. Let’s create a file src/app/my-zippy.component.html with the following contents:

<div class="zippy">
  <div class="zippy__title">
    &blacktriangledown; Details
  </div>
  <div class="zippy__content">
    This is some content.
  </div>
</div>

CSS classes can be ignored for now. They just give us some semantics throughout our template.

Alright, believe it or not, that’s basically all we need to do to create a component. Let’s use our zippy component inside the application. In order to do that, we need to do things:

  • Add our new component to the application module
  • Use ZippyComponent in ZippyAppComponent’s template

Angular comes with a module system that allows us to register directives, components, service and many other things in a single place, so we can use them throughout our application. If we take a look at the src/app/app.module.ts file, we see that Angular CLI already created a module for us. To register ZippyComponent on AppModule, we import it and add it to the list AppModule’s declarations:

import { NgModule } from [email protected]/core';
import { BrowserModule } from [email protected]/platform-browser';
import { ZippyAppComponent } from './zippy-app.component';
import { ZippyComponent } from './my-zippy.component';

@NgModule({
  imports: [BrowserModule],
  declarations: [ZippyAppComponent, ZippyComponent], // we're adding ZippyComponent here
  bootstrap: [ZippyAppComponent]
})
export class AppModule {}

We don’t worry too much about the imports for now, but we acknowledge that Angular needs BrowserModule to make our app run in the browser. The declarations property defines all directives and pipes that are used in this module and bootstrap tells Angular, which component should be bootstrapped to run the application. ZippyAppComponent is our root component and has been generated by Angular CLI as well, ZippyComponent is our own custom component that we’ve just created.

Now, to actually render our zippy component in our application, we need to use it in ZippyAppComponent’s template. Let’s do that right away:

import { Component } from [email protected]/core';

@Component({
  moduleId: module.id,
  selector: 'zippy-app',
  template: '<my-zippy></my-zippy>'
})
export class ZippyAppComponent {

}

Nice! Running this in the browser gives us at least something that looks like a zippy component. The next step is to bring our component to life.

Bringing the component to life

In order to bring this component to life, let’s recap quickly what we need:

  • Clicking on the zippy title should toggle the content
  • The title of the should be configurable from the outside world, currently hard-coded in the template
  • DOM that is used inside the <my-zippy> element should be projected in the zippy content

Let’s start with the first one: when clicking on the zippy title, the content should toggle. How do we implement that in Angular?

We know, in Angular 1.x, we’d probably add an ngClick directive to the title and set a scope property to true or false and toggle the zippy content respectively by using either ngHide or ngShow. We can do pretty much the same in Angular >= 2.x as well, just that we have a bit different semantics.

Instead of adding an ngClick directive (which we don’t have in Angular 2.x), to call for instance a method toggle(), we bind to the click event directly using the following template syntax.

...
<div class="zippy__title" (click)="toggle()">
  &blacktriangledown; Details
</div>
...

If you’re not familiar with this syntax I recommend you either reading this article on integrating Web Components with Angular, or this article about Angular’s template syntax demystified. Misko’s keynote from this year’s ng-conf is also a nice resource.

Now we’re basically listening on a click event and execute a statement. But where does toggle() come from? We can access component methods directly in our template. There’s no $scope service or controller that provides those methods. Which means, toggle() is just a method defined in ZippyComponent.

Here’s what the implementation of this method could look like:

export class ZippyComponent {
  toggle() {
    this.visible = !this.visible;
  }
}

We simply invert the value of the component’s visible property. In order to get a decent default state, we set visible to true when the component is loaded.

export class ZippyComponent {

  visible = true;

  toggle() {
    this.visible = !this.visible;
  }
}

Now that we have a property that represents the visibility state of the content, we can use it in our template accordingly. Instead of ngHide or ngShow (which we also don’t have in Angular >= 2.x), we can simply bind the value of our visible property to our zippy content’s hidden property, which every DOM element has by default.

...
<div class="zippy__content" [hidden]="!visible">
  This is some content.
</div>
...

Again, what we see here is part of the new template syntax in Angular. Angular >= 2.x binds to properties rather than attributes in order to work with Web Components, and this is how you do it. We can now click on the zippy title and the content toggles!

Oh! The little arrow in the title still points down, even if the zippy is closed. We can fix that easily with Angular’s interpolation like this:

...
<div class="zippy__title" (click)="toggle()">
  {{ visible ? '&blacktriangledown;' : '&blacktriangleright;' }} Details
</div>
...

Okay, we’re almost there. Let’s make the zippy title configurable. We want that consumers of our component can define how they pass a title to it. Here’s what our consumer will be able to do:

<zippy title="Details"></zippy>
<zippy [title]="'Details'"></zippy>
<zippy [title]="evaluatesToTitle"></zippy>

In Angular >= 2.x, we don’t need to specify how scope properties are bound in our component, the consumer does. That means, this gets a lot easier in Angular too, because all we need to do is to import the @Input() decorator and teach our component about an input property, like this:

import { Component, Input } from [email protected]/core';

@Component({
  moduleId: module.id,
  selector: 'my-zippy',
  templateUrl: 'my-zippy.component.html'
})
export class ZippyComponent {
  @Input() title;
  ...
}

Basically what we’re doing here, is telling Angular that the value of the title attribute is projected to the title property. Input data that flows into the component. If we want to map the title property to a different attribute name, we can do so by passing the attribute name to @Input():

@Component({
  moduleId: module.id,
  selector: 'my-zippy',
  templateUrl: 'my-zippy.component.html'
})
export class ZippyComponent {
  @Input('zippyTitle') title;
  ...
}

But for simplicity’s sake, we stick with the shorthand syntax. There’s nothing more to do to make the title configurable, let’s update the template for ZippyAppComponent app.

@Component({
  moduleId: module.id,
  selector: 'zippy-app',
  template: '<my-zippy title="Details"></zippy>',
})
...

Now we need to change the template of zippy to make title to appear at correct place, let’s udpate the template for zippy title.

...
<div class="zippy__title" (click)="toggle()">
  {{ visible ? '&blacktriangledown;' : '&blacktriangleright;' }} {{title}}
</div>
...

Insertion Points instead of Transclusion

Our component’s title is configurable. But what we really want to enable, is that a consumer can decide what goes into the component and what not, right?

We could for example use our component like this:

<my-zippy title="Details">
  <p>Here's some detailed content.</p>
</my-zippy>

In order to make this work, we’ve used transclusion in Angular 1. We don’t need transclusion anymore, since Angular 2.x makes use of Shadow DOM (Emulation) which is part of the Web Components specification. Shadow DOM comes with something called “Content Insertion Points” or “Content Projection”, which lets us specify, where DOM from the outside world is projected in the Shadow DOM or view of the component.

I know, it’s hard to believe, but all we need to do is adding a <ng-content> tag to our component template.

...
<div class="zippy__content" [hidden]="!visible">
  <ng-content></ng-content>
</div>
...

Angular uses Shadow DOM (Emulation) since 2.x by default, so we can just take advantage of that technology. It turns out that insertion points in Shadow DOM are even more powerful than transclusion in Angular. Angular 1.5 introduces multiple transclusion slots, so we can explicitly “pick” which DOM is going to be projected into our directive’s template. The <ng-content> tag lets us define which DOM elements are projected too. If you want to learn more about Shadow DOM, I recommend the articles on html5rocks.com or watch this talk from ng-europe.

Putting it all together

Yay, this is how we build a zippy component in Angular. Just to make sure we’re on the same page, here’s the complete zippy component code we’ve written throughout this article:

import { Component, Input } from [email protected]/core';

@Component({
  moduleId: module.id,
  selector: 'my-zippy',
  templateUrl: 'my-zippy.component.html'
})
export class ZippyComponent {

  @Input() title;
  visible = true;

  toggle() {
    this.visible = !this.visible;
  }
}

And here’s the template:

<div class="zippy">
  <div (click)="toggle()" class="zippy__title">
    {{ visible ? '&blacktriangledown;' : '&blacktriangleright;' }} {{title}}
  </div>
  <div [hidden]="!visible" class="zippy__content">
    <ng-content></ng-content>
  </div>
</div>

I’ve set up a repository so you can play with the code here. In fact, I’ve also added this component to the Angular project. The pull request is pending merged here and likely to be merged the next few days. At this point I’d like to say thank you to Victor and Misko for helping me out on getting this implemented.

You might notice that it also comes with e2e tests. The component itself even emits it’s own events using EventEmitter, which we haven’t covered in this article. Check out the demos to see event emitters in action!

Demos

Get updates on new articles and trainings.

Join over 1400 other developers who get our content first.

Author

Related Posts

You might also be interested in