UNPKG

angular2

Version:

Angular 2 - a web framework for modern web apps

241 lines (240 loc) 8.24 kB
import { global, isFunction, stringify } from 'angular2/src/facade/lang'; var _nextClassId = 0; function extractAnnotation(annotation) { if (isFunction(annotation) && annotation.hasOwnProperty('annotation')) { // it is a decorator, extract annotation annotation = annotation.annotation; } return annotation; } function applyParams(fnOrArray, key) { if (fnOrArray === Object || fnOrArray === String || fnOrArray === Function || fnOrArray === Number || fnOrArray === Array) { throw new Error(`Can not use native ${stringify(fnOrArray)} as constructor`); } if (isFunction(fnOrArray)) { return fnOrArray; } else if (fnOrArray instanceof Array) { var annotations = fnOrArray; var fn = fnOrArray[fnOrArray.length - 1]; if (!isFunction(fn)) { throw new Error(`Last position of Class method array must be Function in key ${key} was '${stringify(fn)}'`); } var annoLength = annotations.length - 1; if (annoLength != fn.length) { throw new Error(`Number of annotations (${annoLength}) does not match number of arguments (${fn.length}) in the function: ${stringify(fn)}`); } var paramsAnnotations = []; for (var i = 0, ii = annotations.length - 1; i < ii; i++) { var paramAnnotations = []; paramsAnnotations.push(paramAnnotations); var annotation = annotations[i]; if (annotation instanceof Array) { for (var j = 0; j < annotation.length; j++) { paramAnnotations.push(extractAnnotation(annotation[j])); } } else if (isFunction(annotation)) { paramAnnotations.push(extractAnnotation(annotation)); } else { paramAnnotations.push(annotation); } } Reflect.defineMetadata('parameters', paramsAnnotations, fn); return fn; } else { throw new Error(`Only Function or Array is supported in Class definition for key '${key}' is '${stringify(fnOrArray)}'`); } } /** * Provides a way for expressing ES6 classes with parameter annotations in ES5. * * ## Basic Example * * ``` * var Greeter = ng.Class({ * constructor: function(name) { * this.name = name; * }, * * greet: function() { * alert('Hello ' + this.name + '!'); * } * }); * ``` * * is equivalent to ES6: * * ``` * class Greeter { * constructor(name) { * this.name = name; * } * * greet() { * alert('Hello ' + this.name + '!'); * } * } * ``` * * or equivalent to ES5: * * ``` * var Greeter = function (name) { * this.name = name; * } * * Greeter.prototype.greet = function () { * alert('Hello ' + this.name + '!'); * } * ``` * * ### Example with parameter annotations * * ``` * var MyService = ng.Class({ * constructor: [String, [new Query(), QueryList], function(name, queryList) { * ... * }] * }); * ``` * * is equivalent to ES6: * * ``` * class MyService { * constructor(name: string, @Query() queryList: QueryList) { * ... * } * } * ``` * * ### Example with inheritance * * ``` * var Shape = ng.Class({ * constructor: (color) { * this.color = color; * } * }); * * var Square = ng.Class({ * extends: Shape, * constructor: function(color, size) { * Shape.call(this, color); * this.size = size; * } * }); * ``` */ export function Class(clsDef) { var constructor = applyParams(clsDef.hasOwnProperty('constructor') ? clsDef.constructor : undefined, 'constructor'); var proto = constructor.prototype; if (clsDef.hasOwnProperty('extends')) { if (isFunction(clsDef.extends)) { constructor.prototype = proto = Object.create(clsDef.extends.prototype); } else { throw new Error(`Class definition 'extends' property must be a constructor function was: ${stringify(clsDef.extends)}`); } } for (var key in clsDef) { if (key != 'extends' && key != 'prototype' && clsDef.hasOwnProperty(key)) { proto[key] = applyParams(clsDef[key], key); } } if (this && this.annotations instanceof Array) { Reflect.defineMetadata('annotations', this.annotations, constructor); } if (!constructor['name']) { constructor['overriddenName'] = `class${_nextClassId++}`; } return constructor; } var Reflect = global.Reflect; // Throw statement at top-level is disallowed by closure compiler in ES6 input. // Wrap in an IIFE as a work-around. (function checkReflect() { if (!(Reflect && Reflect.getMetadata)) { throw 'reflect-metadata shim is required when using class decorators'; } })(); export function makeDecorator(annotationCls, chainFn = null) { function DecoratorFactory(objOrType) { var annotationInstance = new annotationCls(objOrType); if (this instanceof annotationCls) { return annotationInstance; } else { var chainAnnotation = isFunction(this) && this.annotations instanceof Array ? this.annotations : []; chainAnnotation.push(annotationInstance); var TypeDecorator = function TypeDecorator(cls) { var annotations = Reflect.getOwnMetadata('annotations', cls); annotations = annotations || []; annotations.push(annotationInstance); Reflect.defineMetadata('annotations', annotations, cls); return cls; }; TypeDecorator.annotations = chainAnnotation; TypeDecorator.Class = Class; if (chainFn) chainFn(TypeDecorator); return TypeDecorator; } } DecoratorFactory.prototype = Object.create(annotationCls.prototype); return DecoratorFactory; } export function makeParamDecorator(annotationCls) { function ParamDecoratorFactory(...args) { var annotationInstance = Object.create(annotationCls.prototype); annotationCls.apply(annotationInstance, args); if (this instanceof annotationCls) { return annotationInstance; } else { ParamDecorator.annotation = annotationInstance; return ParamDecorator; } function ParamDecorator(cls, unusedKey, index) { var parameters = Reflect.getMetadata('parameters', cls); parameters = parameters || []; // there might be gaps if some in between parameters do not have annotations. // we pad with nulls. while (parameters.length <= index) { parameters.push(null); } parameters[index] = parameters[index] || []; var annotationsForParam = parameters[index]; annotationsForParam.push(annotationInstance); Reflect.defineMetadata('parameters', parameters, cls); return cls; } } ParamDecoratorFactory.prototype = Object.create(annotationCls.prototype); return ParamDecoratorFactory; } export function makePropDecorator(decoratorCls) { function PropDecoratorFactory(...args) { var decoratorInstance = Object.create(decoratorCls.prototype); decoratorCls.apply(decoratorInstance, args); if (this instanceof decoratorCls) { return decoratorInstance; } else { return function PropDecorator(target, name) { var meta = Reflect.getOwnMetadata('propMetadata', target.constructor); meta = meta || {}; meta[name] = meta[name] || []; meta[name].unshift(decoratorInstance); Reflect.defineMetadata('propMetadata', meta, target.constructor); }; } } PropDecoratorFactory.prototype = Object.create(decoratorCls.prototype); return PropDecoratorFactory; }