23

Here is a basic TypeScript/ES.next example that uses decorators for DI and follows the syntax suggested by the framework manual:

import {Component, Inject, Injectable, NgModule, OpaqueToken} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';

const CONSTANT = { value: 'constant' };
const CONSTANT_TOKEN = new OpaqueToken;
const CONSTANT_PROVIDER = { provide: CONSTANT_TOKEN, useValue: CONSTANT };

@Injectable()
class Service {
  constructor(@Inject(CONSTANT_TOKEN) constant) {
    console.log('Service constructor', constant);
  }
}

@Component({
  selector: 'app',
  template: '...',
  providers: [Service, CONSTANT_PROVIDER]
})
class AppComponent {
  constructor(@Inject(Service) service: Service, @Inject(CONSTANT_TOKEN) constant) {
    console.log('AppComponent constructor', service, constant);    
  }
}

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
class AppModule {}

platformBrowserDynamic().bootstrapModule(AppModule);

How would it be written in in ES5?

How would the same thing be done in untranspiled ES6/ES2015?

How are Injectable and Inject decorators translated in these cases?

The question particularly applies to real-world ES6 browser implementations that have classes but may use require or System.import instead of ES6 imports.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Why would you want to write it like that? Google is working on it. And their support is going to be for ES6. Just curious. – Arnold Balliu Aug 18 '16 at 20:28
  • 1
    @ArnoldB Babel/TS/Dart metalanguage workflow isn't suited well for every project. A2 development is obviously focused on TS and Dart for now, and I'm not really sure that raw JS will stop being a Cinderella after A2 release. It never hurts to know your options. – Estus Flask Aug 18 '16 at 21:03
  • I see. Well I for one love TS just because as a superset of JS you can really write any JS that is valid and it will run. But to focus more on your question, the "@Injectable" decorator does this : "@Injectable() marks a class as available to an injector for instantiation. Generally speaking, an injector will report an error when trying to instantiate a class that is not marked as @Injectable()". I would assume that to translate "@Injectable" you would need to translate the Injector they are talking about. – Arnold Balliu Aug 18 '16 at 21:22
  • @ArnoldB This is not exactly true, just have figured out the thing about `Injectable` a moment ago. It looks like it enables a class to use implicit injections through TS type annotations and is unnecessary for injectables that solely rely on `Inject` (like in JS). I guess I will post my own answer later. – Estus Flask Aug 18 '16 at 21:39

2 Answers2

6

To use Angular 2 with ES5 you need this script:

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/2.0.0-beta.3/angular2-all.umd.js"></script>

This provides an global variable that contains all of Angular 2. Now you can write ng.core.Component instead of the @Component annotation. The first parameters of the Constructor are the injectables.

