I am playing around with Angular2. As a basis, I used the quickstart project from the angular.io page.
Everything seems to work fine, but as soon as I try to inject a service (ItemService
) into my AppComponent
I get following exception:
Error during instantiation of Token(ComponentRef)!. ORIGINAL ERROR: Cannot resolve all parameters for AppComponent. Make sure they all have valid type or annotations.
I have seen similar issues on the internet (including stackoverflow, for example this post), but none of them seem to solve my problem. Does any body have any idea, what the problem could be?
I have also seen some solutions (for example the ones in the Angular2 repo) which decorate the injectable class with the Injectable
-annotation. However this doesn't work for me, as it is not defined in angular.d.ts
. Am I using a wrong version?
You can find my solution in following Plunker: http://plnkr.co/edit/7kK1BtcuEHjaspwLTmsg
For the record, you can also find my two files below. Please note that the app.js
from the Plunker is the JavaScript file generated out of my TypeScript file below, the exception is always the same.
index.html
:
<html>
<head>
<title>Testing Angular2</title>
<script src="https://github.jspm.io/jmcriffey/[email protected]/traceur-runtime.js"></script>
<script src="https://jspm.io/[email protected]"></script>
<script src="https://code.angularjs.org/2.0.0-alpha.23/angular2.dev.js"></script>
</head>
<body>
<app></app>
<script>
System.import('js/app');
</script>
</body>
</html>
js/app.ts
:
/// <reference path="../../typings/angular2/angular2.d.ts" />
import {Component, View, bootstrap, For, If} from "angular2/angular2";
class Item {
id:number;
name:string;
constructor(id:number, name:string) {
this.id = id;
this.name = name;
}
}
class ItemService {
getItems() {
return [
new Item(1, "Bill"),
new Item(2, "Bob"),
new Item(3, "Fred")
];
}
}
@Component({
selector: 'app',
injectables: [ItemService]
})
@View({
template: `<h1>Testing Angular2</h1>
<ul>
<li *for="#item of items">
{{item.id}}: {{item.name}} |
<a href="javascript:void(0);" (click)="toggleSelected(item);">
{{selectedItem == item ? "unselect" : "select"}}
</a>
</li>
</ul>
<item-details *if="selectedItem" [item]="selectedItem"></item-details>`,
directives: [For, If, DetailComponent]
})
class AppComponent {
items:Item[];
selectedItem:Item;
constructor(itemService:ItemService) {
this.items = itemService.getItems();
}
toggleSelected(item) {
this.selectedItem = this.selectedItem == item ? null : item;
}
}
@Component({
selector: 'item-details',
properties: {
item: "item"
}
})
@View({
template: `<span>You selected {{item.name}}</span>`
})
class DetailComponent {
item:Item;
}
bootstrap(AppComponent);
Thanks in advance for any ideas!
The @Injectable() decorator defines a class as a service in Angular and allows Angular to inject it into a component as a dependency. Likewise, the @Injectable() decorator indicates that a component, class, pipe, or NgModule has a dependency on a service. The injector is the main mechanism.
Injectors are data structures that store instructions detailing where and how services form. They act as intermediaries within the Angular DI system. Module, directive, and component classes contain metadata specific to injectors. A new injector instance accompanies every one of these classes.
Dependency injection is simply a way of making a class independent of its own dependencies. It allows you to separate different parts of your application in a more maintainable way, because every class can make calls to any dependency it needs.
We use the @Inject parameter decorator to instruct Angular we want to resolve a token and inject a dependency into a constructor. We use the @Injectable class decorators to automatically resolve and inject all the parameters of class constructor.
Check for couple of things:-
1) Make sure you are using the correct version of typescript (1.5.x) installed from your package location. () If you have previous versions of typescript installed then merely using "tsc" command could be pointing to the existing (< 1.5) location from the environment path.
2) Make sure you use --emitDecoratorMetadata
flag with the tsc command. It is necessary so the javascript output creates the metadata for the decorators.
3) Error - Cannot read property 'annotations' of undefined
Because AppComponent
directive has a dependency on DetailComponent
which has not been defined yet (by the time the synchronous __decorate(
function runs to resolve the dependencies) and the way TS compiles the hoisted variable DetailComponent
is undefined then (The IIFE for that below is yet to run). Try moving the declaration of DetailComponent
before AppComponent
. Or just move it to a different file and import it.
@Component({
selector: 'item-details',
properties: {
item: "item"
}
})
@View({
template: `<span>You selected {{item.name}}</span>`
})
class DetailComponent {
item:Item;
}
@Component({
selector: 'app',
injectables: [ItemService]
})
@View({
template: `<h1>Testing Angular2</h1>
<ul>
<li *for="#item of items">
{{item.id}}: {{item.name}} |
<a href="#" (click)="toggleSelected(item);">
{{selectedItem == item ? "unselect" : "select"}}
</a>
</li>
</ul>
<item-details *if="selectedItem" [item]="selectedItem"></item-details>`,
directives: [For, If, DetailComponent]
})
class AppComponent {
items:Item[];
selectedItem:Item;
constructor(itemService:ItemService) {
this.items = itemService.getItems();
}
toggleSelected(item) {
this.selectedItem = this.selectedItem == item ? null : item;
return false;
}
}
bootstrap(AppComponent);
Edit
As of angular a26 if you face issues with respect to following the current documentation (as of now) follow this link if it helps as there are some relevant changes.
My setup: I am running WebStorm 10.0.2 on Windows, using TypeScript 1.5 beta (compiled to ES5) and node/npm.
Thanks to PSL's help, I found several problems with my setup. They were not related to the code, e.g. not to the order of the class definitions, etc. Just for the record I would like to summarize the issues I had.. Most of them have been mentioned by PSL, but maybe other people have the same problems, e.g. related to the compiler in WebStorm.
Here are the problems I had:
Even though I configured WebStorm to use TypeScript 1.5 installed via npm, an old version (1.3) was used, as this was also installed on my system (I think together with Visual Studio) and registered in the %path%
variable
I configured WebStorm to use a "Custom Compiler Version" for TypeScript (in settings/Languages and Frameworks/TypeScript), with following arguments: -emitDecoratorMetadata -m commonjs -t es5 -sourceMap
. This some how doesn't work.. I'm not sure, if it is related to an issue in WebStorm. Without the -emitDecoratorMetadata
flag it compiled, but the injectables
didn't work.
[Update, 9. June 2015: Yes, it is related to an issue in WebStorm]
However I need the -emitDecoratorMetadata
-flag in order for the injectables
in Angular2 to work.
My solution was to add a custom "File Watcher" in WebStorm (instead of using the "builtin Compiler"-function) with following arguments: -emitDecoratorMetadata -m commonjs -t es5 --sourceMap $FilePath$
Lessons learned when working with Angular2, TypeScript 1.5 and WebStorm:
Ensure TypeScript 1.5 is really the version which is used to compile the ts files (e.g. in your IDE).
Ensure the -emitDecoratorMetadata
is specified.
It doesn't make a difference, if the classes are defined in a specific order or in own files, etc. -> Order does matter! Splitting the classes/components into different files solves the problem and generally leads to better maintanable files.
Thanks again to PSL for his valuable input!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With