Michael Kang
Michael Kang

Reputation: 52867

@ContentChildren is not being populated

I have three components: App, Parent, and Child:

App Component

@Component({
    selector: 'app',
    directives: [Parent,Child],
    template: '<parent><child>hi</child><child>there</child></parent>'
})
export class AppComponent {
    constructor() {
    }
}

Parent Component

@Component({
    selector: 'parent',
    template: '<div><h1>Parent</h1><ng-content></ng-content></div>'
})
export class Parent {
    @ContentChildren(Child) children: QueryList<Child>;
    ngAfterContentInit() {
        console.log(this.children);
    }
}

Child Component

@Component({
    selector: 'child',
    template: '<div><h1>Child</h1></div>'
})
export class Child {
}

As you can see in the Parent component, I've tried to use @ContentChildren to get the list of Child components, using the Child type as a selector. However, this does not seem to work - the content children list is always undefined.

In the ngAfterContentInit() method, I would have expected content children to be populated.

Am I missing something?

[Update]

So it turns out that the problem exists when all three components are in the same file (see console debug window where I output the content children):

Plnkr Demo of Issue

If they are in separate files the problem goes away:

Plnkr Demo Working

Normally, I would only place all the components in the same file for learning purposes. But it has me curious. Does anyone know why the behaviour is different?

Upvotes: 26

Views: 20480

Answers (3)

Khaled Lela
Khaled Lela

Reputation: 8139

You need to add { descendants: true } to include nested children,

@Component({
    selector: 'parent',
    template: '<div><h1>Parent</h1><ng-content></ng-content></div>'
})
export class Parent {
    @ContentChildren(Child, { descendants: true }) children: QueryList<Child>;
    ngAfterContentInit() {
        console.log(this.children);
        console.log(this.children.length);
    }
}

descendants - True to include all descendants, otherwise include only direct children.

Upvotes: 16

Steve Brush
Steve Brush

Reputation: 3191

This also happens if your child component references your parent component in the constructor; Angular doesn't like the circular reference!

I needed to choose only one method for parent->child communication: either use ContentChildren OR use the parent component in the child's constructor.

@Component()
export class Parent {
  @ContentChildren(Child)
  private children: QueryList<Child>;
}

@Component()
export class Child {
  constructor(
    // !! Remove the following line to make the ContentChildren "work" again.
    @Optional() parent: Parent
  ) { }
}

Upvotes: 1

alexpods
alexpods

Reputation: 48555

You need to use forwardRef to reference classes which are not yet defined. See this plunk. Remember ES6 classes are not hoisted.

@Component({
    selector: 'parent',
    template: '<div><h1>Parent</h1><ng-content></ng-content></div>'
})
export class Parent {
    @ContentChildren(forwardRef(() => Child)) children; // <!- HERE

    ngAfterContentInit() {
        console.log(this.children);
        console.log(this.children.length);
    }
}

UPD Mark Rajcok pointed out an excellent article about forward references in angular2 (see the comment bellow). Must read: thoughtram.io Forward references in Angular 2.

Upvotes: 29

Related Questions