Solvedangular Inheritance for Angular decorators is not well-defined

Currently, when classes with decorators are used in an inheritance chain, the results are undefined.

Consider the following two components:

@Component({selector: 'a', inputs: ['metaFoo'])
class A {
  @Input() fieldFoo;
  metaFoo;
}

@Component({selector: 'b', inputs: ['metaBar']}
class B extends A {
  @Input() fieldBar;
  metaBar;
}

Currently, using component B in JIT mode does not cause an error, but only inputs fieldBar and metaBar will be understood by Angular. This is because the decorators result in properties being defined on the class functions, and those properties are subject to shadowing via prototypical inheritance. So B.annotations (where the @Component metadata lives) shadows A.annotations, and B.propMetadata (where the @Inputs live) shadows A.propMetadata.

If the @Input() fieldBar from B is removed, however, then B will have no property metadata, and so the lookup forB.propMetadata will reach A.propMetadata via the prototype chain and suddenly the fieldFoo input will become visible to Angular.

There is no provision in the spec for inheritance of decorator metadata. However, Angular could be made to support well-defined rules of inheritance for its own decorators.

Here is one set of rules which I believe covers most of the cases where users attempt to inherit components:

  • Class-level decorators (@Component and friends) have their metadata merged according to the merge rules:
    • Metadata objects are merged property by property.
    • For properties with primitive values, a subclass value, if present, overwrites a superclass value.
    • For properties with array values, the arrays are concatenated, superclass first.
    • If the subclass provides no value, the superclass value, if present, is used.
  • Property-level decorators (@Input, etc) on superclasses are included in compilation.
    • @Inputs with the same name on the superclass and subclass should have the same behavior as two @Inputs with the same name on the same class.

Applying these rules would change the behavior of the above example, so component B would have all 4 defined inputs recognized.

For JIT mode, this requires two changes to Angular.

First, the class-level decorators would be changed to check for metadata on the superclass, and perform the merging operation if present.

Secondly, the runtime compiler would need to consider superclass metadata when checking for property annotations.

For the AOT compiler, the semantics would have to be built into the compiler itself as the decorators don't execute during AOT compilation.

32 Answers

✔️Accepted Answer

I found a few that seem related:

Other Answers:

Use-Case: Template switching by selector

class ItemView {
  @Input() item;

  doSomething() {}
}

@Component({
  selector: 'item-view[type=grid]', templateUrl: '...' 
})
class GridItemView extends ItemView {}

@Component({
  selector: 'item-view[type=list]', templateUrl: '...' 
})
class ListItemView extends ItemView {}

@aluanhaddad no problem. And yes, we are working hard on making our AOT story better (watch mode, better errors, ...)

Lifecycle methods from superclasses still not called in AOT compiled code (tested with 4.3.6 and 4.1.1), I found #12922 but it was closed and redirected here, not sure if you need another bug for this issue.

Thanks @zoechi !

More Issues: