23

I don't really understand how object binding works, so if anyone could explain if I can use @Input() inside a base class, or better: decorators and inheritance. For example if each form should receive a customer I have a base class:

export class AbstractCustomerForm{

@Input() customer;
...
}

and then I extend this class in an actual component:

export AwesomeCustomerForm extends AbstractCustomerForm implements OnInit{
    ngOnInit(){

        if(this.customer)
            doSomething();

    }
}

but this won't work, customer will never get set :(

Silencer
  • 1,602
  • 4
  • 17
  • 27
  • The solution is to add @Directive() as per: [https://stackoverflow.com/questions/60526157/cant-bind-input-when-using-abstract-classes-2-level-of-hierarchy](https://stackoverflow.com/questions/60526157/cant-bind-input-when-using-abstract-classes-2-level-of-hierarchy) – user8909281 Aug 23 '23 at 10:43

6 Answers6

15

update

Inheritance is properly supported since 2.3.0-rc.0

original

Decorators are not inherited. They need to be applied to the class used as component directly. Decorators on subclasses are ignored. I have seen it mentioned that @Input() or @Output() are working if only the super class has them and the sub-class has none.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    So I have to write each common input in each of the customer forms :( – Silencer Feb 25 '16 at 15:38
  • 1
    Yup, I hope they improve this eventually but it's a complicated topic because it needs to be compatible with Dart and JS and must not prevent compilation for huge applications (Google intern for example). Probably not something that will be fixed for 1.0. – Günter Zöchbauer Feb 25 '16 at 15:41
  • 1
    Got burnt by this -- pretty cryptic error messages too, complaining about a field that is undefined. Eventually found out that decorators are not inherited unless there are no decorators on the child class. Seems like the decorators information are just added to the class definition, overriding any in the superclass, and the search for decorators do not follow down the chain. Anyhow, the only way is to COPY all the decorated properties from the superclass to the child class. – Stephen Chung May 08 '16 at 08:37
  • 1
    I think this is a pity. Inheriting these decorators feels somewhat natural which I feel can prove to be a very good architectural bonus – Jente Rosseel Sep 12 '16 at 14:52
  • 1
    Perhaps for later versions. I got the impression they just don't consider it a show stopper for 2.0 release. – Günter Zöchbauer Sep 12 '16 at 14:53
  • 1
    Consider changing the accepted answer, [there is a workaround](http://stackoverflow.com/a/39441608/227299) that is better than duplicating all the @Inputs – Ruan Mendes Sep 27 '16 at 17:35
8

One strategy that I am following is something like this:

@Component({
    selector: 'my-component',
    template: `......`,
    inputs: MyAbstractComponent.genericInputs
})
export class MyComponent extends MyAbstractComponent {

    @Input() width: number = 200;
    ........
}

where:

export abstract class MyAbstractComponent {
    public static genericInputs : string[] = ['base'];
    public base: String;
}

therefore, MyComponent would get base as well as width bindings. In fact, I think there is still room for improvement by using reflection.

lqbweb
  • 1,684
  • 3
  • 19
  • 33
5

Even in Angular 4.2.4 it works fine in dev mode. But when doing a prod build (ng build -prod) it fails:

ERROR in Template parse errors:
Can't bind to 'step' since it isn't a known property of 'app-textarea-step'.
1. If 'app-textarea-step' is an Angular component and it has 'step' input, 
then verify that it is part of this module.
2. If 'app-textarea-step' is a Web Component then add 
'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to 
suppress this message.

My component looks like:

abstract class StepComponent {
  @Input() step: BaseStep;
  @Output() next = new EventEmitter<string>();
  @Output() answer = new EventEmitter<Answer>();
}

abstract class SingleNextStepComponent extends StepComponent {

  onSubmit(answer: string) {
    // ConfirmStep heeft geen answer.
    if (answer) {
      this.answer.emit({ question: this.step.key, value: answer });
    }
    const step = this.step as SingleNextStep;
    this.next.emit(step.next);
  }
}

// Decorator inheritance works in standard build (ng build) but fails in production build (ng build -prod)
// Workaround: inputs element on @Component that contains the inputs.....
@Component({
  selector: 'app-textbox-step',
  templateUrl: './textbox-step.component.html',
  inputs: ['step']
})
export class TextboxStepComponent extends SingleNextStepComponent { }

@Component({
  selector: 'app-textarea-step',
  templateUrl: './textarea-step.component.html',
})
export class TextareaStepComponent extends SingleNextStepComponent { }

Fortunately the workaround works. The inputs a added to the TextBoxStepComponent have prevented this one to fail, falling through to the next one, not yet provided with 'inputs'.

But 'ng build' works fine without needing inputs on the @Component decorators...

msanford
  • 11,803
  • 11
  • 66
  • 93
Marc
  • 59
  • 1
  • 1
2

I came across this question, and just wanted to point out that as of Angular 2.3.0-rc.0 this is actually possible.

Inheritance Semantics:

Decorators:

1) list the decorators of the class and its parents in the ancestor first order

2) only use the last decorator of each kind (e.g. @Component / ...)

Constructor parameters:

If a class inherits from a parent class and does not declare a constructor, it inherits the parent class constructor, and with it the parameter metadata of that parent class.

Lifecycle hooks:

Follow the normal class inheritance model, i.e. lifecycle hooks of parent classes will be called even if the method is not overwritten in the child class.

https://github.com/angular/angular/commit/f5c8e09

gamesmad
  • 399
  • 1
  • 2
  • 14
1

I found a possible solution here:

https://medium.com/@ttemplier/angular2-decorators-and-class-inheritance-905921dbd1b7#.ca68alvcz

Basically, it creates a custom Decorator that simply merges parent and child decorators through reflection.

Actually I coudn't make it work on projects baseed on angular-cli.

1

Decorators are not inherited, but class are. So my solution was this:

@Component({selector: 'a')
class A {
  @Input() field;
}

@Component({selector: 'b', inputs: ['field']}
class B extends A {
}
Alexandre N.
  • 2,594
  • 2
  • 28
  • 32