Angular

Writing Angular code in ES5

It’s no news anymore that Angular 2.x was written in TypeScript in order to take advantage of language features like types and meta data annotations through decorators. Taking a first look at Angular examples that are written in TypeScript, can feel a bit unfamiliar and unclear to developers that don’t have experience with that language. Even constructs like classes that ECMAScript 2015 brings to the table can be scary enough to keep developers from learning Angular.

That’s why developers with more experience will tell us that we don’t have to write TypeScript or just ES6 if we don’t want to. We can just stick with ES5. Cool, fine. But how do we do that? In one of our last articles we’ve explored the difference between annotations and decorators and to what they translate to in ES5.

In this article, we will use that information, to actually write Angular code in ES5 with the latest version released at the time of writing (2.x).

Getting started with Angular in ES5

If you’ve read our article on building a zippy component in Angular, you might know that nowadays, there’s quite a bit of work to do, in order to get started if you want to write your application in ES6/TypeScript and take advantage of it’s module system.

In ES5 we don’t have a module system yet. So ideally, we should be able to just take a JavaScript file from somewhere, that has all the Angular code in it, so we can embed it in our website. We don’t have to care about transpiling, concatenating, deciding on a module system (AMD, Common, System, …), or anything else. We can just fetch a bundled file that comes with the ready-to-use code.

The easiest way to get hold of Angular ES5 bundles is npmcdn. Here’s what we need to embed to get started with ES5 and Angular:

<script src="https://npmcdn.com/@angular/[email protected]/bundles/core.umd.js"></script>
<script src="https://npmcdn.com/@angular/[email protected]/bundles/common.umd.js"></script>
<script src="https://npmcdn.com/@angular/[email protected]/bundles/compiler.umd.js"></script>
<script src="https://npmcdn.com/@angular/[email protected]/bundles/platform-browser.umd.js"></script>
<script src="https://npmcdn.com/@angular/[email protected]/bundles/platform-browser-dynamic.umd.js"></script>

Whereas @2.0.0-rc.5 is the version number we specify. So that part might change depending on what we want to do.

Now the next question comes up: How can we access and use given annotations and/or decorators? Usually, in ES2015, we would import them from the framework but now there’s no way for us to import them.

Well, it turns out that the bundled version exposes an ng.core object on the current global scope or reuses an existing one, which has all annotations added to it. In our last article we’ve learned that annotations are just classes, which in the end are just functions. And those functions are called as constructor functions to add meta data to our components. That means, all we have to do is to call those annotation constructors manually and assign them to our component’s annotations property.

Let’s start off with a simple component that has a template:

var HelloComponent = function () {

};

HelloComponent.annotations = [
  new ng.core.Component({
    selector: 'hello-cmp',
    template: 'Hello World!'
  })
];

That’s it. We have a constructor function that has a component annotation and a view annotation. The TypeScript equivalent would look something like this:

@Component({
  selector: 'hello-cmp',
  template: 'Hello World!'
})
class HelloComponent {

}

Bootstrapping an Angular app in ES5

When we come to the point that we want to bootstrap our application, we need to define an NgModule that has everything attached to it that is needed to make our app run, and bootstrap it on a dedicated platform (e.g. browser, webworker or server).

Let’s go ahead and create such a module first. ng.core.NgModule can be used to create the needed metadata on a constructor function. Just like with our HelloComponent, we create an AppModule function like this:

var AppModule = function () {

};

Next, we add NgModule annotations to it:

AppModule.annotations = [
  new ng.core.NgModule({
    imports: [ng.platformBrowser.BrowserModule],
    declarations: [HelloComponent],
    bootstrap: [HelloComponent]
  })
];

Similar to TypeScript world, we have to import the BrowserModule from the ng.platformBrowser package, so we can bootstrap our module in the browser environment. Next, we declare all directives and components that are used inside this module, which in our case, is only the HelloComponent. Last but not least, we tell Angular which component to bootstrap when the module is bootstrapped.

We need to make sure that all of the DOM is loaded before we bootstrap our module. Adding an event listener for the DOMContentLoaded event and call bootstrap once triggered, will help here. When the event is fired, we can call platformBrowserDynamic().bootstrapModule(AppModule) to bootstrap our app:

document.addEventListener('DOMContentLoaded', function () {
  ng.platformBrowserDynamic
    .platformBrowserDynamic().bootstrapModule(AppModule);
});

And of course, the corresponding application template looks like this:

<body>
  <hello-component></hello-component>
</body>

Great we’ve just bootstrapped our Angular application written in ES5! Was it that hard?

Injecting services in ES5

Let’s say we want to add a GreetingService to our component. The @Component annotation takes a property viewProviders to define injectable types for this particular component. This is easy to add. First we build the service. A service in Angular can be just a class, which translates to just a function, which is also just an object in JavaScript.

var GreetingService = function () {}

GreetingService.prototype.greeting = function () {
  return 'Hello';
};

Next we tell our component about it’s injectable types:

HelloComponent.annotations = [
  new ng.Component({
    selector: 'hello-cmp',
    providers: [GreetingService]
  }),
  ...
];

This basically tells our component that it should return an instance of GreetingService when somebody asks for GreetingService. Nobody asked for it yet, so let’s change that. First we need something we want to inject into our component’s constructor:

var HelloComponent = function (greetingService) {
  this.greeting = greetingService.greeting();
};

To make our component explicitly ask for something that is a GreetingService, or in other words, to tell the injector that our greetingService parameter should be an instance of GreetingService, we need to add a parameter annotation accordingly:

HelloComponent.parameters = [[new ng.core.Inject(GreetingService)]];

If you wonder why we define a nested array, this is because one constructor parameter can have more than one associated annotation.

Cool, so it turns out that writing Angular code is actually not weird at all. In addition to that, it kind of gets clear that writing Angular code in ES5 requires more typing. But again, in the end it’s up to the application author which language or transpiler to use.

There’s even a better syntax, that makes writing and reading Angular code a breeze.

Check out the demos below!

Written by  Author

Pascal Precht