Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AOT compilation fails when factory function imported from library is used in DI in Angular

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.

like image 240
Slava Fomin II Avatar asked Jul 30 '18 12:07

Slava Fomin II


1 Answers

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.

like image 152
Okan Aslankan Avatar answered Nov 08 '22 04:11

Okan Aslankan