9

I have an Angular 6.1 application, which imports some external module.

When I'm compiling the application in AOT mode:

$ ng build --aot

I'm getting this error:

ERROR in ./src/app.component.ngfactory.js
Module not found: Error: Can't resolve '/home/user/project/node_modules/@acme/library/src/library/library' in '/home/user/project/src/app'
resolve '/home/user/project/node_modules/@acme/library/src/library/library' in '/home/user/project/src/app'
  using description file: /home/user/project/package.json (relative path: ./src/app)
    Field 'browser' doesn't contain a valid alias configuration
    using description file: /home/user/project/node_modules/@acme/library/package.json (relative path: ./src/library/library)
      no extension
        Field 'browser' doesn't contain a valid alias configuration
        /home/user/project/node_modules/@acme/library/src/library/library doesn't exist
      .ts
        Field 'browser' doesn't contain a valid alias configuration
        /home/user/project/node_modules/@acme/library/src/library/library.ts doesn't exist
      .tsx
        Field 'browser' doesn't contain a valid alias configuration
        /home/user/project/node_modules/@acme/library/src/library/library.tsx doesn't exist
      .mjs
        Field 'browser' doesn't contain a valid alias configuration
        /home/user/project/node_modules/@acme/library/src/library/library.mjs doesn't exist
      .js
        Field 'browser' doesn't contain a valid alias configuration
        /home/user/project/node_modules/@acme/library/src/library/library.js doesn't exist
      as directory
        /home/user/project/node_modules/@acme/library/src/library/library doesn't exist
[/home/user/project/node_modules/@acme/library/src/library/library]
[/home/user/project/node_modules/@acme/library/src/library/library.ts]
[/home/user/project/node_modules/@acme/library/src/library/library.tsx]
[/home/user/project/node_modules/@acme/library/src/library/library.mjs]
[/home/user/project/node_modules/@acme/library/src/library/library.js]
 @ ./src/app.component.ngfactory.js 12:0-92 30:102-122 30:186-206 33:204-224 33:289-309 36:204-224 36:289-309 45:102-122 45:187-207 51:102-122 51:187-207 120:102-122 120:187-207
 @ ./src/app/app.module.ngfactory.js
 @ ./src/main.ts
 @ multi ./src/main.ts

The path node_modules/@acme/library/src/library/library leads to a library.d.ts file, which is present in module directory.

If I remove typing information altogether from the external package, the application compiles and works correctly. Also, if I manually merge all typings into a single index.d.ts file, the compilation completes just fine.

I think there's something wrong in how the compiler resolves type definitions files, it appears like it's not looking for files with .d.ts extension for some reason.


Update 1

The external module @acme/library, is also developed and pre-compiled by me. It's a simple TypeScript library with some exported classes and functions. It's not using anything fancy from Angular like decorators and stuff.

It's package.json has the following fields:

"es2015": "index.es2015.js",
"main": "index.min.js",
"module": "index.es5.js",
"typings": "index.d.ts",

All mentioned files are present in the root directory of the package.


Update 2

After further investigation, it looks like the problem is caused by the dependency injection. Please see the example below.

I'm using a custom factory provider in order to inject an instance of Library to the constructor of the component.

The libraryFactory function is imported from the external module and it looks like this:

export function libraryFactory(): Library {
  const dep1 = new Dep1();
  const dep2 = new Dep2();
  return new Library(dep1, dep2);
}

Failing example

import {Component} from '@angular/core';

import {Library, libraryFactory} from '@acme/library';


@Component({
  selector: 'app-foo',
  templateUrl: './foo.component.html',
  providers: [
    {
      provide: Library,
      useFactory: libraryFactory
    }
  ]
})
export class FooComponent {

  constructor(public library: Library) {
  }

}

Working example

However, if I get rid of the DI it compiles and works correctly:

import {Component} from '@angular/core';

import {Library, libraryFactory} from '@acme/library';


@Component({
  selector: 'app-foo',
  templateUrl: './foo.component.html'
})
export class FooComponent {

  public library: Library;


  constructor() {
    this.library = libraryFactory();
  }

}

What could be causing this problem?

I will be glad to provide more specific information if needed on first request.

Slava Fomin II
  • 26,865
  • 29
  • 124
  • 202

1 Answers1

2

Angular does not know what to inject to your Library class because Dep1 and Dep2 is not injected before when using useFactory. So you can simply change useFactory to useValue in your failing example, then it must be okay.

OR

If you want to use factory for Library instance, you have to inject Dep1 and Dep2 to providers before Library. If Dep1 and Dep2 have no dependencies themself it must work. If they have dependencies too, you have to inject them also.

import {Component} from '@angular/core';

import {Library, Dep1, Dep2} from '@acme/library';

const dep1instance = new Dep1();
const dep2instance = new Dep2();

@Component({
  selector: 'app-foo',
  templateUrl: './foo.component.html',
  providers: [
    {
      provide: Dep1,
      useValue: dep1instance
    },
    {
      provide: Dep2,
      useValue: dep2instance
    },
    {
      provide: Library,
      useFactory: (dep1: Dep1, dep2: Dep2) => new Library(dep1, dep2),
      deps:[Dep1,Dep2]
    }
  ]
})
export class FooComponent {

  constructor(public library: Library) {
  }

}

OR

You can just branch your library with Injectible decorators for angular DI compatibility. IMO easy, maintainable and useful choice.

Okan Aslankan
  • 3,016
  • 2
  • 21
  • 26
  • 1
    I think `(dep1: Dep1, dep2: Dep2) => new Library(dep1, dep2),` should be turned into an exported function to work with AOT – David Aug 02 '18 at 08:26
  • Hello! Thank you for your answer. "you have to inject Dep1 and Dep2 to providers before Library" — Dep1 and Dep2 are just internal implementation of the factory function, these classes are instantiated directly in it. Why do I need to inject them explicitly? Could you please explain it further or give a link to a place where this behavior is explained in more detail? This looks awkward to me. – Slava Fomin II Aug 06 '18 at 08:18
  • "You can just branch your library with Injectible decorators for angular DI compatibility." — could you please explain what do you mean by "branch"? If you are suggesting to add the Angular decorator to the library code than it's a bad idea, because the library should be framework-agnostic. It has nothing to do with Angular directly. I'm going to use it with React as well. – Slava Fomin II Aug 06 '18 at 08:22
  • I've tried to use your example, but it generates even more errors. Actually, it adds the same error as before for each additional explicit dependency (Dep1 and Dep2). – Slava Fomin II Aug 06 '18 at 08:42
  • Hi, for your first question, When you want to inject something to DI container, you have to inject its dependencies too. So Angular can resolve them before resolving Library instance. Thats how DI works. About second question, what I mean with "branch" is rewrite and publish for angular as different npm package like 'library-angular' for DI compatibility. It is a common way. And about code that I shared, I [could not reproduce](https://stackblitz.com/edit/angular-7dmuuw?file=src%2Fapp%2Fhello.component.ts). It should work. Also try changing useFactory method to export function as @David said. – Okan Aslankan Aug 06 '18 at 15:39