13

Can you override a third-party module's component declaration?

Say you're using a third-party module that declares and exports two components:

@NgModule({
  exports: [Cmp1, Cmp2]
  declarations: [Cmp1, Cmp2]
})
export class ThirdPartyModule {}

Cmp1's template:

`<app-cmp2></app-cmp2>`

Cmp2's template:

`<p>foo</p>`

AppModule imports ThirdPartyModule:

@NgModule({
  ...
  imports: [ThirdPartyModule],
  declarations: [AppComponent]
})
export class AppModule {}

AppComponent's template is just <app-cmp1></app-cmp1>.

How would you redeclare/override the third-party module's implementation of Cmp2 so that not Cmp2 but MyCmp2 is rendered inside Cmp1?

Obviously I'd need to extend Cmp2 (or implement its interface):

@Component({
  ... // same selector as Cmp2
})
export const MyCmp2 extends Cmp2 {}

I tried providing it via DI: { provide: Cmp2, useClass: MyCmp2 } which didn't work.

Simply declaring it in the app module won't work either, because angular throws when two components match the same selector. Is this even possible?

My specific usecase is overriding the header component of material's horizontal stepper.

j2L4e
  • 6,914
  • 34
  • 40

2 Answers2

1

It's not possible to do it the way you describe, since you'll either be declaring two components with the same name, or no components at all.

One possible solution is to use ComponentFactoryResolver with a configuration service.

Lets say you want to use ComposableComponent with a different InnerComponent (either InnerComponent1 or InnerComponent2) on two different modules ComposedWithInner1Module and ComposedWithInner2Module.

ComposedWithInner1Module is defined as:

@NgModule({
  imports: [ComposableComponentModule],
  providers: [
    {
      provide: Config,
      useValue: { component: InnerComponent1 }
    }
  ],
})
export class ComposedWithInner1Module {}

ComposedWithInner2Module is defined as:

@NgModule({
  imports: [ComposableComponentModule],
  providers: [
    {
      provide: Config,
      useValue: { component: InnerComponent2 }
    }
  ],
})
export class ComposedWithInner2Module {}

On ComposableComponentModule you need to say that both InnerComponent2 or InnerComponent1 can be injected dynamically, which is done through the entryComponents property:

@NgModule({
  declarations: [ComposableComponent],
  exports: [ComposableComponent]
  entryComponents: [InnerComponent2, InnerComponent1]

})
export class ComposableComponentModule {}

Then ComposableComponent gets the injected InnerComponentX through the injected Config and loads it into the template with ComponentFactoryResolver:

@Component({
  selector: 'prl-column-header-extras',
  template: '<p> I'll be substituted by InnerComponent2 or InnerComponent1 </p>'
})
export class ComposableComponent implements OnInit {
  constructor(
    private config: Config,
    private componentFactoryResolver: ComponentFactoryResolver
  ) {}

  ngOnInit() {
    const comp = this.componentFactoryResolver.resolveComponentFactory(
      this.config.component
    );

    this.viewContainerRef.clear();
    const ref = this.viewContainerRef.createComponent(comp);

    ref.changeDetectorRef.markForCheck();
  }
}
golfadas
  • 5,351
  • 3
  • 32
  • 39
  • 2
    This approach requires `ComposableComponent` to be designed for composability. It doesn't enable you to inject a child component into an arbitrary parent. – j2L4e Dec 12 '18 at 12:07
  • 1
    yes, you're right. I don't think it is possible to "duck type" components through ngModule. Angular likes its boilerplate. – golfadas Dec 12 '18 at 14:29
-1

In your case it is not possible. For such things ng-content is used. You can read about it here https://angular.io/guide/content-projection

Bogdan
  • 9
  • 3