Angular 1.5 and Typescript Best Practices - Part 1 Components

development

Angular 1.5 and Typescript Best Practices - Part 1 Components

VS Code with Angular.js Typescript

Why are you still using Angular 1?! If you want to use Typescript, Angular 2 is far better!

Angular 2 will be far better, and lots of it is far better right now but there still kinks to be worked out in the development stack (using 3rd party, non-es2015 module/typescript libraries from <npmjs.com> or GitHub, the Angular CLI, Angular 2 Material and other important feature libraries … don’t even get me started on the churn with the new router).

I was at NG-Conf the past two years and I think Angular 2 is awesome, but I have also built some cool apps and finished solid products for clients using Angular.js.

It has an established ecosystem and the framework is still awesome compared to what else is out there (especially when using newer features like components and Typescript).

There are plenty of style guides out there, hasn’t all this stuff been figured out already?

Well, yes and no. The popular style guides for Angular 1 by John Papa and Todd Motto are great resources and should be read by any developer working with Angular 1 and they still apply when working with Angular 1.5 (especially Todd’s) and Typescript.

But I feel this combination of technologies lends itself to new options in syntax and architecture.

Well then, show me something!

Below is an example of how I’ve been writing Angular 1.5 components in Typescript. The repository containing all of this example code can be found on my GitHub Here is the structure of the files in this demo app.

VS Code tree view of Angular.js project structure

Models

The user.ts model class defines the structure of the data I’m passing between components. In this case it’s a User with an id and a name.

export class User {
  public id: number;
  public name: string;
}

Services

The user.service.ts is the layer between the components and the API from which they get their data. In this example requests to and responses from that API are simulated using angular’s $timeout service which returns data in the form of a User instance.

import { User } from '../models/index';

import { INotificationService, NotificationService } from '../../shared/index';

export interface IUserService {
  saveUser(user: User): ng.IPromise<User>;
}

export class UserService implements IUserService {
  public static serviceName: string = 'UserService';
  public static $inject: Array<string> = [
    '$timeout',
    NotificationService.serviceName,
  ];

  constructor(
    private $timeout: ng.ITimeoutService,
    private notificationService: INotificationService
  ) {}

  public saveUser(user: User): ng.IPromise<User> {
    return this.$timeout(() => this.simulateSave(user), 3000);
  }

  private simulateSave(user: User): User {
    this.notificationService.showSuccess(
      `User ${user.name} successfully saved`
    );
    return new User(user.id, user.name);
  }
}

On line 1 I import the User model class used throughout the app to keep the fields I have available for that class strongly typed. On line 5 I use an interface to abstract away the actual UserService. This allows me the flexibility of potentially using different implementations of the service in different components and adding additional layers of abstraction if needed, but typically the benefit comes in easier unit testing. The UserService class implements the interface and exposes a static field serviceName which allows me to reference the class by a string name, which is required for angular’s DI system. The constructor, on line 13, allows me to strongly type any dependencies the service has and I use Typescript’s parameter properties to define the classes’ properties along with their access modifiers with the same name all in one expression. I export both the IUserService and UserService since there isn’t much else going on in this file (which makes it easy to spot the exports).

Components

User-Form-Component.ts

Here is the user form component:

import _ from "lodash";

import { User } from "../models/index";

import template from "./user-form.component.html!text";

interface IBindings {
    [key: string]: any;

    user: any;
    onUserSave: any;
}

class Changes implements ng.IOnChangesObject, IBindings {
    [key: string]: ng.IChangesObject<any>;

    public user: ng.IChangesObject<User>;
    public onUserSave: ng.IChangesObject<(param: { user: User}) => ng.IPromise<void>>;
}

let bindings: IBindings = {
    user: "<",
    onUserSave: "&amp;"
};

class controller implements IBindings {
    public static readonly componentName: string = "userForm";
    public static readonly $inject: Array<string> = ["$log"];

    public user: User;

    public onUserSave: (param: { user: User }) => ng.IPromise<void>;

    public componentState: {
        isLoading: boolean
    };

    constructor(private $log: ng.ILogService) {}