var Component = ng.core
  Component({
    selector: 'hello-cmp',
    template: 'Hello World!',
    viewProviders: [Service]
  .Class({
    constructor: [Service, function (service) { 
      ...
    }],
  });

And tell the injector that our service parameter is a instance of Service

Component.parameters = [[new ng.core.Inject(Service)]];


The following Exapmle shows the usage of angular2 with ES6:

import {Component} from 'angular2/core';
import {Service} from './example.service';

let componentAnnotation = new Component({
  selector: 'world-time',
  inputs: ['timeZones'],
  providers: [Service],
  template: `
    ...
  `
});
export class ComponentExample {
   constructor(service) {
    this._service = service;

   }
...

}

WorldTimeComponent.annotations = [componentAnnotation];
WorldTimeComponent.parameters = [[Service]];

In this plunkr you can find a working ES6 example.

But you can use decorators by using Babel. Enabling the optional[]=es7.decorators (in webpack) or by setting your configuration to stage:1.

muetzerich
  • 5,600
  • 7
  • 37
  • 52
  • 1
    ES2015 doesn't include decorators, and the question is about using Angular 2 with untranspiled code. I've encountered this ES5 syntax before, but I'm not sure how it should be applied to ES6 classes. – Estus Flask Aug 09 '16 at 20:21
  • I updated my answer to make the differences between es5/es6 more clearly – muetzerich Aug 09 '16 at 20:37
  • Thanks. Please, consider the example from the question instead for consistency, `Service` is injected there with`constant` which may be an important detail. The example you're referring uses 2.0.0.beta-11, there was a bunch of breaking changes since then. Here's [a plunker with 'es6' TS target](http://plnkr.co/edit/XGWfKzP039y4C2JNuM6y?p=info) that follows the suggested syntax and it throws `"Can't resolve all parameters for Service: (?)`. – Estus Flask Aug 09 '16 at 23:41
  • That `angular2-all` script is 1.5 MB. Too much for a web application. – mohammad rostami siahgeli Jan 06 '18 at 05:26
2

Injectable decorator is specific to TypeScript flavour of Angular 2. It enables a class constructor to be implicitly annotated for DI through TypeScript type annotations. It is redundant in TS and unneeded in JS for injected dependencies that are annotated with Inject.

Angular 2 injectables (classes and constructor functions) are supposed to be annotated with annotations and parameters static properties under the hood.

annotations is an array that contains newed decorators for injectable class:

function SomeComponent(...) {}
SomeComponent.annotations = [new Componenent(...)];

parameters is an array that contains decorators for constructor parameters, each element is an array that contains a list of newed decorators for respective constructor property (similarly to $inject property explicit annotation in Angular 1.x):

function Service(someService, anotherService) {}
Service.parameters = [
  [new Inject(SomeService)],
  [new Inject(AnotherService), new Optional, new SkipSelf]
];

All class decorators are extended from TypeDecorator, meaning that they can be called as functions. In this case so-called DSL syntax is used that allows to chain a decorator with Class helper function:

var SomeComponent = Componenent(...).Class(...);

Class is also available separately, it constructs a new class from given definition object and allows to annotate constructor method with array (similarly to inline array explicit annotation in Angular 1.x):

var SomeService = Class({
  constructor: [[new Inject(SomeService)], function (someService) {}]
});

Class helper was deprecated in latest framework versions. It is supposed to be replaced with raw functions or third-party class helpers in ES5. Decorators support direct chaining with class functions, Componenent(...)(ComponentClass).

Angular 2/4 ES6 with System.import

An example:

Promise.all([
  System.import('@angular/core'),
  System.import('@angular/platform-browser'),
  System.import('@angular/platform-browser-dynamic')
])
.then(([
  {Component, Inject, Injectable, Optional, NgModule, OpaqueToken},
  {BrowserModule},
  {platformBrowserDynamic}
]) => {

  const CONSTANT = { value: 'constant' };
  const CONSTANT_TOKEN = new OpaqueToken;
  const CONSTANT_PROVIDER = { provide: CONSTANT_TOKEN, useValue: CONSTANT };

  class Service {
    constructor(constant) {}
  }
  Service.parameters = [[new Inject(CONSTANT_TOKEN)]];

  class AppComponent {
    constructor(service, constant) {}
  }
  AppComponent.annotations = [new Component({
    selector: 'app',
    template: '...',
    providers: [Service, CONSTANT_PROVIDER]
  })];
  AppComponent.parameters = [[new Inject(Service)], [new Inject(CONSTANT_TOKEN)]];

  class AppModule {}
  AppModule.annotations = [new NgModule({
    imports: [BrowserModule],
    declarations: [AppComponent],
    bootstrap: [AppComponent]
  })];

  platformBrowserDynamic().bootstrapModule(AppModule);

})
.catch((err) => console.error(err));

Angular 2/4 ES5 with UMD modules and ng global

An example:

var Class = ng.core.Class;
var Component = ng.core.Component;
var Inject = ng.core.Inject;
var Injectable = ng.core.Injectable;
var NgModule = ng.core.NgModule;
var OpaqueToken = ng.core.OpaqueToken;

var BrowserModule = ng.platformBrowser.BrowserModule;
var platformBrowserDynamic = ng.platformBrowserDynamic.platformBrowserDynamic;

var CONSTANT = { value: 'constant' };
var CONSTANT_TOKEN = new OpaqueToken;
var CONSTANT_PROVIDER = { provide: CONSTANT_TOKEN, useValue: CONSTANT };

// Class helper function that uses A1-flavoured inline array DI annotations
// and creates an annotated constructor
var Service = Class({
  constructor: [[new Inject(CONSTANT_TOKEN)], function (constant) {
    console.log('Service constructor', constant);
  }]
});
// can also be
// function Service(constant) {};
// Service.parameters = [[new Inject(...)], ...];

// when not being `new`ed, Component is a chainable factory that has Class helper method
var AppComponent = Component({
  selector: 'app', 
  template: '...',
  providers: [Service, CONSTANT_PROVIDER]
})
.Class({
  constructor: [
    [new Inject(Service)],
    [new Inject(CONSTANT_TOKEN)],
    function (service, constant) {
      console.log('AppComponent constructor', service, constant);
    }
  ]
});
// can also be
// function AppComponent(...) {};
// AppComponent.annotations = [new Component(...)];
// AppComponent.parameters = [[new Inject(...)], ...];

var AppModule = NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
.Class({ constructor: function () {} });
// can also be
// function AppModule() {};
// AppModule.annotations = [new NgModule(...)];

platformBrowserDynamic().bootstrapModule(AppModule);
Estus Flask
  • 206,104
  • 70
  • 425
  • 565