Injectables in Angular2

2020-04-02 07:06发布

问题:

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/bower-traceur-runtime@0.0.87/traceur-runtime.js"></script>
        <script src="https://jspm.io/system@0.16.js"></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!

回答1:

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.



回答2:

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!



回答3:

I had faced the same problem. Below were the resolution:

  1. --emitDecoratorMetadata was missed in tsc
  2. If I was injecting service from same file it was failing, but when I put the service is some other file and when imported that, it started working. probably bug in Angular2 version I was using.