angular: Recursion of components (and circular imports) doesn't work in Ivy built libraries

🐞 bug report

Affected Package

Probably @angular/compiler

Is this a regression?

The first version I detected the bug in is 9.0.0.

Description

If I use recursion of components in an Ivy-built library, children will be not rendered. It only appears when built with --prod.

Relevant code:

@Component({
  selector: 'lib-recursive-list',
  template: `
    <lib-recursive-item *ngFor="let item of list" [model]="item"></lib-recursive-item>
  `,
  styles: []
})
export class RecursiveListComponent {
    @Input() list: RecursiveModel[];
}

@Component({
  selector: 'lib-recursive-item',
  template: `
    <div style="padding-left: 10px;">
        <div>{{model.name}}</div>
        <lib-recursive-list [list]="model.children"></lib-recursive-list>
    </div>
  `,
  styles: []
})
export class RecursiveItemComponent {
    @Input() model: RecursiveModel;
}

🔬 Minimal Reproduction

https://github.com/darress/angular9_lib_bugs/tree/recursion

Build the library with ng build my-lib, then build the app with ng build, it will work as expected. However if built with ng build --prod, only the two parents will be rendered, not the children.

🔥 Exception or Error

No exceptions or errors.

🌍 Your Environment

Angular Version:



     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/
    

Angular CLI: 9.0.3
Node: 10.14.2
OS: win32 x64

Angular: 9.0.2
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Ivy Workspace: Yes

Package                            Version
------------------------------------------------------------
@angular-devkit/architect          0.900.3
@angular-devkit/build-angular      0.900.3
@angular-devkit/build-ng-packagr   0.900.3
@angular-devkit/build-optimizer    0.900.3
@angular-devkit/build-webpack      0.900.3
@angular-devkit/core               9.0.3
@angular-devkit/schematics         9.0.3
@angular/cli                       9.0.3
@ngtools/webpack                   9.0.3
@schematics/angular                9.0.3
@schematics/update                 0.900.3
ng-packagr                         9.0.1
rxjs                               6.5.4
typescript                         3.7.5
webpack                            4.41.2

Anything else relevant? Reproducable with Chrome and Firefox. Tested under Windows 10.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 26
  • Comments: 36 (13 by maintainers)

Commits related to this issue

Most upvoted comments

Reproduces as reported, and the cause is exactly what I expected to find.

  1. when ngtsc compiles the library, the cycle detector rejects the import from RecursiveItemComponent for the directiveDefs of RecursiveListComponent as it would create a cycle.

  2. instead, ngtsc deopts to a side-effectful ɵɵsetComponentScope call next to MyLibModule.

  3. because the module is in a library, it gets built by CLI with a package.json which says sideEffects: false

  4. Terser dutifully strips the ɵɵsetComponentScope call when processing the library code

  5. One of the components is missing its directiveDefs at runtime.

I can think of only one real solution here: ngtsc needs to be modified to be okay with cycles externally, since external tooling can handle them. We would need to generate wrapped directiveDef arrays for both components in this case, as we don’t know in what order these files will be imported. This is pretty tricky to keep track of, and also has an impact on incremental build since the addition of a cycle could require the emit of new files.

A workaround is to set sideEffects: true in the library package.json, which tells the optimizer not to remove top-level calls (among other things).

We have encountered this recently.

We have moved a part of our app to a library and as our app is built with Ivy, we consumed Ivy library version as well. It was very much confusing experience when in prod mode the library turned out broken where it was working flawlessly in dev mode. The worst thing that there are zero clues how to debug and where to dig - fail happens silently and verbose mode is not much of a help - the output of terser is very cryptic - it’s not clear at all what is being shaken off and why. After hours of googling was lucky to found this issue…

With that said, I believe --verbose mode should provide meaningful information about things being removed from the build. That would make it much easier to troubleshoot issues like this. I have already raised this once in another issue (start reading from https://github.com/angular/angular/issues/37102#issuecomment-628782822), but that seems didn’t go anywhere.

Also, I agree this issue shouldn’t be marked as low frequency and second priority - this is pretty much a fundamental issue with libraries Ivy builds and Ivy builds are promoted as main way to build libraries going forward.

If you have 2 components in the library, A and B, and you include B in A’s template and use a static method of A in B (or exported funtion/class/anything), the same problem occurs.

// my-lib.component.ts
@Component({
  selector: 'lib-my-lib',
  template: `
      <lib-other></lib-other>
  `,
  styles: []
})
export class MyLibComponent{}
export function getSomething() {
  return `something`;
}

// other.component.ts
@Component({
  selector: 'lib-other',
  template: `
    <div>{{someInput}}</div>
  `,
  styles: []
})
export class OtherComponent {
  someInput = getSomething();
}

Reproduction is here: https://github.com/darress/angular9_lib_bugs/tree/import_recursion

This is maybe a more minimal reproduction of the problem.

The same workaround ("sideEffects": true) works here too.

I agree that freq1: low is probably not appropriate - and in fact is not featuring in our triage of issues anymore anyway. The key label is P2 which I think accurately captures this issue:

The issue is important to a large percentage of users, with a workaround. Issues that are significantly ugly or painful (especially first-use or install-time issues). Issues with workarounds that would otherwise be P0 or P1.

Hi @elesueur to build your library with view engine your tsconfig needs enableIvy: false in the angularCompilerOptions, see here: https://angular.io/guide/ivy#opting-out-of-ivy-in-version-9

Hi @alxhub we meet this problem too https://github.com/ng-packagr/ng-packagr/issues/1576 I think the freq label should not be low, and it is very hard to figure what happened after encountering this bug

@Phil147 indeed, this is an instance of the same problem.

What is really interesting if you build against the library source code and not the dist folder, this problem does not happen.

This is because the application is built with sideEffects: true by default 😃

Thank you @alxhub for the workaround, we can go full Ivy now!

Also @darress, thank you for the small and useful reproduction, it allowed me to verify what I thought was happening very easily.