angularangular-ivy

Angular Ivy ignoring entryComponents setting


I have a custom component decorator, used to link components to "names", in order to use a JSON object to link components into a hierarchy.

@MyDecorator('name1')
@Component({
  selector: 'my-component',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.scss']
})
export class MyComponent implements OnInit {
  // ... rest of the implementation
}

What the decorator does is automatically register the component into a Map, pretty much like the following:

{
  "name1": MyComponent
}

I have a configuration, pretty much a dynamic route config, stored as a JSON object external to the application code.

[
  {
    link: '/myroute',
    component: 'name1'
  }
]

In a dynamic host component, I use code like the following, to instantiate the prescribed component:

// ... gets the component "name"
const componentName = getConfigForRoute('myroute'); 
// should return MyComponent class, if MyComponent gets included in the app bundle:
const componentType = componentRegistry[componentName];

// componentType is ok with ng serve, and undefined in prod builds!
const cf = this.componentFactoryResolver.resolveComponentFactory(componentType);
// reportOutlet is a ViewContainerRef
this.reportOutlet.createComponent(cf);

Everything works as expected when running in dev mode via ng serve.
As is often the case, things doesn't go smoothly with a production build: the components decorated with MyDecorator are not referenced by ts code, other than the NgModule of the application, so they are happily dropped by the zealous compiler.

I (like everyone else) used to include them in the EntryComponents array in the module, but now it seems Ivy is simply ignoring it, so I'm left with no apparent option to make sure the components are not dropped from the build.


@NgModule({
  declarations: [
    AppComponent,
    MyComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule,
    // ...some other stuff
  ],
  entryComponents: [MyComponent],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

How can I accomplish the same thing I used to do with EntryComponents or make it work again? That is, make sure MyComponent gets included? The ideal solution would not include a global list: the whole point of having a decorator is so that I don't need to maintain yet another components list, but I would accept every viable alternative.

Thanks!


Solution

  • Well, I hacked my way around the problem, in a very ugly way.

    AFAICT, Ivy is letting tsc and Webpack do their Mojo when dropping classes during tree shake (not sure about the details, this is just the general picture I made).

    So in order to "emulate" entryComponents we must include a "usage" of the components somewhere. I don't want to have references to this components outside the module, so I solved the problem with an entryComponents "emulation". It is as simple as:

    @NgModule({
      declarations: [
        AppComponent,
        MyComponent
      ],
      imports: [
        BrowserModule,
        HttpClientModule,
        AppRoutingModule,
        // ...some other stuff
      ],
      entryComponents: [MyComponent],
      providers: [
        {
          provide: '__DEFINITELY_NOT_ENTRY_COMPONENTS__',
          // here I can include a list of components not to be dropped by tree shaking
          useValue: [MyComponent]
        }
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule {}
    

    I really wish there was a better way.
    I think that EntryComponents has its share of use cases, like mine.