TR

The Blog of Tyler Russell

An Angular2 Timezone Picker - Part 2: Exploring the World (of Ng2)

> Important Notice: > This article was originally posted on April 21, 2015 but due to major changes in the Angular2 codebase, needed to be rewritten. This article should now but up to date as up Angular2 alpha 48. ### TLDR 1. In part 2 of our series, we will use the map generated in part 1 as the beginning of our Angular2 component. 2. Our component will consist of three directives that interact together, the TimeZone, TimeZoneMap, and TimeZonePicker. 3. We will use `@Template` and `@Decorator` to build the first of our three components, the TimeZone. 4. We will use `PropertySetter` to toggle classes on elements not injected into our directive. ### Getting Started Welcome back! Glad to see I didn't scare you off already. In part 1 of our series, we used Kartograph to build and optimize an SVG map from open source .shp files. Now that we're in part 2 of our series, congratulations, you're finally going to see some JavaScript. Well... kind of. We're going to be using TypeScript with ES6 structure and modules. Love it or hate it, it's going to be the defacto way to write Angular2. No, it's not required. But I'd wager that most tutorials and documentation will be written that way. If you've never seen ES6 or TypScript code, take a look [here](http://www.typescriptlang.org/Tutorial) and [here](https://github.com/lukehoban/es6features). That will give you a brief introduction. We'll be using a small subset of the syntax here, so don't fret if you feel overwhelmed. We'll use the [Angular2 Yeoman generator](https://github.com/swirlycheetah/generator-angular2) as a starting point for our project. I personally liked the generator better than the "Getting Started" on [angular.io](http://www.angular.io), but that is not to say that it is perfect. Once the generator was run, I made a few small tweaks to the file organization, just for my own sake. In case you are wondering, my beginning file structure looked like the image below.
Since I updated the file structure, I also had to update a few of the generated files to reflect the changes. You can see those changes in the code blocks below. index.html ```html My Timezone Picker Loading... ``` /src/components/app/main.js ```javascript import {Component, Decorator, Template, bootstrap} from 'angular2/angular2'; import {TimeZonePicker} from 'components/timezonepicker/timezonepicker' // Annotation section @Component({ selector: 'my-app' }) @Template({ inline: 'Testing' }) // Component controller export class Main { constructor() { } } //boostrap the component bootstrap(Main); ``` ### A Quick Note on Angular2 Before we get too far into the weeds, I need to get something out in open... I'm still an Angular2 n00b. *Whew*. I'm glad we got that out of the way. Otherwise this might have gotten really awkward later on. Instead of thinking of this an authoritative source regarding how to write Angular2, this of it as a documented learning experience. It is more of a play-by-play of my own personal adventures as I began learning Angular2 (and continue to learn it). In future posts, you'll notice me trying a couple of different ways to see which works best. Despite what most blog posts would have you believe, we make mistakes as we program. So if you're still learning Angular2, we'll be learning together. If you're an expert, feel free to comment and let me know how to improve. Feedback is welcome and highly encouraged. Also, as to be expected of a framework in alpha stages, Angular2 is still the wild wild west of coding. Expect to read source code, design documents, and pull requests as you try to figure things out. And expect that things are likely to change in the future. In fact, since beginning this post, a [few](https://github.com/angular/angular/issues/1244) [open](https://github.com/angular/angular/issues/973) [issues](https://github.com/angular/angular/issues/1239) have already changed some fundamental parts of how to write Angular2 directives. I will try and update once the changes have been made public in the alpha releases. Even with all those caveats, writing Angular2 code is a lot of fun. It feels like a combination of the best parts of a lot of different frameworks. I hope you will find that you learn something from my misadventures. I will attempt to explain some of the speed bumps I've run into, and some of the lessons that I have learned thus far. If you feel like my explanation are lacking, and you need more information, [this curated list](https://github.com/timjacobi/angular2-education) is probably a good starting point for information on Angular2. ### The Architecture Because of it's complexity, we already know that our time zone picker is going to consist of a number of different Angular2 directives working together. There is one major constraint that we need to keep in mind as we plan our architecture so that we don't code ourselves into a corner. We need to remember that *our map is a generated SVG*. This turns out to be very important as we consider how to write our code. We want to be able to use our map in it's generated form without modifying the SVG code. This will allow us to regenerate maps with different bounds, timezones, etc without requiring tweaks by hand, or writing some sort of post-processor. In more specific terms, it means that our Angular2 components need to work with the existing SVG structure of the map. In traditional directive development, we would be defining the templates. With our picker, that is almost reversed. The template is defined, and we are writing directives to assign behavior. You could do the same thing with AngularJs (v1.x), but it actually feels even slicker doing it in Angular2. You'll see what I mean as we dig deeper. Alright, so with that constraint in mind, we know that we will need at least 3 directives: 1. The Time Zone Picker - this will serve as the container component. It will provide some behavior, but mostly styling and a wrapper template 2. The Time Zone Map - this is the component that will represent our map. It will coordinate the state across all time zones. 3. The Time Zone - this component will serve to assign click behaviors to a time zone in our map. Let's start by creating stubs for each of these directives. /src/components/timezonepicker.js ```javascript import {Component, Template} from 'angular2/angular2'; import {TimeZoneMap} from 'components/timezonepicker/timezonemap'; @Component({ selector: "timezone-picker" }) @Template({ inline: "", directives: [TimeZoneMap] }) export class TimeZonePicker{ constructor(){ console.log("TimeZonePicker constructed"); } } ``` /src/components/timezonemap.js ```javascript import {Component, Template} from 'angular2/angular2'; import {TimeZone} from 'components/timezonepicker/timezone'; @Component({ selector: "timezone-map" }) @Template({ url: "components/timezonepicker/timezonemap.svg", directives: [TimeZone] }) export class TimeZoneMap{ constructor(){ console.log("TimeZoneMap constructed"); } } ``` /src/components/timezone.js ```javascript import {Decorator} from 'angular2/angular2'; import {PropertySetter} from 'angular2/src/core/annotations/di'; @Decorator({ selector: '[data-tz-id]' }) export class TimeZone{ constructor(){ console.log("TimeZone constructed"); } } ``` ### Pause! If you're new to Angular2, those stubs already contain an awful amount of new information. Here is a crash course: ##### "import {item} from 'location' This is ES6 module syntax for loading dependencies. Pretty sweet huh? With ES6, we finally get a proper module syntax baked into the language itself. The other side of this is the `export` syntax, which allows us to declare what items are exposed to consumers of our modules. If you want more information, do a quick Google search for "ES6 modules". Or read about the different aspects of the syntax [here](http://www.2ality.com/2014/09/es6-modules-final.html). Lot's has been written already, so I won't bother duplicating it. ##### @Component, @Template, @Decorator These fancy things are called "annotations". They've been a pivotal part of other languages for a while, and a few JavaScript libraries as well. Angular2 uses annotations extensively. Think of them as neat ways to establish metadata. Annotations are becoming more commonplace in client-side frameworks, so I wouldn't be surprised to see widespread use in JavaScript in the near future. That being said, annotations are not part of the ES6 spec, and still rely on transpilers to convert them into functional browser code. But for all you [babel.js](https://babeljs.io/) (and equivalent) users out there, don't worry, people are already working on getting the Angular2 annotations supported. ##### @Component vs @Decorator There are [three types of directives in Angular2](https://github.com/angular/angular/blob/master/modules/angular2/docs/core/02_directives.md), Decorator, Component, and Viewport. In short, decorators are useful for encapsulating behavior, components are useful for encapsulating behavior and visual state. ##### Classes and constructor() ES6 provides the `class` keyword as some nice syntax sugar for OO-style programming in JavaScript. Constructor functions are part of that syntax sugar. It is called any time an instance of this class is created. If you want more information about the class syntax, [here](http://www.2ality.com/2015/02/es6-classes-final.html), [here](http://www.2ality.com/2013/07/defending-constructors.html) and [here](http://www.frontendjournal.com/javascript-es6-learn-important-features-in-a-few-minutes/) are great starting points. Otherwise, a few Google searches will get you a ton of information. Most of the other items in the stub code should be pretty self-explanatory. The properties of the annotations provide configuration for Angular2 to build and find the components we are creating. You'll also notice that we use the selector "[data-tz-id]" in our TimeZone `@Decorator` annotation. When we generated our map, we designated that the timezone id should be added to each time zone SVG path with that attribute. That makes it really easy to create a directive instance for each time zone. ### Back to the show Now that we have our stubs, let's make sure it all works. In `/src/components/app/main.js` let's change our app component to include a TimeZonePicker in its template. ```javascript //change the template from this @Template({ inline: 'Testing' }) //to this @Template({ inline: '' }) ``` And let's fire up our component using `gulp dev`.
We see that each of the components is being instantiated! Perfect. Let's start fleshing them out. ### Make the Time Zones Clickable Let's start to add some behavior. We know that we want the user to be able to click on a time zone and have it be selected. So let's start there. In Angular 1.x, we'd add an ng-click with a callback. In standard Angular2, we'd probably add something like `(click)="doSomething()"` to our template. But remember how we don't want to change the SVG to add properties? So how in the world can we assign click behavior to the time zone? Maybe we could inject the current element and add a click listener by hand, that calls a function in the TimeZone. That is actually very possible in Angular2. This is actually the way that the Angular2 team does it in a few cases. If you look at the code for the [`class` directive](https://github.com/angular/angular/blob/master/modules/angular2/src/directives/class.js), you'll see that you can inject the element in your class constructor. ```javascript import {Decorator} from 'angular2/angular2'; import {PropertySetter} from 'angular2/src/core/annotations/di'; @Decorator({ selector: '[data-tz-id]' }) export class TimeZone{ constructor(ngEl: NgElement) { console.log("TimeZone constructed"); this._domEl = ngEl.domElement; } onTimeZoneClick(){ console.log("time zone was clicked!"); } } ``` As we get started down this path, however, it quickly becomes apparent that this isn't the way we want to go. It's nice to know this is available, but there must be a simpler way than just adding behavior directly to the DOM element. Luckily, there it. If we go back to the documentation for the [`@Decorator` directive type](https://github.com/angular/angular/blob/master/modules/angular2/docs/core/02_directives.md#decorators), we'll notice a property called `events`. The `events` property allows up to wire up listeners on our host DOM element that are automatically wired to expressions in our directives. So let's tweak our TimeZone implementation to include the click event and corresponding callback.
--Author Injection-- Ugh. This one literally [changed underneath me](https://github.com/angular/angular/issues/1244) as I was writing this article. It looks like it is now `hostListeners`. The Angular2 documentation has been updated, but the code hasn't been shipped yet as far as I can tell. I'll update my examples once the changes ship in the alpha releases.
So let's take out our element in the constructor, and wire up the `events` property of the `@Decorator` annotation. We'll also add in a callback function on our TimeZone component to be called when the click happens. That feels more Angular2-ish that manipulating the DOM element directly. ```javascript import {Decorator} from 'angular2/angular2'; import {PropertySetter} from 'angular2/src/core/annotations/di'; @Decorator({ selector: '[data-tz-id]', events: { click: 'onTimeZoneClick()' } }) export class TimeZone{ constructor(){ console.log("TimeZone constructed"); } onTimeZoneClick(){ console.log("time zone was clicked!"); } } ``` Alright, now let's fire up our picker and take a look. Hmm. As we click around, you might notice that nothing is happening. What the heck? Why isn't it working? Ah yes, a quick Google search reminds us that [SVG paths with no fill are not clickable](http://stackoverflow.com/questions/11701461/attach-events-to-svg-paths). I can't tell you how many times I've had to re-learn that lesson. We don't have a proper place to add the fill style yet, so let's just make a quick tweak to `index.html` to add a style tag with the necessary CSS. We'll come back to styling a few times later. ```html ``` Now we're in business. Our time zone is responding to clicks. But we still don't know which id corresponds to the time zone shape being clicked. That information is available in the `data-tz-id` attribute on the DOM element, but not in our Decorator. Let's head to the [documentation](https://github.com/angular/angular/blob/master/modules/angular2/docs/core/02_directives.md#decorators) one more time. It looks like directives can specify a `bind` property to get attributes from the DOM elements. That sounds like it might fit the bill, so let's give it a try. On a quasi-related side note, because of the way that Angular2 bindings work, the `bind` property is actually the same for both static property values and dynamic values from parent directives. So no more `@` and `=` in directive definitions. The value we get back from `bind` is the value we can use. No more need to run $parse or $eval. Yay!
--Author Injection-- It looks like `bind` has also been changed in some recent Angular2 changes. It's new name is `properties` and has changed from an object to a list. Again, expect an update in the future to fix the code samples once the changes are published to the alpha releases.
In our TimeZone directive, let's add the `bind` property. ```javascript import {Decorator} from 'angular2/angular2'; import {PropertySetter} from 'angular2/src/core/annotations/di'; @Decorator({ selector: '[data-tz-id]', bind: { "id" : "data-tz-id" }, events: { click: 'onTimeZoneClick()' } }) export class TimeZone{ constructor(){ console.log("TimeZone constructed"); } onTimeZoneClick(){ console.log("time zone " + this.id + " was clicked!"); this.active = true; } } ```
And just like that, we have an `id` property in our TimeZone directive that stores our time zone id. But we also want to show the visual state to our users. Ideally, when a time zone is selected, it would be highlighted on our map. The easiest way would be to add a class based on the state of the component. But again, since we can't modify the SVG template, we can't add a class directive to do it for us. So how can we add a class to the host element? Are we back to injecting the NgElement into the component? Fortunately, no. The answer comes by means of a `PropertySetter`. A `PropertySetter` is an object that can be injected into our directive instance and allows us to modify a property of the DOM element to which the component is bound. In the case of modifying classes, if we prefix our property name with "class.", we have a neat shortcut that will add/remove the class whenever the property is set to true or false. ```javascript import {Decorator} from 'angular2/angular2'; import {PropertySetter} from 'angular2/src/core/annotations/di'; @Decorator({ selector: '[data-tz-id]', bind: { "id" : "data-tz-id" }, events: { click: 'onTimeZoneClick()' } }) export class TimeZone{ constructor(@PropertySetter('class.active') setClassProperty:Function){ console.log("TimeZone constructed"); this.setClassProperty = setClassProperty; } onTimeZoneClick(){ console.log("time zone " + this.id + " was clicked!"); this.active = !this.active; } set active(value){ if(value !== this._active){ this.setClassProperty(value); this._active = value; } } get active(){ return this._active; } } ```
--Author Injection-- And... `PropertySetter` syntax has been changed as well. I'll update the code soon.
With the `PropertySetter` now configured, we should be able to see the `active` class get toggled on our SVG timezone path whenever we click on a timezone.
Okay, that seems to be working. But we still can't visually see the state change. We need to assign some visual significance to the `active` class. This is probably a good time to take a step back and do some quick initial styling of our component. ### Some Quick Styling Let's take a quick break from all of this new Angular2, and put in some quick CSS so that we can see when a timezone is selected. We already have a small style block In `index.html` that we used to set our timezone fill to transparent. We'll tweak it to include a few more styles. ```html ```
Now we're getting somewhere. We'll come back to tweak these styles one more time later (to jazz it up just a bit), but this should get us enough to test actual user interaction without looking at the DOM every time. ### Wrapping Up With all the topics that we've covered already, now is probably as good a time as any to give our minds a well deserved rest. Let's do a quick review of what we've learned: 1. Angular2 is still evolving, many things are still changing under the covers. 2. Our component will consist of three directives, TimeZonePicker, TimeZoneMap, and TimeZone. 3. There are three different directive annotations in Angular2, `@Decorator`, `@Component`, and `@Viewport`. We use `@Decorator` and `@Component` for our directives. 4. ES6 syntax mixed with TypeScript is different, but neat. 5. Using directive annotations, we can get/set element properties and events without getting a direct reference to the DOM element. ### Coming Up Next In part 3 of our series, we'll get into some of the really fun and interesting improvements of Angular2 over AngularJs (v1.x). We'll discuss the four different dependency injection types, what they mean, and a few caveats of wiring everything up. We'll also cover using `EventEmitter` to broadcast changes to consumers of our component. Finally, we'll put the finishing touches on our component in order to prepare it for users. It should be a good read. Thanks for reading! Follow on Twitter for updates on subsequent posts! --T

Comments