feat(core): properly support inheritance

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

## Example

E.g. the following is a valid use of inheritance and it will
also inherit all metadata:

```
@Directive({selector: 'someDir'})
class ParentDirective {
  constructor(someDep: SomeDep) {}

  ngOnInit() {}
}

class ChildDirective extends ParentDirective {}
```

Closes #11606
Closes #12892
This commit is contained in:
Tobias Bosch
2016-11-18 15:17:44 -08:00
committed by vsavkin
parent 4a09251921
commit f5c8e0989d
19 changed files with 1102 additions and 260 deletions
+3 -2
View File
@@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {makeParamDecorator} from '../util/decorators';
import {makeDecorator, makeParamDecorator} from '../util/decorators';
/**
* Type of the Inject decorator / constructor function.
@@ -150,7 +151,7 @@ export interface Injectable {}
* @stable
* @Annotation
*/
export const Injectable: InjectableDecorator = makeParamDecorator('Injectable', []);
export const Injectable: InjectableDecorator = <InjectableDecorator>makeDecorator('Injectable', []);
/**
* Type of the Self decorator / constructor function.
@@ -12,6 +12,12 @@ import {Type} from '../type';
import {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
import {GetterFn, MethodFn, SetterFn} from './types';
/**
* Attention: This regex has to hold even if the code is minified!
*/
export const DELEGATE_CTOR =
/^function\s+\S+\(\)\s*{\s*("use strict";)?\s*(return\s+)?\S+\.apply\(this,\s*arguments\)/;
export class ReflectionCapabilities implements PlatformReflectionCapabilities {
private _reflect: any;
@@ -49,15 +55,26 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
return result;
}
parameters(type: Type<any>): any[][] {
private _ownParameters(type: Type<any>, parentCtor: any): any[][] {
// If we have no decorators, we only have function.length as metadata.
// In that case, to detect whether a child class declared an own constructor or not,
// we need to look inside of that constructor to check whether it is
// just calling the parent.
// This also helps to work around for https://github.com/Microsoft/TypeScript/issues/12439
// that sets 'design:paramtypes' to []
// if a class inherits from another class but has no ctor declared itself.
if (DELEGATE_CTOR.exec(type.toString())) {
return null;
}
// Prefer the direct API.
if ((<any>type).parameters) {
if ((<any>type).parameters && (<any>type).parameters !== parentCtor.parameters) {
return (<any>type).parameters;
}
// API of tsickle for lowering decorators to properties on the class.
const tsickleCtorParams = (<any>type).ctorParameters;
if (tsickleCtorParams) {
if (tsickleCtorParams && tsickleCtorParams !== parentCtor.ctorParameters) {
// Newer tsickle uses a function closure
// Retain the non-function case for compatibility with older tsickle
const ctorParameters =
@@ -70,20 +87,35 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
// API for metadata created by invoking the decorators.
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
const paramAnnotations = this._reflect.getMetadata('parameters', type);
const paramTypes = this._reflect.getMetadata('design:paramtypes', type);
if (isPresent(this._reflect) && isPresent(this._reflect.getOwnMetadata)) {
const paramAnnotations = this._reflect.getOwnMetadata('parameters', type);
const paramTypes = this._reflect.getOwnMetadata('design:paramtypes', type);
if (paramTypes || paramAnnotations) {
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
}
}
// The array has to be filled with `undefined` because holes would be skipped by `some`
// If a class has no decorators, at least create metadata
// based on function.length.
// Note: We know that this is a real constructor as we checked
// the content of the constructor above.
return new Array((<any>type.length)).fill(undefined);
}
annotations(typeOrFunc: Type<any>): any[] {
parameters(type: Type<any>): any[][] {
// Note: only report metadata if we have at least one class decorator
// to stay in sync with the static reflector.
const parentCtor = Object.getPrototypeOf(type.prototype).constructor;
let parameters = this._ownParameters(type, parentCtor);
if (!parameters && parentCtor !== Object) {
parameters = this.parameters(parentCtor);
}
return parameters || [];
}
private _ownAnnotations(typeOrFunc: Type<any>, parentCtor: any): any[] {
// Prefer the direct API.
if ((<any>typeOrFunc).annotations) {
if ((<any>typeOrFunc).annotations && (<any>typeOrFunc).annotations !== parentCtor.annotations) {
let annotations = (<any>typeOrFunc).annotations;
if (typeof annotations === 'function' && annotations.annotations) {
annotations = annotations.annotations;
@@ -92,21 +124,27 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
// API of tsickle for lowering decorators to properties on the class.
if ((<any>typeOrFunc).decorators) {
if ((<any>typeOrFunc).decorators && (<any>typeOrFunc).decorators !== parentCtor.decorators) {
return convertTsickleDecoratorIntoMetadata((<any>typeOrFunc).decorators);
}
// API for metadata created by invoking the decorators.
if (this._reflect && this._reflect.getMetadata) {
const annotations = this._reflect.getMetadata('annotations', typeOrFunc);
if (annotations) return annotations;
if (this._reflect && this._reflect.getOwnMetadata) {
return this._reflect.getOwnMetadata('annotations', typeOrFunc);
}
return [];
}
propMetadata(typeOrFunc: any): {[key: string]: any[]} {
annotations(typeOrFunc: Type<any>): any[] {
const parentCtor = Object.getPrototypeOf(typeOrFunc.prototype).constructor;
const ownAnnotations = this._ownAnnotations(typeOrFunc, parentCtor) || [];
const parentAnnotations = parentCtor !== Object ? this.annotations(parentCtor) : [];
return parentAnnotations.concat(ownAnnotations);
}
private _ownPropMetadata(typeOrFunc: any, parentCtor: any): {[key: string]: any[]} {
// Prefer the direct API.
if ((<any>typeOrFunc).propMetadata) {
if ((<any>typeOrFunc).propMetadata &&
(<any>typeOrFunc).propMetadata !== parentCtor.propMetadata) {
let propMetadata = (<any>typeOrFunc).propMetadata;
if (typeof propMetadata === 'function' && propMetadata.propMetadata) {
propMetadata = propMetadata.propMetadata;
@@ -115,7 +153,8 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
// API of tsickle for lowering decorators to properties on the class.
if ((<any>typeOrFunc).propDecorators) {
if ((<any>typeOrFunc).propDecorators &&
(<any>typeOrFunc).propDecorators !== parentCtor.propDecorators) {
const propDecorators = (<any>typeOrFunc).propDecorators;
const propMetadata = <{[key: string]: any[]}>{};
Object.keys(propDecorators).forEach(prop => {
@@ -125,11 +164,32 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
// API for metadata created by invoking the decorators.
if (this._reflect && this._reflect.getMetadata) {
const propMetadata = this._reflect.getMetadata('propMetadata', typeOrFunc);
if (propMetadata) return propMetadata;
if (this._reflect && this._reflect.getOwnMetadata) {
return this._reflect.getOwnMetadata('propMetadata', typeOrFunc);
}
return {};
}
propMetadata(typeOrFunc: any): {[key: string]: any[]} {
const parentCtor = Object.getPrototypeOf(typeOrFunc.prototype).constructor;
const propMetadata: {[key: string]: any[]} = {};
if (parentCtor !== Object) {
const parentPropMetadata = this.propMetadata(parentCtor);
Object.keys(parentPropMetadata).forEach((propName) => {
propMetadata[propName] = parentPropMetadata[propName];
});
}
const ownPropMetadata = this._ownPropMetadata(typeOrFunc, parentCtor);
if (ownPropMetadata) {
Object.keys(ownPropMetadata).forEach((propName) => {
const decorators: any[] = [];
if (propMetadata.hasOwnProperty(propName)) {
decorators.push(...propMetadata[propName]);
}
decorators.push(...ownPropMetadata[propName]);
propMetadata[propName] = decorators;
});
}
return propMetadata;
}
hasLifecycleHook(type: any, lcProperty: string): boolean {
+2 -2
View File
@@ -262,7 +262,7 @@ export function makeDecorator(
const metaCtor = makeMetadataCtor([props]);
function DecoratorFactory(objOrType: any): (cls: any) => any {
if (!(Reflect && Reflect.getMetadata)) {
if (!(Reflect && Reflect.getOwnMetadata)) {
throw 'reflect-metadata shim is required when using class decorators';
}
@@ -327,7 +327,7 @@ export function makeParamDecorator(
return ParamDecorator;
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
const parameters: any[][] = Reflect.getMetadata('parameters', cls) || [];
const parameters: any[][] = Reflect.getOwnMetadata('parameters', cls) || [];
// there might be gaps if some in between parameters do not have annotations.
// we pad with nulls.