好好爱自己!

【转】angular 自定义 component decorator

https://netbasal.com/inspiration-for-custom-decorators-in-angular-95aeb87f072c

 

以下代码使用了 class decorator, method decorator, property decorator

 

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { environment } from 'src/environments/environment';
import { Component, OnChanges, Input } from '@angular/core';
import { AppModule } from 'src/app/app.module';
import { Observable } from 'rxjs';
 
export function NgLog(): ClassDecorator {
  return (constructor: any) => {
    console.log('cons:', constructor);
    if (!environment.production) {
      // You can add/remove events for your needs
      const LIFECYCLE_HOOKS = [
        'ngOnInit',
        'ngOnChanges',
        'ngOnDestroy'
      ];
      const component = constructor.name;
 
      LIFECYCLE_HOOKS.forEach(hook => {
        console.log('123:',hook);
        const original = constructor.prototype[hook];
 
        constructor.prototype[hook] = function (...args) {
          console.log(`%c ${component} - ${hook}`, `color: #4CAF50; font-weight: bold`, ...args, constructor.prototype);
          original && original.apply(this, args);
        }
      });
    }
 
  }
}
 
export function PageTrack(pageName: string): ClassDecorator {
 
  return function (constructor: any) {
    // const analyticsService = AppModule.injector.get(AnalyticsService);
 
    const ngOnInit = constructor.prototype.ngOnInit;
 
    constructor.prototype.ngOnInit = function (...args) {
      // analyticsService.visit(pageName);
      console.log(`%c visit: ${pageName}`, `color: #4CAF50; font-weight: bold`);
      ngOnInit && ngOnInit.apply(this, args);
    }
 
    const ngOnDestroy = constructor.prototype.ngOnDestroy;
 
    constructor.prototype.ngOnDestroy = function (...args) {
      // analyticsService.leave(pageName);
      console.log(`%c leave: ${pageName}`, `color: green; font-weight: bold`);
      ngOnDestroy && ngOnDestroy.apply(this, args);
    }
 
  }
}
 
 
export function log$(target: any, propertyKey: string) {
  let propertyValue;
 
  function getter() {
    return propertyValue;
  }
 
  function setter(value: any) {
    if (value instanceof Observable) {
      propertyValue = value.forEach(res => {
        const isArrayOfObjects = Array.isArray(res) && typeof res[0] === 'object';
        const logType = isArrayOfObjects ? 'table' : 'log';
        console.groupCollapsed(propertyKey);
        console[logType](res);
        console.groupEnd();
      });
    } else {
      console.error('none observable', value);
      propertyValue = value;
    }
  }
 
  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}
 
 
export function throttle(milliseconds: number = 500): MethodDecorator {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    descriptor.value = () => {
      console.error('throttle...........');
    }
    return descriptor;
  };
}
 
 
@Component({
  selector: 'fly-aa',
  template: `
    <div >zzzzz {{zz}}</div>
  `
})
@NgLog()
@PageTrack("fly")
export class FlyComponent implements OnChanges {
  @Input() @log$ zz: string;
   name = 'Angular';
    
  mm() {
    console.log('m');
  }
 
  @throttle()
  ngOnChanges(ss) {
    console.log('cha:', ss);
  }
  ngOnDestory(ss) {
    console.log('destroy:', ss);
  }
}

  

 

 

 

-------------------------------------------------------------

 

This post assumes that you at least have some working knowledge of Angular and Decorators.

If you have no prior knowledge on the subject, you can read the following articles:

NgLog Class Decorator

This decorator will be helpful for debugging purposes. We will log the ngOnInitngOnDestroy and ngOnChanges lifecycle hooks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { environment } from "../environments/environment";
export function NgLog() : ClassDecorator {
  return function ( constructor : any ) {
    if( !environment.production ) {
      // You can add/remove events for your needs
      const LIFECYCLE_HOOKS = [
        'ngOnInit',
        'ngOnChanges',
        'ngOnDestroy'
      ];
      const component = constructor.name;
 
      LIFECYCLE_HOOKS.forEach(hook => {
        const original = constructor.prototype[hook];
 
        constructor.prototype[hook] = function ( ...args ) {
          console.log(`%c ${component} - ${hook}`, `color: #4CAF50; font-weight: bold`, ...args);
          original && original.apply(this, args);
        }
      });
    }
 
  }
}

  