    public $onChanges(changes: Changes) {
        if (changes.user &amp;&amp; this.user) {
            this.user = _.cloneDeep(this.user);
        }
    }

    public $onInit() {
        this.componentState = {
            isLoading: false
        };

        this.$log.info("User Form initialized");
    }

    public saveUser() {
        this.$log.info("Saving user");

        this.componentState.isLoading = true;
        this.onUserSave({ user: this.user })
            .finally(() => this.componentState.isLoading = false);
    }
}

export let UserFormComponent = { controller, template, bindings };

User-Form-Component.html

<h4>Editing User {{ $ctrl.user.id }}</h4>

<input
  type="text"
  ng-model="$ctrl.user.name"
  ng-disabled="$ctrl.componentState.isLoading"
/>
<button
  ng-click="$ctrl.saveUser()"
  ng-disabled="$ctrl.componentState.isLoading"
>
  <span ng-if="!$ctrl.componentState.isLoading">Save</span>
  <span ng-if="$ctrl.componentState.isLoading">Saving...</span>
</button>

I’ll comment on a few things here that apply to some of the other components in the project.

Imports
import template from './user-form.component.html!text';

On line 5 I use the SystemJs text plugin which inlines this template file into the ‘template’ variable at run/build time. There is also a custom template.d.ts typing which tells Typescript that this is ok! The nice thing about this plugin is that I don’t need gulp to do any builds (this is great for a project this small) and for production I only have 1 file to deploy as a bundle.

Bindings
interface IBindings {
  [key: string]: any;

  user: any;
  onUserSave: any;
}

class Changes implements ng.IOnChangesObject, IBindings {
  [key: string]: ng.IChangesObject<any>;

  public user: ng.IChangesObject<User>;
  public onUserSave: ng.IChangesObject<
    (param: { user: User }) => ng.IPromise<void>
  >;
}

