Angular 2 Dependency Injection in Es5 and Es6

Angular 2 dependency injection in ES5 and ES6

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);

Angular 5 : How to inject Router service with ES6/ES5 (without Typescript)

Injectable is for TypeScript only, it uses emitted parameter types to annotate injectable class for DI.

Classes should be annotated with static parameter property in ES5 and ES6, as shown in this answer:

class HomeComponent {
constructor(service, router) {
this.message = service.toUpperCase('hello');
}
}
HomeComponent.parameters = [
[new ng.core.Inject(Service)],
[new ng.core.Inject(ng.router.Route)]
];

A problem with this example is that Router should be global provider, it shouldn't be defined in component providers. Here's an example.

create an angular 2 service in es5

Here is a complete sample of dependency injection with ES5. (service into component, service into service). Don't forget to specify your service when bootstrapping your application or within the providers attribute of components.

var OtherService = function() {};
OtherService.prototype.test = function() {
return 'hello';
};

var Service = ng.core.Injectable().Class({
constructor: [ OtherService, function (service) {
this.service = service;
}],

test: function() {
return this.service.test();
}
});

var AppComponent = ng.core
.Component({
selector: 'my-app',
template: '<div>Test: {{message}}</div>',
})
.Class({
constructor: [Service, function (service) {
this.message = service.test();
}],
});

document.addEventListener('DOMContentLoaded', function () {
ng.platform.browser.bootstrap(AppComponent, [
OtherService, Service
]);
});

In your case, I think that your forgot to add app.database in providers. Something like:

document.addEventListener('DOMContentLoaded', function () {
ng.platform.browser.bootstrap(AppComponent, [
app.database
]);
});

You could also have a look at this question:

  • Dependency Injection in Angular 2 with ES5

Migration to angular 2 - ES6 or TypeScript?

Typescript isn't really about OOP. OOP is orthogonal to types (think Java vs. Smalltalk). Typescript is about static type verification: are you using a string where you think you're using an array? I actually fixed a bug in a co-worker's code along those lines just last Friday where he was using a for loop over the length of what he thought was an array (Array.prototype.forEach makes that an easy to find error).

Is static type-checking worth the effort of adding type annotations all over your codebase? That's a judgement call.

ES6/ESNext on the other hand just flat-out offers you better ways to write code. I don't see how anyone can make the argument that

function(arr) {
var foo = arr[0];
var bar = arr[1];
return foo + bar;
}

is better than

([foo, bar]) => foo + bar

Same for lots of other features, if you write in a functional style your codebase (like mine) is probably littered with

Object.keys(someObj)
.map(k => someObj[k])
.filter...

Now you just have Object.values. Plus about a million other features.

How to use angular2 DynamicComponentLoader in ES6?

If you want to write code in ES7, I think the most concise approach to specify injections at this time is to use static getter for parameters:

import {Component, View, DynamicComponentLoader, ElementRef } from 'angular2/angular2'

@Component({
selector: 'my-app'
})
@View({
template: '<div #container></b>'
})
export class App {

static get parameters() {
return [[DynamicComponentLoader], [ElementRef]];
}

constructor(dynamicComponentLoader, elementRef) {
dynamicComponentLoader.loadIntoLocation(DynamicComponent, elementRef, 'container');
}
}

See this plunker

If you want to write code in ES6, which doesn't support decorators, you must also use static getter for annotations property. In this case you must import ComponentMetadata and ViewMetadata instead of Component and View For example:

import {ComponentMetadata, ViewMetadata, DynamicComponentLoader, ElementRef } from 'angular2/angular2';

export class App {

static get annotations() {
return [
new ComponentMetadata({
selector: 'app'
}),
new ViewMetadata({
template: '<div #container></b>'
})
];
}

static get parameters() {
return [[DynamicComponentLoader],[ElementRef]];
}

constructor(dynamicComponentLoader, elementRef) {
dynamicComponentLoader.loadIntoLocation(DynamicComponent, elementRef, 'container');
}
}

See this plunker

typescript/eS5-ES6 module is not getting called

To be able to create and use FirstComponent you must decorate it.
It means that you add @Component above the FirstComponent class, it will tell Angular to add meta-data to it.

first.ts:

@Component({
selector: <f-comp></f-comp>, // Here specify a CSS selector used to create the FirstComponent component.
template: `
<p>This is the first component</p>
`
})
export class FirstComponent {
...
}

To be created you must call the FirstComponent inside the app.ts file.

app.ts:

@Component({
directive: [ FirstComponent ], // Here you tell Angular that you are going to use FirstComponent.
...
template: `
...
// here you must put the FirstComponent CSS selector --> <f-comp></f-comp>
`
})

I create a plnkr where I explain every steps and how they works.
http://plnkr.co/edit/CKZ2l9WqMljXM1FO0IoF?p=preview

How to inject upgraded Angular 1 service/factory to Angular 2 component in ES5?

To complete the pixelbits' answer, you need also define my service within the providers list either:

  • At the application level

    document.addEventListener('DOMContentLoaded', function() {
    ng.platform.browser.bootstrap(Cmp, [MyService]);
    });
  • At the component level

    var Cmp = ng.core.
    Component({
    selector: 'cmp',
    providers: [ MyService ]
    }).
    (...)
    Class({
    constructor: [MyService, function(service) {
    }]
    });

It depends on what you want to do: share your service instance for all elements in the Angular2 application or per component.

This answers could give more details on dependency injection in Angular2 with ES5: Dependency Injection in Angular 2 with ES5.

Edit

In fact, there is a subtlety. IN the case of upgrade you need not to bootstrap using the ng.platform.browser.bootstrap function but the one from the upgrade object.

upgrade.bootstrap(document.body, ['heroApp']);

Where heroApp is the Angular1 module containing services and factories I want to use in the Angular2 application.

In fact, when you call the upgradeNg1Provider method on the upgrade, corresponding providers are registered within its associated injector. This means that you don't need to specify them at described above.

So you simply need to do that where you want to inject:

(...)
Class({
constructor: [ ng.core.Inject('customService'),
ng.core.Inject('customFactory'),
function(cService, cFactory) {
}]
});

Miško Hevery provides a great plunkr for this: http://plnkr.co/edit/yMjghOFhFWuY8G1fVIEg?p=preview.

Hope it helps you,
Thierry

How to bootstrap an angular2 component from ES5?

Angular modules were introduced in Angular 2.0.0 RC5 as a direct successor of AngularJS modules.

As it's shown in this ES5 example, it is:

platformBrowserDynamic().bootstrapModule(AppModule);

And the component that is supposed to be bootstrapped should have a respective module defined:

var AppModule = NgModule({
declarations: [AppComponent],
bootstrap: [AppComponent]
})
.Class({ constructor: function () {} });


Related Topics



Leave a reply



Submit