Write Compound Components with Angular’s ContentChild

Share this video with your friends

Send Tweet

Allow the user to control the view of the toggle component. Break the toggle component up into multiple composable components that can be rearranged by the app developer.

Chandan
Chandan
~ 5 years ago

This video has a typo as shown below which kept me guessing where the toggled is defined. Checked the stackblitz to confirm which has correct one.

ngAfterContentInit { this.toggleButton.toggle(d).subscribe(() => {}) }

chihab
chihab
~ 5 years ago

@Isaac is there a way to query content child component by a directive so that we could have access to its hosting component instance:

@ContentChild(ToggleOnDirective) toggleButton: ToggleButtonInterface

This way we could declare our own toggle-button like components.

<toggle (toggled)="showMessage($event)">
  <toggle-on>On</toggle-on>
  <toggle-off>Off</toggle-off>
  <toggle-button-other appToggleButton></toggle-button-other>
</toggle>

Otherwise we would be limited to toggle predefined "sub components" and the only benefit would be being able to organize them in our view.

Isaac Mann
Isaac Mann(instructor)
~ 5 years ago

@chihab, I'm not sure what you're trying to do here. Where is this code?

@ContentChild(ToggleOnDirective) toggleButton: ToggleButtonInterface

Is that in the appToggleButton directive? What are you expecting to show up in toggleButton?

chihab
chihab
~ 5 years ago

@Isaac my bad, ToggleOnDirective should be ToggleButtonDirective which is the selector in ContentChild.

Please consider this code:

export interface ToggleButtonInterface {
    reset();
}

toggle-button-other.component.ts

@Component({
  selector: 'toggle-button-other',
  ...
})
export class ToggleButtonOtherComponent implements ToggleButtonInterface {
    ...
    reset() {
    }
}

toggle-button-awesome.component.ts

@Component({
  selector: 'toggle-button-awesome',
  ...
})
export class ToggleButtonAwesome implements ToggleButtonInterface {
    ...
    reset() {
    }
}

app.component.html

<toggle>
  ...
  <toggle-button-other appToggleButton></toggle-button-other>
</toggle>

<toggle>
   ...
  <toggle-button-awesome appToggleButton></toggle-button-awesome>
</toggle>

toggle.component.ts

...
@ContentChild(ToggleButtonDirective) toggleButton: ToggleButtonInterface
ngAfterContentInit() {
    this.toggleButton.reset(); // the problem here is that we get ToggleButtonDirective instance, how to get the instance of the hosting component ?
}
...

The question is how to specify to ContentChild right above how to get ToggleButtonOtherComponent or ToggleButtonAwesome instance instead of ToggleButtonDirective instance without explicitly naming class name as we don't konw what to expect. How to query the component hosting ToggleButtonDirective ?

Isaac Mann
Isaac Mann(instructor)
~ 5 years ago

@chihab, I don't think you can do exactly what you want.

One possible workaround is to have the two types of buttons extend a parent class and then inject that parent class.

Another option is to use template references in your template:

<toggle-button-other appToggleButton #mybutton></toggle-button-other>

And use that string to find the button:

@ContentChild('mybutton') toggleButton: ToggleButtonInterface

More info: https://blog.angularindepth.com/handle-template-reference-variables-with-directives-223081bc70c2

Eugene Vedensky
Eugene Vedensky
~ 5 years ago

Perhaps a dumb question, but is @ContentChild meant to be used with a component that has an <ng-content></ng-content> template?

Isaac Mann
Isaac Mann(instructor)
~ 5 years ago

Not dumb. Yes @ContentChild gives a component a way to access items that are placed inside of its <ng-content></ng-content> area.

Christian Crowhurst
Christian Crowhurst
~ 4 years ago

There's a problem with querying for the toggle-button using ContentChild. In essence the query is not "live. so whenever the toggle-button is destroyed and recreated, the toggle component breaks.

Here's a demo of the problem: https://stackblitz.com/edit/adv-ng-patterns-02-compound-components-problem?file=app%2Fapp.component.html

Try unchecking then checking the "Create/destroy ToggleButton". And then use the toggle button. The component is broken.

I had a go at fixing it using ContentChildren and a dose of observables. See here: https://stackblitz.com/edit/adv-ng-patterns-02-compound-components-fixed

My solution is "ok" but considerably more code. Not sure there is any simpler solution - what do you think?

Christian Crowhurst
Christian Crowhurst
~ 4 years ago

just watched the next video. much better solution than trying to orchestrate things from the parent. Solves the problem I identified with much less code than my solution - nice