let bindings: IBindings = {
  user: '<',
  onUserSave: '&amp;',
};
class UserFormController implements IBindings {

On line 7 I define an interface named IBindings which I use to ensure my component controller and changes parameter in $onChanges() have the same binding property names. I’ve found that with a component architecture the bindings of the components become more of a focus in the application and as I’m developing I’m often refactoring the names of the bindings and which bindings exist since they define the public API of the component to the application. What I don’t want to happen is to forget to update those bindings each place their used throughout my component file whenever a change is made. I also like to have that API front and center so that when I want to re-use or debug the component I know what I should be looking for. This is also the reason I define a bindings variable directly below the IBindings interface. The [key: string]: any; part of IBindings satisfies the Angular type definitions for a component object’s bindings property ( let bindings: IBindings = {}) - a necessary bit of extra cruft at this time to make this all work. I also have a class Changes which implements both IBindings and ng.IOnChangesObject. This all ensures that my component object bindings property has the same properties as my class and my $onChanges parameter. The IOnChangesObject interface gives my $onChanges parameter even more intelligence with type checking for something like changes.onUserSave.isFirstChange().

Note that none of this bindings stuff is required to make Angular or Typescript work but like I mentioned above, I have run into changing bindings coming back to bite me and since they are truly the public interface of the component, especially with non-trivial and often reused components, it can make sense to guard against typos.

I follow the **onNounVerb** pattern for event bindings, which isn’t required but it helps keep the names predictable. This also makes them easier to read and identify as HTML attributes in templates.

Component plumbing
public static readonly componentName: string = "userForm";
public static readonly $inject: Array<string> = ["$log"];

The controller class has two static fields. The first is the componentName which is what I use in my call to angular.component in main.ts to make sure I don’t have any typos. It is also the pattern to use this component in a template. Keeping this at the top of the class is a nice reference of an implementation detail and a defining characteristic of the component. This is the component version of the serviceName static field I use in my UserService above. The second static field is $inject which is the list of keys of injectable dependencies that Angular 1 requires to match up the controller’s constructor parameter list with objects the framework knows about. I have used the angular annotate plugin for gulp in the past and while I like it, as mentioned earlier, I’m not using gulp in the project, so manual wiring of the injectables is required. It’s not as bad as it seems and with a component architecture each piece is doing less so the list of dependencies is often much shorter.

Binding in Typescript

The UserFormController implements the IBindings interface which means it needs to expose a user and onUserSave field or property.

public user: User;

public onUserSave: (param: { user: User }) => ng.IPromise;

On line 30 is the first of the two component bindings which satisfy the IBindings interface and match the bindings variable. The first is the user field of the class which is of type User. This is a one-way-binding of information coming into the component.

The second, onUserSave, is a callback or event binding (I use both terms interchangeably) which allows the component to send information back up to any parent context. Due to the way the way that callback bindings are implemented in Angular 1 it is necessary to wrap any information you would like to pass to this callback in an object with a property that matches the name of the parameter of the callback function being bound to. To represent this in Typescript, the signature of (param: { user: User }) => ng.IPromise; matches any callback function passed to the UserFormComponent that has a parameter named user of type User which returns a Promise of type void when called.

Although I don’t see it very often, it is absolutely possible to send information back to a child component through the return value of a callback function. For example, if onUserSave returned a string instead of a Promise we could write a statement like let stringVal = this.onUserSave({ user: this.user }); I don’t like the idea of circumventing Angular’s one-way-binding in/callback-binding out design by sending information back to the child component through that callback binding. It feels like a secret side channel of communication from the typical cyclical flow of component bindings. That said, I have found it beneficial to chain off promises returned by these callbacks to put my child components into different states if say, the parent is loading something or some other data which is expected to arrive is not yet ready for the child to consume. This, of course, can be accomplished through more bindings (for example an isLoading binding into the component) but it’s also advantageous to keep the binding API of a component small because that tends to mean the component itself is small and simple.

Component fields/state
public componentState: {
    isLoading: boolean
};

Following the class fields which contain those binding values is the field which I use to represent the component’s internal state, which is why it is named componentState. I tend to place anything that relates to toggling UI elements, modes, Enums, ect… in this object. You can see it currently has the Boolean property isLoading. I’ve found that placing all values related to the visual or functional state of the component into the componentState field to be helpful for organization and readability. I try to contain mutations and state changes inside my components to componentState and the component bindings.

constructor(private $log: ng.ILogService) {}

After all my class fields have been defined I define the class constructor where Angular does its dependency injection magic. I always use interfaces for my injected services for the sake of testing and loose coupling. I don’t often place any logic in my constructor and instead rely on the $onInit lifecycle hook but sometimes it’s necessary to perform field initialization before then and the constructor is a good place for that. You can see here I’m using the angular typings library. Typescript knows that ng is a global object so I can refer to it anywhere without an import statement. That ng object has as properties all the Angular services, types or providers that I could want access to. They are mostly interfaces and are mostly named how you would expect. $q for example can be found at ng.IQService. I always define these injected dependencies as private (or protected if the class is using inheritance). These services are not things I want exposed in my template and in order to keep my component’s API small I expose the effects of injected dependencies through the component class fields, methods or bindings.

Component Lifecycle
public $onChanges(changes: Changes) {
    if (changes.user &amp;&amp; this.user) {
        this.user = _.cloneDeep(this.user);
    }
}

public $onInit() {
    this.componentState = {
        isLoading: false
    };

    this.$log.info("User Form initialized");
}

Next on line 40 is the first of the Angular 1.5 component lifecycle hooks. This post by Todd Motto on component lifecycle hooks was a regular resource for me when I was first exploring all the new features made available by the component API. I always place my hooks in the same order, $onChanges, $onInit, ( $postLink), $onDestroy. I stick to this order because this is the order that these hooks are called. When first using components I spent a day quite confused because I though $onInit always came first when in fact the binding updates in $onChanges happen before the component has initialized. I use lodash and its cloneDeep method to ensure that the object reference connection between the component’s binding field’s value and the parent component value are broken. Without _.cloneDeep(this.user); I could change the internal properties of this.user and have it reflected in the parent context. This is too close to the two-way binding of directives pre-Angular 1.5 and in my experience it makes it much hard to reason about the components, their communication and their state.

I don’t use functions like angular.copy in my code because the global angular variable isn’t there in Angular 2 (so why build legacy into my code if I can avoid it?) and I am using lodash any way for an assortment of other use cases so I rely on it instead. On line 39 is $onInit where I normally initialize my componentState field and call any services that provide data the component depends on.

Component Methods
public saveUser() {
    this.$log.info("Saving user");

    this.componentState.isLoading = true;
    this.onUserSave({ user: this.user })
        .finally(() => this.componentState.isLoading = false);
}

Here on line 54 is the only normal public method of my class which happens to be bound to an ng-click on a button in my component template. This method performs a series of pretty common actions in my component. First it does some setup, maybe constructs an object to be sent to an API or toggles the state of the component. Then the action is performed which here is calling the callback binding with the user object which came in through the other binding. You can see how I chain a .finally() off the Promise returned by the callback binding so that I can set this.componentState.isLoading to false again. Note, as mentioned earlier I could avoid this back channel transfer of information and instead add an isLoading binding to the component which the parent could toggle on and off before and after communicating with an API, for example.

Exports
export let UserFormComponent = { controller, template, bindings };

Finally I construct the object Angular expects me to register as the component and then export it using the ES2015 module syntax and some shorthand object initialization.

So how does this all work?

User-List.Component.ts

If we take a look at the user-list.component.html template things should start to come together.

<div>
  <ul>
    <li ng-repeat="user in $ctrl.users">
      <user user="user" on-user-select="$ctrl.userSelectHandler(user)"> </user>
    </li>
  </ul>
</div>
<div>
  <user-form
    ng-if="$ctrl.selectedUser"
    user="$ctrl.selectedUser"
    on-user-save="$ctrl.userSaveHandler(user)"
  >
  </user-form>
</div>

So we can see here that I have a repeated list of users all being bound to a element (one of the app’s components) and below that is the component with the exposed bindings. user is bound to the user-list.component’s selectedUser field and the on-user-save callback binding is bound to the user-list.component’s userSaveHandler method. This is another convention I use for components. All methods bound to callback bindings of child components are named with the **nounVerbHandler** pattern. Let’s look at the user-list.component.ts code.

import _ from 'lodash';

import { User } from '../models/index';

import { IUserService, UserService } from '../services/index';

import template from './user-list.component.html!text';

class controller {
  public static readonly componentName: string = 'userList';
  public static readonly $inject: Array<string> = [
    '$log',
    UserService.serviceName,
  ];

