Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested multiple levels of ContentChild query returns undefined #20810

Closed
niamleeson opened this issue Dec 5, 2017 · 19 comments
Closed

Nested multiple levels of ContentChild query returns undefined #20810

niamleeson opened this issue Dec 5, 2017 · 19 comments
Labels
area: core Issues related to the framework runtime freq1: low type: bug/fix
Milestone

Comments

@niamleeson
Copy link

niamleeson commented Dec 5, 2017

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

When ng-content is used on multiple levels, only the first component is able to find the ContentChild.

Expected behavior

I think every parent component at each level should be able to find the ContentChild. Please see the plunker example.

Minimal reproduction of the problem with instructions

https://plnkr.co/edit/iMDKo9cdKeyB0Y4is11y?p=preview

What is the motivation / use case for changing the behavior?

I have a div[myFormElement] component that projects an input. Based on what kind of input it is, I would like to add extra functionality, but I would like to separate that logic from the div[myFormElement] component and only include it in my-custom-wrapper-component.

<div myFormElement>
  <input myInput />  
</div>
<div myFormElement>
  <input mySpecialInput />
</div>

Then in the my-form-element component,

@ContentChild(myInput) inputElement: myInput;
@ContentChild(mySpecialInput) specialElement: mySpecialInput;

<div *ngIf="myInput">
  <ng-content select="[myInput]"></ng-content>
</div>
<div *ngIf="mySpecialInput">
  <my-custom-wrapper-component> <!-- This component adds extra functionality that my-form-element shouldn't be concerned with -->
    <ng-content select="[mySpecialInput]"></ng-content>
  </my-custom-wrapper-component>
</div>

In my-custom-wrapper-component, @ContentChild() is returning undefined.

@ContentChild(mySpecialInput) specialElement: mySpecialInput; // This is returning undefined

<ng-content></ng-content>

Environment


Angular version: Latest

Browser:
- [x] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
@pkozlowski-opensource pkozlowski-opensource added the area: core Issues related to the framework runtime label Dec 6, 2017
@ngbot ngbot bot added this to the Backlog milestone Jan 23, 2018
@floreks
Copy link

floreks commented Feb 2, 2018

Is there any workaround for this for now? I'd like to wrap some library components to extend their behavior however this issue pretty much prevents it.

Example usage of library components:

<mat-table [dataSource]="dataSource">
  <ng-container matColumnDef="name">
    <mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
    <mat-cell *matCellDef="let element">{{element.name}}</mat-cell>
  </ng-container>

  <ng-container matColumnDef="labels">
    <mat-header-cell *matHeaderCellDef>Labels</mat-header-cell>
    <mat-cell *matCellDef="let element">{{element.labels}}</mat-cell>
  </ng-container>

  <ng-container matColumnDef="status">
    <mat-header-cell *matHeaderCellDef>Status</mat-header-cell>
    <mat-cell *matCellDef="let element">{{element.status}}</mat-cell>
  </ng-container>

  <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>

What I'd like to have is:

<kd-card-list [resource]="nodes" [sortable]="true" [filterable]="true" [pageable]="true" sortActive="name">
  <div title>Nodes</div>

  <kd-card-list-element name="name">
    <mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
    <mat-cell *matCellDef="let node">{{node.name}}</mat-cell>
  </kd-card-list-element>

  <kd-card-list-element name="labels">
    <mat-header-cell *matHeaderCellDef>Labels</mat-header-cell>
    <mat-cell *matCellDef="let node">{{node.labels}}</mat-cell>
  </kd-card-list-element>

  <kd-card-list-element name="status">
    <mat-header-cell *matHeaderCellDef>Status</mat-header-cell>
    <mat-cell *matCellDef="let node">{{node.status}}</mat-cell>
  </kd-card-list-element>
</kd-card-list>

Unfortunately when wrapping <ng-container matColumnDef="..."> in a <kd-card-list-element name="..."> and <mat-header-row> with <mar-row> into <kd-card-list> @ContentChildren of underneath CdkTable are failing to find these children.

@maciaszczykm
Copy link

/subscribe

@mlc-mlapis
Copy link
Contributor

@floreks ... did you read ... #14769 (comment)

@floreks
Copy link

floreks commented Feb 2, 2018

So unless library specifies differently (by using descendants: true), there is no option to make it work with a wrappers? And what about cases where only @ContentChild is used and it does not allow this descendants: true option. It won't traverse through multiple components/directives levels also.

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Feb 2, 2018

@floreks ... it is necessary to wait till the moment when the logic of @ContentChildren will be modified ... it is possible to expect that after releasing V6, I guess. I suppose that also other combinations will be considered to cover that thema.

@floreks
Copy link

floreks commented Feb 2, 2018

I understand that this is still an open issue. I was just wondering if there is any workaround right now I could try to make it work. We'll just have to wait for the fix.

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Feb 2, 2018

@floreks ... I suppose that the way is to use some observable / subscribe pattern to self-register those components ... so you would be able to use them when you need ...

@floreks
Copy link

floreks commented Feb 2, 2018

That won't help as this is library function that crashes. It can not find required children using @ContentChild and @ContentChildren and I can't really modify external code.

@mlc-mlapis
Copy link
Contributor

