17

Every time I generate something in my shared folder the index.ts file is rebuilt and exports are put in alphabetical order. This seems to break dependencies for me. Changing order manually so that dependencies is exported before classes with the dependencies makes it work again.

If we have app/shared/auth.guard.ts:

import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';

import { AuthService, User } from './';

@Injectable()
export class AuthGuard implements CanActivate {

    constructor(private accountService: AuthService, private router: Router) { }

    canActivate(next: ActivatedRouteSnapshot): Observable<boolean> {
        let result = this.accountService.currentUser.first().map(user => user != null);

        let route: any[] = ['/login'];

        if (next.url.length) {
            route.push({ redirectUrl: next.url });
        }

        result.subscribe(isLoggedIn => {
            if (!isLoggedIn) {
                this.router.navigate(route);
            }
        });

        return result;
    }
}

and app/shared/account.service.ts:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

import { User } from './';

const LOCAL_STORAGE_KEY = 'currentUser';

@Injectable()
export class AuthService {
  private currentUserSubject: BehaviorSubject<User>;

  constructor() {
    this.currentUserSubject = new BehaviorSubject<User>(this.getUserFromLocalStorage())
    this.currentUserSubject.subscribe(user => this.setUserToLocalStorage(user));
  }

  logIn(userName: string, password: string) : Observable<User> {
    this.currentUserSubject.next({
      id: userName,
      userName: userName,
      email: userName
    });

    return this.currentUser.first();
  }

  logOut() {
    this.currentUserSubject.next(null);
  }

  get currentUser(): Observable<User> {
    return this.currentUserSubject.asObservable();
  }

  private getUserFromLocalStorage(): User {
    let userString = localStorage.getItem(LOCAL_STORAGE_KEY);

    if (!userString) {
      return null;
    }

    return JSON.parse(userString);
  }

  private setUserToLocalStorage(user: User) {
    if (user) {
      localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(user));
    }
    else {
      localStorage.removeItem(LOCAL_STORAGE_KEY);
    }
  }

}

This doesn't work:

export * from './auth.guard';
export * from './auth.service';

Unhandled Promise rejection: Error: Cannot resolve all parameters for 'AuthGuard'(undefined, Router). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'AuthGuard' is decorated with Injectable.

This works:

export * from './auth.service';
export * from './auth.guard';

From what I noticed this doesn't apply to all. For example can my user model be exported after auth service and it works fine.

I wish I didn't have to change this manually every time. Is there a workaround available? Could I structure the files in a different way?

Dependencies from package.json:

"@angular/common": "^2.0.0-rc.2",
"@angular/compiler": "^2.0.0-rc.2",
"@angular/core": "^2.0.0-rc.2",
"@angular/forms": "^0.1.0",
"@angular/http": "^2.0.0-rc.2",
"@angular/platform-browser": "^2.0.0-rc.2",
"@angular/platform-browser-dynamic": "^2.0.0-rc.2",
"@angular/router": "^3.0.0-alpha.7",
"bootstrap": "^3.3.6",
"es6-shim": "0.35.1",
"moment": "^2.13.0",
"ng2-bootstrap": "^1.0.17",
"reflect-metadata": "0.1.3",
"rxjs": "5.0.0-beta.6",
"slideout": "^0.1.12",
"systemjs": "0.19.26",
"zone.js": "0.6.12"

devDependencies:

"angular-cli": "1.0.0-beta.6"
mollwe
  • 2,185
  • 2
  • 18
  • 17
  • Removing AuthService from AuthGuard constructor makes the error go away. So it seems to have something to do with injection. – mollwe Jun 19 '16 at 00:43
  • changing import to `import { AuthService } from './auth.service'; import { User } from './';` is a possible workaround for it – mollwe Jun 19 '16 at 00:46

3 Answers3

36

This is an issue with ordering of exports in barrels. It has been reported in the angular repo here: https://github.com/angular/angular/issues/9334

There are three workarounds:

Change the ordering of exports in your barrels

Change the ordering so that module dependencies are listed before their dependants.

In this example, AuthGuard depends on AuthService. AuthService is a dependency of AuthGuard. Therefore, export AuthService before AuthGuard.

export * from './auth.service';
export * from './auth.guard';

Don't use barrels at all.

This is not recommended since it means more imports are required.

In this example, you would import AuthService from its file rather than a barrel.

import { AuthService } from './auth.service';
import { User } from './';

Use the systemJS module format rather than commonJS

Change the typescript compiler options to compile to the SystemJS format rather than commonJS. This is done by changing tsconfig.json's compilerOptions.module from commonjs to system.

Note that when you change that configuration, you'll need to update the moduleId properties of all your component decorators from module.id to __moduleName and declare it in typings.d.ts as follows:

declare var __moduleName: string;

This module format is not the default in the Angular-CLI tool (official build tool created by the Angular team) and so may not be recommended or supported.


Note: I'm personally not satisfied with any of the workarounds.

Michael
  • 5,994
  • 7
  • 44
  • 56
  • Thank you for your answer and directing me to bug report, I missed that. Tried using system instead of commonjs and it built just fine but I get `Unhandled Promise rejection: ReferenceError: __moduleName is not defined` when trying to run it in browser. Am I doing something wrong? – mollwe Jun 19 '16 at 22:44
  • That would most likely indicate that some or all of your files have not been built using `system`. If you browse the compiled `*.js` files in your dist directory, the files should start with `System.register(...` and you should be able to see the `__moduleName` variable being declared a bit further down. Re-run an `ng serve`/`ng build` and see if that fixes it. – Michael Jun 20 '16 at 02:58
  • Sorry, but what do you mean by barrel? I'm having the same problem. I have a service and it works when injected in one component, but not when injected in other. The two components are at the same level, but the service is two directories behind. This is the import I have in the components: import { ViewNavigatorService } from './../../services/viewnavigator'; and this is the error: ORIGINAL EXCEPTION: Can't resolve all parameters for SubmenuPage: (NavController, NavParams, PopoverController, AlertController, ModalController, ?). I get a (?) when it should be the service. – hsantos Sep 22 '16 at 09:26
5

I had this issue and this was caused by circular reference. Explanations : within the 'provider service class' I had a reference to a UI page that the constructor referenced this same service causing the circular reference...

import { MyUIPage } from "../pages/example/myuipage";

So what I had to do, is remove the reference from the service and built a function receiving a callback. No need to reference the UI page from the service and error goes away.

public setCallBackSpy(callback)
{
   this.callBackSpy = callback;
}

In the app.component class constructor, the one referencing the service, I simply set the link to the callback function like below.

this.servicingClass.setCallBackSpy(this.myCallBackFunctionUsingUIPage);

Hope that helps, my first answer ever :)

Floydus
  • 81
  • 1
  • 6
1

You can also get this error in Angular2 live and suggests that angular doesn't want to instantiate a service that has already been instantiated. It can happen if you "mis-"include the service e.g. ActivatedRoute from '@angular/router' in the providers property of the @Component class decorator despite having already included the RouterModule in the App.module file, which anyhow registers all router providers and directives for the App.

Kieran Ryan
  • 593
  • 2
  • 8
  • 18