  public selectedUser: User;

  public users: Array<User>;

  constructor(
    private $log: ng.ILogService,
    private userService: IUserService
  ) {}

  public $onInit() {
    this.users = [new User(1, 'Joe'), new User(2, 'Abby'), new User(3, 'Jin')];

    this.$log.info('User List initialized');
  }

  public userSelectHandler(user: User) {
    this.selectedUser = user;
  }

  public userSaveHandler(user: User): ng.IPromise<void> {
    this.$log.info('Saving user');

    // make request to api to save the user
    return this.userService.saveUser(user).then((updatedUser) => {
      let index = _.findIndex(this.users, (u) => u.id === updatedUser.id);
      this.users[index] = updatedUser;
      this.selectedUser = null;
    });
  }
}

export let UserListComponent = { controller, template };

It might seem like a lot of new stuff is going on here but it’s mostly the same patterns and conventions we looked at in user-form.component.ts.

Imports

import _ from 'lodash';

import { User } from '../models/index';

import { IUserService, UserService } from '../services/index';

import template from './user-list.component.html!text';

First we import everything we need, including lodash, the User model class, the UserService and its interface and finally the component’s template, again using the SystemJs text plugin.

Component plumbing
public static readonly componentName: string = "userList";
public static readonly $inject: Array<string> = ["$log", UserService.serviceName];

Here’s that component metadata again with the componentName stating that it will appear in a template as because of Angular’s conversion of camelCase to dash-case of selectors for custom components/directives. We also see the advantage of defining the name of a class as a static string field on that class. The UserService is registered as a service in the app but to satisfy dependency injection we need to have the same string name in the $inject array as we do in the angular.module().service() call. Using the static serviceName field allows us to avoid magic strings and ensure that the name we give Angular is the same name we spread around our app wherever the dependency is required.

Constructor
constructor(
    private $log: ng.ILogService,
    private userService: IUserService) {}

In the constructor we ensure the dependencies are in the same order as they appear in our $inject array.

Component Lifecycle
public $onInit() {
    this.users = [new User(1, "Joe"), new User(2, "Abby"), new User(3, "Jin")];

    this.$log.info("User List initialized");
}

We use the $onInit to set up some fake data the app will use to demo functionality.

Component Methods
public userSelectHandler(user: User) {
    this.selectedUser = user;
}

public userSaveHandler(user: User): ng.IPromise<void> {
    this.$log.info("Saving user");

    // make request to api to save the user
    return this.userService.saveUser(user)
        .then(updatedUser => {
            let index = _.findIndex(this.users, (u) => u.id === updatedUser.id);
            this.users[index] = updatedUser;
            this.selectedUser = null;
        });
}

These component methods are the handlers to the callback bindings of the user-list.component’s child components ( UserComponent and UserFormCompnoent). They get called whenever an event happens in one of the child components that it deems important enough to alert the parent about. They both accept a parameter of type User.

Since callback bindings have always seemed to confuse Angular developers, make sure to note that the parameter name in these methods does not need to match the name of the parameter used in the template but the name of the parameter used in the template does need to match the name of object property of the object passed to the callback binding in the child component. For example, I could change the signature of userSaveHandler(user: User) to userSaveHandler(aUserToSave: User) and everything keeps working just fine, but if I update my template from on-user-save="$ctrl.userSaveHandler(user)" to on-user-save="$ctrl.userSaveHandler(abcdefg)" then I will need to update the callback binding call inside user-form.component.ts to this.onUserSave({ abcdefg: this.user }).

Export
export let UserListComponent = { controller, template };

You can see here that this component has no bindings but the component declaration and export look the same as the user-form.component.ts.

The Final Pieces

The user.service.ts and user.component.ts are more of the same so you can explore those on your own. So lets just ahead to the final few pieces that make this all work.

main.ts

import * as ng from 'angular';

import * as shrdSvcs from './shared/index';

import * as userCmpt from './user/components/index';
import * as userSvcs from './user/index';

let moduleName = 'demo';

ng.module(moduleName, [])
  .service(
    shrdSvcs.NotificationService.serviceName,
    shrdSvcs.NotificationService
  )

