2

I have a ButtonGroupComponent in which i need to render two types of children: ButtonActionComponent or ButtonNavigationComponent.

I need to project these children via @ContentChildren, but what can i do, since it is not possible to pass both selectors?

@ContentChildren(??)
  public children: QueryList<??>;

So, I tried another solution: I created a component, GroupableButtonComponent, which is extended by the two (ButtonActionComponent and ButtonNavigationComponent).

And then I passed directly this component to ContentChildren:

@ContentChildren(GroupableButtonComponent)
  public children: QueryList<GroupableButtonComponent>;

But I found that children remains empty.

Another solution I tried was to exploit dependency injection and create a GroupableButton class:

export class GroupableButton {
}

where ButtonActionComponent:

@Component({
      selector: "button-action",
      template: `
        ...
      `,
      providers: [{ provide: GroupableButton, useClass: ButtonActionComponent }],
    })
    export class ButtonActionComponent {
        ...
    }

and ButtonNavigationComponent:

@Component({
      selector: "button-navigation",
      template: `
        ...
      `,
      providers: [{ provide: GroupableButton, useClass: ButtonNavigationComponent }],
    })
    export class ButtonNavigationComponent {
        ...
    }

I used it this way:

@ContentChildren(GroupableButton)
  public children: QueryList<GroupableButton>;

but since inside my ButtonGroupComponent I have:

...

this.children
  .toArray()
  .slice(0, visibleButtons)
  .reverse()
  .forEach((button) => {
    const factory = this.factoryResolver.resolveComponentFactory(
      GroupableButton
    );

    const component = this.visibleButtonsContainer.createComponent(
      factory,
      0,
      null,
      [[button.element.nativeElement]]
    );
    this.clonePropertiesFrom(button, component.instance);
    this.buttons.push(component);
  });

...

this time the problem is that I can't pass GroupableButton as entryComponents inside my module.

Shlok Nangia
  • 2,355
  • 3
  • 16
  • 24
Elisa
  • 45
  • 3
  • 1
    you are 100% over complicating this. can you take a step back and talk about your actual goals here – bryan60 May 25 '20 at 15:25

2 Answers2

1

first, instead of the providers you have, do this:

providers: [{ provide: GroupableButton, useExisting: forwardRef(() => ButtonActionComponent) }],

this will ensure you're getting the correct component and can loop your components as you see fit.

I'm not sure this solves your instantiation issue, but instead of manually instantiating your components, have you considered the good old ng-content tag in your component template?

<!-- put this where ever you want to project your buttons.... -->
<ng-content></ng-content>
bryan60
  • 28,215
  • 4
  • 48
  • 65
0

You're overcomplicating the solution.

Why not simply query the two components via a string selector:

@ContentChildren('templateRef1, templateRef2') allTypesOfButtons: QueryList<ButtonActionComponent | ButtonNavigationComponent>;

I took the time to put an example on stackblitz

Rachid O
  • 13,013
  • 15
  • 66
  • 92
  • 1
    She is looping over the elements. With two different query lists she'd loose the order. – 0xc14m1z May 25 '20 at 12:26
  • @0xc14m1z what order are you talking about? OP didn't mention any order – Rachid O May 25 '20 at 14:08
  • Well, the last snipped of code is a `forEach` statement on a reversed array, hence, I am assuming that a particular order is required, if the list of children has to be looped through in reverse order... but maybe I'm wrong. @Elisa what can you tell us about this? – 0xc14m1z May 25 '20 at 14:26
  • @0xc14m1z Yes, you're right, I need to preserve the exact order of my buttons because i have to place them in a submenu. – Elisa May 25 '20 at 14:42
  • if order is important, you can add template selector for each button inside `ng-content`, and use it like: `@ContentChildren('baseButton') children: QueryList;` Stackblitz: https://stackblitz.com/edit/angular-e89usn BTW, I think this question is a possible duplicate of [this](https://stackoverflow.com/questions/36063627/class-inheritance-support) – Alex Voloshyn May 25 '20 at 16:13
  • @RachidO you're assuming the button groupings will be in order, which may be wrong. it could go `btn1 btn2 btn1` and you lose that ordering. I suspect what OP is trying to accomplish is better solved with `ng-content` but if the order does really matter, multiple content children doesn't work. – bryan60 May 25 '20 at 16:14
  • @AlexVoloshyn it's an option but a sub optimal one as it complicates the usage of the component with a strange hidden dependency and doesn't solve the dynamic component creation issue – bryan60 May 25 '20 at 16:16
  • @bryan60 indeed I assumed the btns won't be mixed, i'll update my answer then – Rachid O May 25 '20 at 16:17
  • looking at the blitz, i'm not seeing the content children array populated? – bryan60 May 25 '20 at 17:28
  • I see the empty array too – 0xc14m1z May 25 '20 at 18:26
  • @0xc14m1z sorry I forgot to update the link https://stackblitz.com/edit/angular-ivy-a32iee?file=src%2Fapp%2Fhello.component.ts – Rachid O May 25 '20 at 18:56