Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Override imported module's component declaration

Tags:

angular

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.

like image 391
j2L4e Avatar asked Mar 01 '26 12:03

j2L4e


1 Answers

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();
  }
}
like image 77
golfadas Avatar answered Mar 04 '26 10:03

golfadas



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!