@floreks ... ah, I understand ... so the only chance is just completely a different way ... lib / or your own, I guess.

@jefBinomed
Copy link

I observe the same behaviour. I write my own StackBlitz to show the problem : https://stackblitz.com/edit/angular-56ruzp For me it's a major problem because if we want to deal with that, we have to passed the reference of the contentChild element to the child. So if there is an official and clean workaround, I'm intersted by it !

@pkozlowski-opensource
Copy link
Member

I think that there is confusion here about intention of @ContentChildren / @ContentChild. In Angular the intention is that you query only you own content (and not content from some other templates.

Let me illustrate what I mean by a simple example, assuming that we've got the @ContentChildren('foo') foos query.

In the following case #foo will be found just fine:

<my-component>
    <div #foo></div>
</my-component>

On the other hand we will not (and should not) "descent" into <ng-content> (even if contains elements "matching queries"):

<my-component>
    <div #foo></div> <!-- this element will be found -->
    <ng-content></ng-content> <!-- non of the elements in the projected content gets queries, even if it contains #foo -->
</my-component>

There is very simple reasoning behind this behaviour - you can think of content of a component as an additional argument / input to it. So when you use a component in your template you must control what you provide to it. You do so by sticking tags between component tags (<my-component></my-component>).

Descending into <ng-content> would break the contract of controlling what you provide to a component. Imagine that you do the @ContentChildren('foo') foos query in one of the top-level components and we would descend into <ng-content> - all of the sudden you would find yourself getting #foo from all over an application and quite possibly from places you would never expect.

In short: we only query component's own content, not content coming from other templates. This makes sense since a template forms a namespace #foo in one template might mean one thing and completely different thing in another template. Names might be the same but meaning quite different.

I'm going to close this one as content queries work as designed here. If you still need help with some specific use-cases please open a new issue.

@maxkoretskyi
Copy link

maxkoretskyi commented Jul 26, 2018

@pkozlowski-opensource , what about querying components from a projected view container? Here is a very basic setup and the code:

@Component({
  selector: 'my-app',
  template: `
    <button (click)='addComp()'>Add</button>
    <child-comp>
      <ng-container #vc></ng-container>    
    </child-comp>
  ` 
})
export class AppComponent  {
  @ViewChild('vc', {read: ViewContainerRef}) vc;

  constructor(private resolver: ComponentFactoryResolver) {}

  addComp() {
    const f = this.resolver.resolveComponentFactory(ProjectedComponent);
    this.vc.createComponent(f);
  }
}

And the child component:

@Component({
  selector: 'child-comp',
  template: '<ng-content></ng-content>'
})
export class ChildComponent  {
  @ContentChildren(ProjectedComponent) projComps;

  ngAfterViewChecked() {
    console.log(this.projComps.length); // always outputs 0
  }
}

@jpzwarte
Copy link

jpzwarte commented Mar 8, 2019

@MaximusK I just ran into a similar issue with 2 <ng-content>s. The content will project fine in the nested <ng-content>, but @ContentChildren will never see the projected content. Do you know of a way around this?

@pkozlowski-opensource
Copy link
Member

@MaximusK @jpzwarte @ContentChildren (same for @ViewChildren, BTW) queries never look inside <ng-content>. Current Angular queries implementation look into nodes as written in a template and not into the resulting DOM.

Would be good to have a real-life use-case of what you are trying to achieve so I can propose an alternative approach. But currently queries are not checking projected nodes (<ng-content>) for matching for the reasons described previously. Currently we are not planning changes to the queries logic so it is important for us to understand a real-life usage scenario so we can propose and alternative approach.

@jpzwarte
Copy link

jpzwarte commented Mar 8, 2019

@pkozlowski-opensource I'm writing a <foo-user-menu> component that uses MatMenu. The FooUserMenu component contains 1 menu item by default ("Log out"), but an application can add additional menu items via content projection.

foo-user-menu.component.html:

<mat-menu>
  <ng-content></ng-content>
  <button mat-menu-item>Log out</button>
</mat-menu>

app.component.html:

<foo-user-menu>
  <button mat-menu-item>Settings</button>
</foo-user-menu>

So FooUserMenu first projects the content inside it's own template, then MatMenu projects it inside itself. The content projection works fine, but @ContentChildren inside MatMenu doesn't.

@jpzwarte
Copy link

jpzwarte commented Mar 8, 2019

To be clear, @ContentChildren inside MatMenu only sees the "Log out" menu item. It doesn't see the "Settings" menu item.

@pkozlowski-opensource
Copy link
Member

@jpzwarte this is a good use-case, thnx! I would say that it should be solved through #8563 (that is - better, more dynamic content projection system). I hope that we will get to it pos-ivy. I'm going to add your use-case to #8563, thnx!

@fxck
Copy link

fxck commented May 24, 2019

I have a form component that queries all material inputs with @ContentChildren, which works fine as long as they are direct descentants, when I have the form split into multiple groups with inputa, no dice.

works

<my-form>
  <input matInput /> 
</my-form>

doesn't (descentands: true or not)

<my-form>
  <my-form-group></my-form-group> // no ng-content, input in template
</my-form>

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime freq1: low type: bug/fix
Projects
None yet
Development

No branches or pull requests

10 participants