  .service(userSvcs.UserService.serviceName, userSvcs.UserService)
  .component(
    userCmpt.UserFormComponent.controller.componentName,
    userCmpt.UserFormComponent
  )
  .component(
    userCmpt.UserComponent.controller.componentName,
    userCmpt.UserComponent
  )
  .component(
    userCmpt.UserListComponent.controller.componentName,
    userCmpt.UserListComponent
  );

let appRootEl = document.querySelector('#app-container') || document.body;

ng.bootstrap(appRootEl, [moduleName]);

This file is the root of the app and it’s where everything gets imported, pulled together and registered with angular. You will notice on the first line there is an import for angular import * as ng from "angular";.

Why is this imported here when Typescript considers ng to be a global object? Because our application has angular.js as a dependency and needs it at runtime to function correctly. This import, when combined with JSPM’s package management and mapping in our config.js file and SystemJs’s browser module loader polyfill we can write import statements like these and be reassured that our application will have everything, declare and initialize it and do this in the correct order when the browser loads the page. To state it a different way, the ng object we use in the component files is part of Typescript’s Typing system, which tells Typescript what things look like, whereas this import in main.ts is part of JSPM/ SystemJs’s module loading system, which tells the browser how to do the things of tomorrow today. We then import all the other parts of our app using relative paths and the ES2015 import syntax and import namespacing to group by module. Now that we have everything in one place we use Angular 1’s module API to register each piece to be used with dependency injection. Here again the .componentName and .serviceName static fields come in handy and turn code that would normally be full of strings into only functions, objects and properties.

Note that the imports for our modules are paths to index files that I haven’t discussed. These are ‘barrels’ which group up child module exports and re-export them up the hierarchy. If you take a look at the repository you can see how each folder in app has an index.ts. These barrels don’t have any logic of their own so they do not import, only export.

Finally we use Angular’s manual bootstrapping process to keep our index.html clean of any ng-app or ng-controller.

Side Note 1: Running, building and bundling

Config.js

If we take a quick look inside config.js, which is the configuration for the browser System module loader polyfill, we see a packages object.

packages: {
    "app": {
      "main": "main.ts",
      "defaultExtension": "ts",
      "meta": {
        "*.ts": {
          "loader": "ts"
        }
      }
    }
  },

This is what defines our main.ts (which is transpiled and loaded in the browser thanks to the SystemJs Typescript-loader) as the entry point for our application. More information can be found on JSPM and SystemJs. You can also see a map object with a bunch of library: path key-value pairs. JSPM manages this section of the file for when you type commands like jspm install angular. The "lodash" and "angular" strings here are the same identifiers we used when importing them into main.ts.

Typings

Everything in the /typings folder is what allows us to know what kinds of functions the angular and lodash objects will have available at runtime but with Typescript types. These libraries were not written in Typescript which is why they require separate type definition files ( .d.ts) which are placed in this /typings folder during an npm install, with one exception. The template.d.ts file is a manual addition that is required if we want to use the SystemJs text plugin and import the text content of our component templates directly into the component files. template.d.ts defines a pattern for modules that end in .html!text and states they all export a string - which is what our HTML templates are. This makes Typescript happy and lets us transpile to JavaScript without errors. Yay!

Index.html

The index.html is where our application runs and since we are using SystemJs for module loading in the browser we have to load up files a little differently than a traditional JavaScript web app.

<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>
  System.import('app').catch(function (err) {
    console.error(err);
  });
</script>

First we load system.js which is in the root of the /jspm_packages folder. This ensures the module loader polyfill is running in the browser before we try to load any modules. Then we load config.js which defines how all of our modules are mapped to the filesystem and to each other. Finally we call System.import("app") where "app" defines the name of the package we wish to load (if you scroll back up you can see it’s the name we give to the package with main.ts as its entrypoint. System.import is asynchronous, which allows it to fetch modules from a server on demand without blocking the page, but this means it also returns a promise. We .catch any errors that importing "app" might cause.

<body>
  <div id="app-container">
    <user-list></user-list>
  </div>
</body>

If you look back at main.ts you can see we bootstrap angular onto an element in the DOM with an id of "app-container" and here we have such an element with our root component in it.

Deployment Commands

In the package.json there is a list of scripts which can be run at the command line to run/build the app. The important ones are start and bundle:prod. Running npm start will begin the entire transpiling, file-change watching and web server running process. After everything is set a browser window will open up and the app will load. The app will be running off your local files and will be transpiled on the fly as you make changes. The get the app into a deployable state, run npm run bundle:prod. This will package the app and all of the 3rd party libraries into a single, minified production ready application script in the form of build.js in the root of the project folder. Copy the build.js to your server and add a script tag referencing it along with the #app-container div with the user-list element inside in the page being served. The build.js contains all 3rd party libraries installed via jspm and imported into your code ( /jspm_packages), our application ( /app), the package configuration ( config.js) and the module loader ( jspm_packages/system.js).

Conclusions?

I hope I’ve made a good case for Angular 1.5, Typescript and JSPM as a trifecta of tools. I’ve achieved really good mileage with them and I expect I will continue to use them for some time as I (and everyone else) eases into Angular 2. Since so much of Angular 2’s shift in approach is related to the change from previously being a framework based on MVC to now being a Component architecture one, making that same shift with Angular 1 can prepare you for Angular 2 but in an environment you are familiar with. Typescript is gaining in popularity and while not a requirement for Angular 2 it is definitely a must if you want to be as productive as possible with the framework. It also brings advantages when used in teams, larger projects and newer developers who (like myself) don’t trust themselves to remember the types or structure of all their variables and objects. There are many front end build systems these days but JSPM should not be overlooked. Its ability to help you rapidly build up an application, manage client side dependencies, stick to the ES2015 module spec and provide cool plugin and bundling options makes it an extremely useful tool. Check out the repository and let me know your thoughts and suggestions. Happy coding!