Crocsx
Crocsx

Reputation: 7640

ContentChild of a parent class

closely related to ContentChild by extended abstract class

I am using a chart package that handle very poorly the resize.

I made a wrapper component that would set a static height/width, and draw the chart inside using ng-content:

<app-chart-sizer>
   <chart>
   </chart>
</app-chart-sizer>

app-chart-sizer is basically

<div #sizer>
    <ng-content></ng-content>
</div>

with

@Component({
  selector: 'app-chart-sizer',
  templateUrl: './chart-sizer.component.html',
  styleUrls: ['./chart-sizer.component.scss']
})
export class ChartSizerComponent implements AfterContentInit {

  @Input() percentageH: number
  @Input() percentageW: number

  @ViewChild('sizer') sizer: ElementRef


  @HostListener('window:resize', ['$event'])
  onResize() {
    this.setHeight()
  }

  constructor(
    private renderer: Renderer2
  ) {

  }

  ngAfterContentInit(): void {
    this.setHeight()
  }

  setHeight() {
// 
  }
}

The problem I face is, at first load, after draw in the content, the chart are not using a correct size, so I need to trigger their resize event.

I can just dispatch a window resize, or more cleanly, I would like to call the resize on the chart component inside the ngContent

The library as multiple charts, and they all have a parent BaseChartComponent but their component are different.

I tried to do

  @ContentChild(BaseChartComponent) chart: BaseChartComponent

but this return undefined, If I name a specific chart name like (BarChartComponent extends BaseChart)

 @ContentChild(BarChartComponent) chart: BarChartComponent

this work, but I miss all other chart type.

how can I query correctly for an instance of BaseChartComponent? I can't edit the childs, so I can't use the solution provided in many SO exemples.

Upvotes: 0

Views: 998

Answers (1)

julianobrasil
julianobrasil

Reputation: 9377

Yeah, it's not possible, because these decorators work like the querySelector would work when search in the DOM for the components: and your AbstractClass is not in the DOM at all, so it won't be found. But maybe you can have a workaround using a directive to be applied to your charts.

Stackblitz demo

To try to minimize the code here, I'm gonna make some assumptions that can be easily overridden by your real case. The main part is: you cannot query for a base abstract class not for a component without previous knowledge about its type, but you can query for a directive and you can apply a directive to the components you wanna query.

Step 1: Let's build the directive

It has just one function: get and expose a reference of the component it's applied to. As we're gonna use an @Input for grabbing the reference, we can use any type we want for the input, including the base abstract class BaseChartComponent that's not a decorated with @Component, despite its name (because you have some important properties/methods there that you'd like to access, like a resize() method).

@Directive({selector: "[baseChart]"})
export class BaseChartDirective {
  @Input() baseChart: BaseChartComponent;
}

Step 2: Put the resizer calling properties/methods in your base abstract class

Something like this:

export abstract class BaseChartComponent { size = 20 }

Step 3: Build your resizer normally, grabbing a reference for the Directive we've built above

I'm also supposing that your sizer component is like below. Notice we're querying for the directive, so the component passed to ng-content should have the directive applied to it. Maybe you could put some warnings in the sizer when it doesn't find anything.

@Component({
  selector: "app-chart-sizer",
  template: '<ng-content></ng-content>'
})
export class ChartSizerComponent implements AfterContentInit {
  @ContentChild(BaseChartDirective) _chart: BaseChartDirective;

  ngAfterContentInit() {
    // You can get the new size via @Inputs in this component
    this._chart.size = 45;
  }
}

Step 4: Your chart components extend the base class normally and need no previous knowledge about the directive.

@Component({
  selector: "app-bar-chart",
  template: '<p>My size is {{size}}</p>'
})
export class BarChartComponent extends BaseChartComponent {}

Step 5: Apply the directive to the charts

So, when using the sizer. It's recommended that, in the directive, you throw an error if someone forgets to set the @Input() baseChart. This is necessary because, in the directive, we cannot inject the parent, like it's common in many cases (we cannot do that because, as stated above, we don't know beforehand the type of the component where the directive is applied to).

<app-chart-sizer>
  <app-bar-chart #chart [baseChart]="chart"></app-bar-chart>
</app-chart-sizer>

Upvotes: 2

Related Questions