UNPKG

ngx-decorate

Version:

Useful decorators for Angular 2 and above

414 lines (319 loc) 11.5 kB
[![NpmVersion](https://img.shields.io/npm/v/ngx-decorate.svg?style=flat-square)](https://www.npmjs.com/package/ngx-decorate) [![Travis (.com) branch](https://img.shields.io/travis/com/Alorel/ngx-decorators/3.0.3.svg?style=flat-square)](https://travis-ci.com/Alorel/ngx-decorators) [![Coveralls github branch](https://img.shields.io/coveralls/github/Alorel/ngx-decorators/3.0.3.svg?style=flat-square)](https://coveralls.io/github/Alorel/ngx-decorators) [![CodeFactor](https://www.codefactor.io/repository/github/alorel/ngx-decorators/badge)](https://www.codefactor.io/repository/github/alorel/ngx-decorators) [![codebeat badge](https://codebeat.co/badges/b2f3eead-f17f-4b9c-8861-d226057cef30)](https://codebeat.co/projects/github-com-alorel-ngx-decorators-master) [![Greenkeeper badge](https://badges.greenkeeper.io/Alorel/ngx-decorators.svg)](https://greenkeeper.io/) Useful decorators for Angular 2 and above. Full API docs available [here](https://alorel.github.io/ngx-decorators/) ----- # Table of Contents <!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> - [Installation](#installation) - [Basic principle](#basic-principle) - [IMPORTANT](#important) - [AoT compilation notice](#aot-compilation-notice) - [Class inheritance](#class-inheritance) - [Decorators](#decorators) - [CdrProp - automatically trigger change detection](#cdrprop---automatically-trigger-change-detection) - [Complete - automatically complete a subject](#complete---automatically-complete-a-subject) - [LazySubject - create a subject lazily](#lazysubject---create-a-subject-lazily) - [SubjectSetter - mirror a property to a subject](#subjectsetter---mirror-a-property-to-a-subject) - [SubscribeTo - set a property's value from an observable](#subscribeto---set-a-propertys-value-from-an-observable) - [TrackDestroyed - set the property to true during OnDestroy](#trackdestroyed---set-the-property-to-true-during-ondestroy) - [TrackInit - set the property to true during ngOnInit](#trackinit---set-the-property-to-true-during-ngoninit) - [Unsubscribe - automatically unsubscribe from subscriptions](#unsubscribe---automatically-unsubscribe-from-subscriptions) <!-- END doctoc generated TOC please keep comment here to allow auto update --> # Installation ```bash npm install ngx-decorate ``` Or use the CDN version: ```html <script src="https://cdn.jsdelivr.net/npm/ngx-decorate@3.0.3/dist/ngx-decorators.umd.min.js"></script> ``` The AMD name is `NgxDecorate`. # Basic principle Angular apps have a lot of repetitive code - completing subjects, unsubscribing, triggering change detection etc. This library hooks into a component's `OnInit` and `OnDestroy` hooks and does all the magic for you. If your component already has an `OnInit` or `OnDestroy` hook, it will still be called. # IMPORTANT ## AoT compilation notice For the decorators to work in AoT mode the classes *must* contain the `ngOnInit` and `ngOnDestroy` methods. It is currently not possible to provide this functionality in the form of a Webpack plugin because `@ngtools/webpack` ignores loader input. Until this ceases to be the case you can use the [ngx-decorate-preprocessor](https://www.npmjs.com/package/ngx-decorate-preprocessor) tool. ## Class inheritance The decorators might not work or work incorrectly if used from within a subclass. Only use the decorators for classes that do not extend other classes. # Decorators ## CdrProp - automatically trigger change detection Before: ```typescript @Component({}) export class Foo { private _prop: string; public constructor(private readonly cdr: ChangeDetectorRef) {} public get prop(): string { return this._prop; } public set prop(v: string) { this._prop = v; this.cdr.markForCheck(); } } ``` After: ```typescript @Component({}) export class Foo { @CdrProp('cdr') public prop: string; public constructor(private readonly cdr: ChangeDetectorRef) {} } ``` API: ```typescript export declare type ConfEnum = Pick<PropertyDescriptor, 'configurable' | 'enumerable'>; export interface CdrPropConfig { /** Default value to set on the prototype */ default?: any; /** Partial property descriptor */ desc?: ConfEnum; /** * Key of a property used for tracking whether the component's been destroyed * to prevent errors caused by triggering change detection on a destroyed component. */ destroyed?: PropertyKey; } /** * Call .markForCheck() on the change detector ref whenever this property is written to. * This decorator does <b>not</b> require the class to be decorated with {@link NgxDecorate} * * @param propName Property at which the change detector can be found * @param conf Optional configuration */ export declare function CdrProp(propName: PropertyKey, conf?: CdrPropConfig): PropertyDecorator; ``` ## Complete - automatically complete a subject The property can hold either a single subject or an array of subjects. Before: ```typescript @Component({}) export class Foo implements OnDestroy { private _subjects: Subject<any>[] = []; private _oneSubject: Subject<any>; public ngOnDestroy(): void { if (this._oneSubject) { this._oneSubject.complete(); } for (const subj of this._subjects) { subj.complete(); } } } ``` After: ```typescript @NgxDecorate() @Component({}) export class Foo { @Complete() private _subjects: Subject<any>[] = []; @Complete() private _oneSubject: Subject<any>; } ``` API: ```typescript /** * Automatically completes the subjects and event emitters at this property. * The property can be either a single object or an array of objects. */ export declare function Complete(): PropertyDecorator; ``` ## LazySubject - create a subject lazily Creating subjects within a constructor on the `OnInit` hook can sometimes be wasteful if there's a chance they won't be used. Instantiate your subjects lazily and make sure you only allocate resources to what you actually need! The subject will only be created once; any further access will happen as if it were a regular property. Code: ```typescript @NgxDecorate() @Component({}) export class Foo { @LazySubject() public get someSubject(): Subject<string> { return new Subject<string>(); } } ``` Equivalent logic: ```typescript @Component({}) export class Foo implements OnDestroy { private _subjects: Subject<any>[] = []; public get someSubject(): Subject<string> { const value = new Subject<string>(); this._subjects.push(value); Object.defineProperty(this, 'someSubject', {value}); return value; } public ngOnDestroy(): void { for (const subj of this._subjects) { subj.complete(); } } } ``` API: ```typescript /** * Decorate a getter that returns a subject. The subject will automatically get completed * when the component/service/pipe is destroyed. */ export declare function LazySubject(): MethodDecorator; ``` ## SubjectSetter - mirror a property to a subject Note: Setting the default value will **not** work if the class does not support `OnInit` hooks, i.e. don't use the option on services and pipes. Additionally, the option should not be used on component inputs as the input would get overridden during the `OnInit` hook. Before: ```typescript @Component({}) export class Foo implements OnInit, OnDestroy { private _name: string; private _name$: Subject<string> = new Subject<string>(); public set name(v: string) { this._name = v; this._name$.next(v); } public get name(): string { return this._name; } public ngOnInit(): void { this.name = 'foo'; } public ngOnDestroy(): void { this._name$.complete(); } } ``` After: ```typescript @NgxDecorate() @Component({}) export class Foo { @SubjectSetter('_name$', {default: 'foo'}) public name: string; @Complete() private _name$: Subject<string> = new Subject<string>(); } ``` API (see ConfEnum docs under [@CdrProp](#cdrprop---automatically-trigger-change-detection)): ```typescript export interface SubjectSetterConfig { /** Default value to set on the prototype */ default?: any; /** Partial property descriptor */ desc?: ConfEnum; } /** * Mark the property as a subject setter. When written to, it will call .next(value) on the subject. * The decorator's <b>default</b> config option only works on items that make use of the OnInit hook, * i.e. it will <b>not</b> work for services and pipes. * * Because the <b>default</b> config option utilises the OnInit hook, it should not be used on * properties that are component inputs as the input would get overridden during the hook. * * @param subjectPropName Name of the property at which the subject resides * @param conf Optional configuration */ export declare function SubjectSetter(subjectPropName: PropertyKey, conf?: SubjectSetterConfig): PropertyDecorator; ``` ## SubscribeTo - set a property's value from an observable Effectively the opposite of `@SubjectSetter` Before: ```typescript @Component({}) export class Foo implements OnInit, OnDestroy { public prop: SomeInterface; private prop$: Observable<SomeInterface>; private _propSubscription: Subscription; public constructor(private svc: SomeService, private cdr: ChangeDetectorRef) {} public ngOnInit(): void { this.prop$ = this.svc.getSomeInterface(); this._propSubscription = this.prop$.subscribe((v: SomeInterface) => { this.prop = v; this.cdr.markForCheck(); }); } public ngOnDestroy(): void { if (this._propSubscription) { this._propSubscription.unsubscribe(); } } } ``` After: ```typescript @NgxDecorate() @Component({}) export class Foo implements OnInit { @SubscribeTo('prop$', {cdrProp: 'cdr'}) public prop: SomeInterface; public constructor(private svc: SomeService, private cdr: ChangeDetectorRef) {} public ngOnInit(): void { this.prop$ = this.svc.getSomeInterface(); } } ``` API: ```typescript export interface SubscribeToConfig { /** Property at which the change detector resides */ cdrProp?: PropertyKey; } /** * Subscribe to an observable and set its last emitted value to this property * * This decorator requires the ngOnInit hook and therefore will only work with directives and components. * * @param prop Property at which the observable resides * @param cfg Optional configuration */ export declare function SubscribeTo(prop: PropertyKey, cfg?: SubscribeToConfig): PropertyDecorator; ``` ## TrackDestroyed - set the property to true during OnDestroy This could potentially be used with the `@CdrProp()` decorator. Before: ```typescript @Component({}) export class Foo implements OnDestroy { private _destroyed: boolean; public ngOnDestroy(): void { this._destroyed = true; } } ``` After: ```typescript @NgxDecorate() @Component({}) export class Foo { @TrackDestroyed() private _destroyed: boolean; } ``` API: ```typescript /** Set the given property to true when the component is destroyed */ export declare function TrackDestroyed(): PropertyDecorator; ``` ## TrackInit - set the property to true during ngOnInit Same as `@TrackDestroyed()`, but for `ngOnInit` ## Unsubscribe - automatically unsubscribe from subscriptions This decorator is equivalent to [@Complete()](#complete---automatically-complete-a-subject), but instead of `Subjects` it works on `Subscriptions`.