46

I am building an Angular2 application in Typescript and would like to use the class system functionality (read: class inheritance) offered by Typescript. However, it seems that Angular2 is not playing nice with derived classes. I am looking for some help in getting my application to work.

The problem I am facing is that I have a base class and derive a few child classes from there. When I am building up my component tree, I would like to be able to access the parent/children of the components (either way is fine). From what I understand, Angular2 offers two options to accomplish that:

  1. Inject the parent into the child component
  2. Use ContentChildren (or ViewChildren) to access the children of the component.

Both work fine if you know the type of the class you are working with (ChildComponent), but seem to fail when you try to use the base class of these components (BaseComponent) as selector.

To visualize it in some code (see this Plunker for a live demo), I have an application component/class as follows:

@Component({
  selector: 'my-app',
  template: `<parent-comp>
      <child-comp1></child-comp1>
      <child-comp1></child-comp1>
      <child-comp2></child-comp2>
    </parent-comp>`,
  directives: [ParentComponent, ChildComponent1, ChildComponent2]
})
export class MyApplication  {
}

The Base Class and Child Classes are defined as:

export class BaseComponent {
  // Interesting stuff here
}

@Component({
  selector: 'child-comp2',
  template: '<div>child component #2</div>'
})
export class ChildComponent2 extends BaseComponent {
}

@Component({
  selector: 'child-comp1',
  template: '<div>child component #1</div>'
})
export class ChildComponent1 extends BaseComponent {
}

And the Parent class has some logic to count its children.

@Component({
  selector: 'parent-comp',
  template: `<div>Hello World</div>
   <p>Number of Child Component 1 items: {{numComp1}}
   <p>Number of Child Component 2 items: {{numComp2}}
   <p>Number of Base Component items: {{numBase}}
   <p><ng-content></ng-content>
  `
})
export class ParentComponent implements AfterContentChecked  {

  @ContentChildren(ChildComponent1) contentChild1: QueryList<ChildComponent1>
  @ContentChildren(ChildComponent2) contentChild2: QueryList<ChildComponent2>
  @ContentChildren(BaseComponent) contentBase: QueryList<BaseComponent>
  public numComp1:number
  public numComp2:number
  public numBase:number

  ngAfterContentChecked() {
    this.numComp1 = this.contentChild1.length
    this.numComp2 = this.contentChild2.length
    this.numBase = this.contentBase.length
  }

(Again, you can see a live demo here)

The output for the first two counters is as expected. There are 2 ChildComponent1 and 1 ChildComponent2 children. Unfortunetely, the BaseComponent counter doesn't show the sum of these counters, but shows 0. It doesn't find any class that is of type BaseComponent in the children.

The same thing happens when ParentComponent also extends from BaseComponent and you want to Inject it into a ChildComponent. The Injector will require the specific type of the ParentComponent and can't work with the base class.

Any clues on how to work with derived classes in Angular2? Am I missing something or trying something impossible?

3
  • I think this is an Angular problem and more specifically with the implementation of ContentChildren and ViewChildren because as I can understand they are using only the metadata of the child to make the comparison so as long BaseComponent isn't included there the child isn't added to the QueryList. However if you add providers: [BaseComponent] to your children there will be BaseComponent instance in the metadata of the component and current child will be added to the QueryList EVEN if inheritance isn't used and also will create ANOTHER instance of the BaseComponent different from the child. Commented Mar 17, 2016 at 20:21
  • Plunker link for the case described in the previous comment. Commented Mar 17, 2016 at 20:22
  • @NikolaNikolov: I tried out your suggestion with having BaseComponent as a provider. That indeed adds the BaseComponent to the list of ContentChildren of the parent. However, these instances are, as you already mentioned, different from the derived class instances itself. That is too bad. Commented Mar 19, 2016 at 9:00

2 Answers 2

52
+100

Add a provider to each derived component, aliasing the base component to the derived component:

@Component({
  selector: 'child-comp2',
  template: '<div>child component #2</div>',
  providers: [{provide: BaseComponent, useExisting: forwardRef(() => ChildComponent2) }]
})
export class ChildComponent2 extends BaseComponent {
}

@Component({
  selector: 'child-comp1',
  template: '<div>child component #1</div>',
  providers: [{provide: BaseComponent, useExisting: forwardRef(() => ChildComponent1) }]
})
export class ChildComponent1 extends BaseComponent {
}

Plunker: http://plnkr.co/edit/5gb5E4curAE2EfH2lNZQ?p=preview

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks, that seems to work! I also checked if you really get the derived instance rather than a separate component instance, and that also seems to work out correctly: plnkr.co/edit/KSchL5dOCebxEQU692u9?p=preview
Been looking for a way to achieve this (without having to use the #ref hack) for hours!!! It's great that I found this. I can't believe this is not well documented on the angular docs. The #ref-hack would be great, if there wouldn't be a big warning about don't using the same ref-variable on the same template on the docs :-)
0

You can use a local template variables with a ContentChildren query to create a list of all components with a shared base class. Here is a plunk for the code below. This approach was taken from the suggestion in Kara Erickson's Rookie mistakes blog post.

In the template each child component is marked with #child:

  <app-container>
    <app-child1 #child></app-child1>
    <app-child1 #child></app-child1>
    <app-child1 #child></app-child1>
    <app-child1 #child></app-child1>
    <app-child2 #child></app-child2>
  </app-container>

This can then be used to select all the components with the shared base component:

  import { Component, QueryList, ContentChildren, ViewChildren, AfterViewInit, AfterContentInit } from '@angular/core';
  import { BaseComponent } from '../base/base.component';
  import { Child1Component } from '../child1/child1.component';
  import { Child2Component } from '../child2/child2.component';

  @Component({
    selector: 'app-container',
    templateUrl: './src/container/container.component.html'
  })
  export class ContainerComponent implements AfterContentInit  {
    @ContentChildren('child') allChildren: QueryList<BaseComponent>;
    @ContentChildren(Child1Component) child1Chdilren: QueryList<Child1Component>;
    @ContentChildren(Child2Component) child2Chdilren: QueryList<Child2Component>;

    constructor() { }

    ngOnInit() {
    }

    ngAfterContentInit() {
    }

  }

3 Comments

As I commented on the accepted answer, this solution would be great, if there wouldn't be a big warning about don't using the same ref-variable on the same template on the docs :-)
@andzep The warning is only relevant to trying to access a specific instance using the template ref. As long as you aren't doing that it will be fine.
thanks. Yes, normally I wouldn't want to get a specific one. I ended up using the first solution anyway, because in my case it's a good plus that it actually only selects children extended from my base component. But for other kind of mixed elements, this would be perfect.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.