装饰器在Angular中是个很常见的概念,@Component
、@Directive
、@Pipe
、@NgModule
、@Injectable
、@Input
、@Inject
等等.本文着重讲解各种装饰器的作用以及在Angular中的实际使用
装饰工厂 装饰器工厂只是一个函数,它返回将由装饰器在运行时调用的表达式
1 2 3 4 5 6 7 8 function color(value: string) { // this is the decorator factory, it sets up // the returned decorator function return function (target) { // this is the decorator // do something with 'target' and 'value'... }; }
类装饰 类型注释:
1 2 type ClassDecorator = <TFunction extends Function> (target: TFunction) => TFunction | void;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function toString() { return function <T extends { new (...args: any[]): any }>(constructor: T) { return class extends constructor { toString() { return JSON.stringify(this); } }; }; } @toString() class C { public foo = "foo"; public num = 24; } console.log(new C().toString()) // -> {"foo":"foo","num":24}
方法装饰 类型注释:
1 2 3 4 5 type MethodDecorator = <T>( target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T> ) => TypedPropertyDescriptor<T> | void;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function logger(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = function (...args) { console.log('params: ', ...args); const result = original.call(this, ...args); console.log('result: ', result); return result; } } class C { @logger add(x: number, y:number ) { return x + y; } } const c = new C(); c.add(1, 2); // -> params: 1, 2 // -> result: 3
属性装饰 类型注释:
1 2 type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Greeter { @format("Hello, %s") greeting: string; constructor(message: string) { this.greeting = message; } } function format(value:string){ return function(target:any,propertyKey:string){ return { configurable: true, writable: true, value:value }; } }
访问器装饰 访问器装饰器通常与方法装饰器相同,唯一的区别是描述符中的键:
方法装饰器中的描述符具有键:
value
writable
enumerable
configurable
访问器装饰器中的描述符具有键:
get
set
enumerable
configurable
参数装饰 类型注释:
1 2 3 4 5 type ParameterDecorator = ( target: Object, propertyKey: string | symbol, parameterIndex: number ) => void;
@参数:
1.target
:类的构造函数或实例成员的类的原型
2.propertyKey
:属性名称(方法名,不是参数名)
3.parameterIndex
:函数参数列表中参数的序号索引
@Returns
返回值将被忽略。
实例 有如下的button组件,在传入danger输入属性时,根据不同的值,传入不同的值以此来改变按钮样式,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 .. @Component({ selector: 'button[my-button]', exportAs: 'myButton', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, preserveWhitespaces: false, template: ` <ng-content></ng-content> `, host: { class: 'btn', '[class.btn-dangerous]': `danger`, //danger参数决定是否加载样式 }, }) export class ButtonComponent { @Input() danger : boolean= false; }
实际使用时,组件的使用:<button my-button [danger]="true">btn</button>
,danger的入参过于繁琐,通过给danger添加装饰器来达到将不符合的类型值转化为布尔值;已达到<button my-button danger>btn</button>
的效果
定义参数装饰器,引入@angular/cdk
的coerceBooleanProperty
方法将值转换为布尔;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function InputBoolean(){ return function ( target: any,propertyKey: string){ const privatePropName = `$$__private__${propName}`; //创建新的属性并设置属性描述 Object.defineProperty(target, privatePropName, { configurable: true, writable: true, }); /** * 返回新的属性描述 * 来达到如下效果 * // @Input() * // get visible() { return this.__visible; } * // set visible(value) { this.__visible = value; } * // __visible = false; */ return { get(): string { this[privatePropName]; }, set(value: T): void { this[privatePropName] = coerceBooleanProperty(value); }, }; } }
之后使用定义好的装饰器,此时使用组件并设置danger参数时,angular会有Type 'string' is not assignable to type 'boolean'
的报错
1 2 3 4 5 ... export class ButtonComponent { @Input() @InputBoolean() danger : boolean= false; }
1 2 //danger会提示报错 <button my-button danger>btn</button>
此时通过添加ngAcceptInputType_
前缀来放宽入参的类型
1 2 3 4 5 6 7 8 9 10 export type BooleanInput = boolean | string | undefined | null; ... export class ButtonComponent { static ngAcceptInputType_danger: BooleanInput; @Input() @InputBoolean() danger : boolean= false; }
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 //button.component.js ... @Component({ selector: 'button[my-button]', exportAs: 'myButton', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, preserveWhitespaces: false, template: ` <ng-content></ng-content> `, host: { class: 'ant-btn', '[class.ant-btn-dangerous]': `danger`, }, }) export class ButtonComponent { static ngAcceptInputType_danger: BooleanInput; @Input() @InputBoolean() danger = false; constructor() {} }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 //util/convert.ts ... import { coerceBooleanProperty, coerceCssPixelValue, _isNumberValue, } from '@angular/cdk/coercion'; export function toBoolean(value: boolean | string): boolean { return coerceBooleanProperty(value); } function propDecoratorFactory<T, D>( name: string, fallback: (v: T) => D ): (target: any, propName: string) => void { function propDecorator( target: any, propertyKey: string, originalDescriptor?: TypedPropertyDescriptor<any> ): any { const privatePropName = `$$__private__${propertyKey}`; if (Object.prototype.hasOwnProperty.call(target, privatePropName)) { console.warn( `The prop "${privatePropName}" is already exist, it will be overrided by ${name} decorator.` ); } Object.defineProperty(target, privatePropName, { configurable: true, writable: true, }); return { get(): string { return originalDescriptor && originalDescriptor.get ? originalDescriptor.get.bind(this)() : this[privatePropName]; }, set(value: T): void { if (originalDescriptor && originalDescriptor.set) { originalDescriptor.set.bind(this)(fallback(value)); } this[privatePropName] = fallback(value); }, }; } return propDecorator; } export function InputBoolean(): any { return propDecoratorFactory('InputBoolean', toBoolean); }
1 2 3 //convert-input.ts export type BooleanInput = boolean | string | undefined | null;