@Input and other decorators and inheritance

19k Views Asked by At

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 :(

6

There are 6 best solutions below

6
On BEST ANSWER

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.

0
On

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

1
On

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...

0
On

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 {
}
0
On

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.

2
On

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.