Angular 2 is already available though there are a lot of code and libraries that are still in Angular 1.x. Here we outline how to write AngularJS 1.x in the modern javascript.
Prerequisites: EcmaScript 2015, javascript decorators, AngularJS 1.x. No knowledge of Angular 2.0 is required.
Please note that decorators we have introduced, while resemble those from Angular 2, do not match them exactly.
A sample uses nodejs, npm and gulp as a building pipeline. In addition we have added Visual Studio Nodejs project, and maven project.
Build pipeline uses Babel with ES 2015 and decorator plugins to transpile sources into javascript that today's browsers do support. Babel can be replaced or augmented with Typescript compiler to support Microsoft's javascript extensions. Sources are combinded and optionally minified into one or more javascript bundles. In addition html template files are transformed into javascript modules that export a content of html body as a string literals. In general all sources are in src folder and the build's output is assembled in the dist folder. Details of build process are in gulpfile.js
So, let's introduce an API we have defined in angular-decorators.js module:
Component(name, options?)
Controller(name)
Directive(name, options?)
Injectable(name)
Module(name, ...require)
Pipe(name, pure?)
Component's and Directive's options is the same object passed into Module.component(), Module.directive() calls with difference that no options.bindings, options.scope, options.require is specified. Instead @Attribute(), @Input(), @Output(), @TwoWay(), @Collection(), @Optional() are used to describe options.bindings, and @Host(), Self(), SkipSelf(), @Optional() are used to describe options.require
Module.component(), Module.directive()
options.bindings
options.scope
options.require
@Attribute(), @Input(), @Output(), @TwoWay(), @Collection(), @Optional()
@Host(), Self(), SkipSelf(), @Optional()
Every decorated class can use @Inject() member decorator to inject a service.
@Inject()
Attribute(name?)
BindThis()
this
Collection()
Host(name?)
HostListener(name?)
Inject(name?)
Input(name?)
Optional()
Output(name?)
Self(name?)
SkipSelf(name?)
TwoWay()
If optional name is omitted in the member decorator then property name is used as a name parameter. @Host(), @Self(), @SkipSelf() accept class decorated with @Component() or @Directive() as a name parameter.
@Host(), @Self(), @SkipSelf()
@Component()
@Directive()
@Inject() accepts class decorated with @Injectable() or @Pipe() as a name parameter.
@Injectable()
@Pipe()
modules(...require)
Now we can start with samples. Please note that we used samples scattered here and there on the Anuglar site.
@Component(), @SkipSelf(), @Attribute()
myTabs
myPane
import { Component } from "../angular-decorators"; // Import decorators import template from "../templates/my-tabs.html"; // Import template for my-tabs component @Component("myTabs", { template, transclude: true }) // Decorate class as a component export class MyTabs // Controller class for the component { panes = []; // List of active panes select(pane) // Selects an active pane { this.panes.forEach(function(pane) { pane.selected = false; }); pane.selected = true; } addPane(pane) // Adds a new pane { if (this.panes.length === 0) { this.select(pane); } this.panes.push(pane); } }
import { Component, Attribute, SkipSelf } "../angular-decorators"; // Import decorators import { MyTabs } from "./myTabs"; // Import container's directive. import template from "../templates/my-pane.html"; // Import template. @Component("myPane", { template, transclude: true }) // Decorate class as a component export class MyPane // Controller class for the component { @SkipSelf(MyTabs) tabsCtrl; //Inject ancestor MyTabs controller. @Attribute() title; // Attribute "@" binding. $onInit() // Angular's $onInit life-cycle hook. { this.tabsCtrl.addPane(this); console.log(this); }; }
import { Component, Input, Output } from "../angular-decorators"; import template from "../templates/heroDetail.html"; @Component("heroDetail", { template }) // Decorate class as a component export class HeroDetail // Controller class for the component { @Input() hero; // One way binding "<" @Output() onDelete; // Bind expression in the context of the parent scope "&" @Output() onUpdate; // Bind expression in the context of the parent scope "&" delete() { this.onDelete({ hero: this.hero }); }; update(prop, value) { this.onUpdate({ hero: this.hero, prop, value }); }; }
@Directive(), @Inject(), @Input(), @BindThis()
myCurrentTime
import { Directive, Inject, Input, BindThis } from "../angular-decorators"; // Import decorators @Directive("myCurrentTime") // Decorate MyCurrentTime class as a directive export class MyCurrentTime // Controller class for the directive { @Inject() $interval; // "$interval" service is injected into $interval property @Inject() dateFilter; // "date" filter service is injected into dateFilter property @Inject() $element; // "$element" instance is injected into $element property. @Input() myCurrentTime; // Input one way "<" property. timeoutId; // updateTime is adapted as following in the constructor: // this.updateTime = this.updateTime.bind(this); @BindThis() updateTime() { this.$element.text(this.dateFilter(new Date(), this.myCurrentTime)); } $onInit() // Angular's $onInit life-cycle hook. { this.timeoutId = this.$interval(this.updateTime, 1000); } $onDestroy() // Angular's $onDestroys life-cycle hook. { this.$interval.cancel(this.timeoutId); } $onChanges(changes) // Angular's $onChanges life-cycle hook. { this.updateTime(); } }
@Directive(), @Inject(), @HostListener(), @BindThis()
myDraggable
import { Directive, Inject, HostListener, BindThis } from "../angular-decorators"; // Import decorators @Directive("myDraggable") // Decorate class as a directive export class MyDraggable // Controller class for the directive { @Inject() $document; // "$document" instance is injected into $document property. @Inject() $element;// "$element" instance is injected into $element property. startX = 0; startY = 0; x = 0; y = 0; // Listen mousedown event over $element. @HostListener() mousedown(event) { // Prevent default dragging of selected content event.preventDefault(); this.startX = event.pageX - this.x; this.startY = event.pageY - this.y; this.$document.on('mousemove', this.mousemove); this.$document.on('mouseup', this.mouseup); } @BindThis() mousemove(event) // bind mousemove() function to "this" instance. { this.y = event.pageY - this.startY; this.x = event.pageX - this.startX; this.$element.css({ top: this.y + 'px', left: this.x + 'px' }); } @BindThis() mouseup() // bind mouseup() function to "this" instance. { this.$document.off('mousemove', this.mousemove); this.$document.off('mouseup', this.mouseup); } $onInit() // Angular's $onInit life-cycle hook. { this.$element.css( { position: 'relative', border: '1px solid red', backgroundColor: 'lightgrey', cursor: 'pointer' }); } }
@Injectable(), @Inject()
notifier
import { Inject, Injectable } from "../angular-decorators"; // Import decorators @Injectable("notifier") // Decorate class as a service export class NotifierService { @Inject() $window; // Inject "$window" instance into the property msgs = []; notify(msg) { this.msgs.push(msg); if (this.msgs.length === 3) { this.$window.alert(this.msgs.join('\n')); this.msgs = []; } } }
reverse
import { Pipe } from "../angular-decorators"; // Import decorators @Pipe("reverse") // Decorate class as a filter export class ReverseFilter { transform(input, uppercase) // filter function. { input = input || ''; var out = ''; for(var i = 0; i < input.length; i++) { out = input.charAt(i) + out; } // conditional based on optional argument if (uppercase) { out = out.toUpperCase(); } return out; } }
import { angular, modules, Module } from "../angular-decorators"; // Import decorators import { MyController } from "./controllers/myController"; // Import components. import { HeroList } from "./components/heroList"; import { HeroDetail } from "./components/heroDetail"; import { EditableField } from "./components/editableField"; import { NotifierService } from "./services/notify"; import { MyTabs } from "./components/myTabs"; import { MyPane } from "./components/myPane"; import { ReverseFilter } from "./filters/reverse"; import { MyCurrentTime } from "./directives/myCurrentTime"; import { MyDraggable } from "./directives/myDraggable"; @Module( // Decorator to register angular module, and refer to other modules or module components. "my-app", [ MyController, NotifierService, HeroList, HeroDetail, EditableField, MyTabs, MyPane, ReverseFilter, MyCurrentTime, MyDraggable ]) class MyApp { } // Manual bootstrap, with modules() converting module classes into an array of module names. angular.bootstrap(document, modules(MyApp));
Please see angular-decorators.js to get detailed help on decorators.