We just log the hook and calling the original method. Let’s use the decorator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component({
  selector: 'posts-page',
  template: `
    <posts [posts]="posts$ | async"></posts>
  `
})
@NgLog()
export class PostsPageComponent {
  constructor( private store : Store<any> ) {
    this.posts$ = store.select('posts');
  }
}
 
     
@Component({
  selector: 'posts',
  template: `
    <p *ngFor="let post of posts">{{post.title}}</p>
  `
})
@NgLog()
export class PostsComponent implements OnInit {
  @Input() posts = [];
}

  

 

 

Throttle Method Decorator

This decorator will be helpful when working for example with scroll events.

1
2
3
4
5
6
7
8
9
import t from 'lodash.throttle';
 
export function throttle( milliseconds : number = 500 ) : MethodDecorator {
  return function ( target : any, propertyKey : string, descriptor : PropertyDescriptor ) {
    const original = descriptor.value;
    descriptor.value = t(original, milliseconds);
    return descriptor;
  };
}

  

We are using the throttle helper from lodash and replacing the original method with our “throttle” version. Let’s use the decorator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component({
  selector: 'app-posts-page',
  template: `
     <posts [posts]="posts$ | async"></posts>
  `
})
export class PostsPageComponent {
  constructor( private store : Store<any> ) {
    this.posts$ = store.select('posts');
  }
 
  @HostListener('document:scroll')
  @throttle()
  scroll() {
    console.log('scroll');
  }
 
}

  

 

 

Track Page Class Decorator

This decorator will be helpful when you need to report page visits to your analytics provider.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { AnalyticsService, AppModule } from "./app.module";
export function PageTrack( pageName : string ): ClassDecorator {
 
  return function ( constructor : any ) {
    const analyticsService = AppModule.injector.get(AnalyticsService);
 
    const ngOnInit = constructor.prototype.ngOnInit;
 
    constructor.prototype.ngOnInit = function ( ...args ) {
      analyticsService.visit(pageName);
      ngOnInit && ngOnInit.apply(this, args);
    }
 
    const ngOnDestroy = constructor.prototype.ngOnDestroy;
 
    constructor.prototype.ngOnDestroy = function ( ...args ) {
      analyticsService.leave(pageName);
      ngOnDestroy && ngOnDestroy.apply(this, args);
    }
 
  }
}

  

Based on the hook we can call the appropriate method from our Analytics service then invoke the original method.

1
2
3
4
5
6
7
8
9
10
11
@Component({
  ...
})
@PageTrack('blog')
export class PostsPageComponent {
 
  constructor( private store : Store<any> ) {
    this.posts$ = store.select('posts').pluck('data');
  }
 
}

 

 

  

Log Observable Property Decorator

This decorator will be helpful for debugging purposes. You can also achieve the same thing with a custom operator.

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
export function log$( target : any, propertyKey : string ) {
  let propertyValue;
 
  function getter() {
    return propertyValue;
  }
 
  function setter( value : any ) {
    if( value instanceof Observable ) {
      propertyValue = value.do(res => {
        const isArrayOfObjects = Array.isArray(res) && typeof res[0] === 'object';
        const logType = isArrayOfObjects ? 'table' : 'log';
        console.groupCollapsed(propertyKey);
        console[logType](res)
        console.groupEnd();
      });
    } else {
      propertyValue = value;
    }
  }
 
  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

  

We can get the observable and add the do operator to log the value. Based on the value we can choose between console.table or console.log .

Let’s use the decorator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component({
  selector: 'posts-page',
  template: `
      <posts [posts]="posts$ | async"></posts>
  `
})
export class PostsPageComponent implements OnInit {
 
  @log$ posts$ : Observable<Post[]>;
     
  constructor( private store : Store<any> ) {
    this.posts$ = store.select('posts');
  }
 
}

  

 

 

Conclusion:

You can leverage decorators in your apps and create powerful things with them. Decorators are not only for frameworks or libraries, so be creative and start using them.

Follow me on Medium or Twitter to read more about Angular, JS and Vue.

posted @   立志做一个好的程序员  阅读(247)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
历史上的今天:
2020-02-02 SSH -R 反向端口转发---通过一台阿里云的服务器来访问公司内部网络种的电脑
2020-02-02 ubuntu桌面系统rdesktop远程到win10报错

不断学习创作,与自己快乐相处

点击右上角即可分享
微信分享提示