Angular: Using the providers array of a component imported from a different module

531 Views Asked by At

I have a ViewerWidgetComponent - selector app-viewer-widget, declared in and exported from SharedModule.

The ViewerWidgetComponent has a child component from a third party library: AcMapComponent - selector ac-map, where content is projected within ac-map:

viewer-widget.component.html:

    <ac-map [mapId]="viewerId">
        <ng-container *ngIf="viewerInitialised | async">
            <ng-container [ngTemplateOutlet]="content.templateRef"></ng-container>
        </ng-container>
    </ac-map>

Side note - template is used as content to be projected is conditionally rendered, found using custom ProjectContentDirective directive.

ViewerWidgetComponent has a ViewerConfiguration provider, required for the third party AcMapComponent

viewer-widget.component.ts:

@Component({
  selector: 'app-viewer-widget',
  templateUrl: './viewer-widget.component.html',
  styleUrls: ['./viewer-widget.component.scss'],
  providers: [ViewerConfiguration],
})
export class ViewerWidgetComponent implements AfterViewInit {

  @ContentChild(ProjectContentDirective) content!: ProjectContentDirective;
  public viewerInitialised = new BehaviorSubject(false);

  constructor(private viewerConfiguration: ViewerConfiguration) { 
  }

  ngAfterViewInit(): void {    
    // other logic here...
    this.viewerInitialised.next(true);
  }
}

I am then importing this ViewerWidgetComponent into ExploreModule and using it in PortalComponent.

PortalComponent is declared in ExploreModule:

portal.component.html:

<app-viewer-widget>

  <ng-template appProjectContent>
    <ac-layer>
      <ac-billboard-desc></ac-billboard-desc>
    </ac-layer>
  </ng-template>

</app-viewer-widget>

Now the problem here, is ac-layer is another third party component, which must be a child of the ac-map component, as it relies on the providers array from the ac-map component. Which it is, since it is in a template container and rendered via the ViewerWidgetComponent, but since the template is in another module, DI is not picking up the ac-map providers.

Snippet from third party ac-map component ts:

@Component({
  selector: 'ac-map',
  template: `
    <ng-content></ng-content>
  `,
  providers: [
    CesiumService,
    several more...
    MapLayersService,
  ],
})

So Angular is throwing this error: NullInjectorError: R3InjectorError(ExploreModule)[MapLayersService -> MapLayersService -> MapLayersService -> MapLayersService]: NullInjectorError: No provider for MapLayersService!

Since it can't find the required providers, as ac-map component is in a different module.

How can I forward the providers array within ac-map, a child of ViewerWidgetComponent, declared in SharedModule, to be used in the PortalComponent, which is in a different module?

1

There are 1 best solutions below

0
On

There's another way of creating dynamic component. If there was a running code snippet i could have tested but you can try this:

html

<ac-map [mapId]="viewerId" ProjectContentDirective>
</ac-map>

ts file:

 @ViewChild(ProjectContentDirective) content!: ProjectContentDirective;
  public viewerInitialised = new BehaviorSubject(false);

  constructor(private componentFactoryResolver:ComponentFactoryResolver) { }

  ngOnInit(): void {
    this.viewerInitialised.subscribe(v=>{
      if(v==true){
        const viewContainerRef = this.content.viewContainerRef;
        viewContainerRef.clear();
        const componentRef = viewContainerRef.createComponent<AcMacCopmponent>(this.componentFactoryResolver.resolveComponentFactory(AcMacCopmponent));    
      }
    })
  }

  
  ngAfterViewInit(): void {    
    // other logic here...
    this.viewerInitialised.next(true);
  }