Useful decorators for Angular 2 and above. Full API docs available here
npm install ngx-decorate
Or use the CDN version:
<scriptsrc="https://cdn.jsdelivr.net/npm/ngx-decorate@3.0.3/dist/ngx-decorators.umd.min.js"></script>
The AMD name is NgxDecorate
.
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.
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 tool.
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.
Before:
@Component({})
exportclass Foo {
private _prop: string;
publicconstructor(private readonly cdr: ChangeDetectorRef) {}
publicget prop(): string {
returnthis._prop;
}
publicset prop(v: string) {
this._prop = v;
this.cdr.markForCheck();
}
}
After:
@Component({})
exportclass Foo {
@CdrProp('cdr')
public prop: string;
publicconstructor(private readonly cdr: ChangeDetectorRef) {}
}
API:
exportdeclaretype ConfEnum = Pick<PropertyDescriptor, 'configurable' | 'enumerable'>;
exportinterface 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
*/exportdeclarefunctionCdrProp(propName: PropertyKey, conf?: CdrPropConfig): PropertyDecorator;
The property can hold either a single subject or an array of subjects.
Before:
@Component({})
exportclass 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:
@NgxDecorate()
@Component({})
exportclass Foo {
@Complete()
private _subjects: Subject<any>[] = [];
@Complete()
private _oneSubject: Subject<any>;
}
API:
/**
* Automatically completes the subjects and event emitters at this property.
* The property can be either a single object or an array of objects.
*/exportdeclarefunctionComplete(): PropertyDecorator;
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:
@NgxDecorate()
@Component({})
exportclass Foo {
@LazySubject()
publicget someSubject(): Subject<string> {
returnnew Subject<string>();
}
}
Equivalent logic:
@Component({})
exportclass Foo implements OnDestroy {
private _subjects: Subject<any>[] = [];
publicget 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:
/**
* Decorate a getter that returns a subject. The subject will automatically get completed
* when the component/service/pipe is destroyed.
*/exportdeclarefunctionLazySubject(): MethodDecorator;
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:
@Component({})
exportclass Foo implements OnInit, OnDestroy {
private _name: string;
private _name$: Subject<string> = new Subject<string>();
publicset name(v: string) {
this._name = v;
this._name$.next(v);
}
publicget name(): string {
returnthis._name;
}
public ngOnInit(): void {
this.name = 'foo';
}
public ngOnDestroy(): void {
this._name$.complete();
}
}
After:
@NgxDecorate()
@Component({})
exportclass Foo {
@SubjectSetter('_name$', {default: 'foo'})
public name: string;
@Complete()
private _name$: Subject<string> = new Subject<string>();
}
API (see ConfEnum docs under @CdrProp):
exportinterface 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
*/exportdeclarefunctionSubjectSetter(subjectPropName: PropertyKey, conf?: SubjectSetterConfig): PropertyDecorator;
Effectively the opposite of @SubjectSetter
Before:
@Component({})
exportclass Foo implements OnInit, OnDestroy {
public prop: SomeInterface;
private prop$: Observable<SomeInterface>;
private _propSubscription: Subscription;
publicconstructor(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:
@NgxDecorate()
@Component({})
exportclass Foo implements OnInit {
@SubscribeTo('prop$', {cdrProp: 'cdr'})
public prop: SomeInterface;
publicconstructor(private svc: SomeService, private cdr: ChangeDetectorRef) {}
public ngOnInit(): void {
this.prop$ = this.svc.getSomeInterface();
}
}
API:
exportinterface 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
*/exportdeclarefunctionSubscribeTo(prop: PropertyKey, cfg?: SubscribeToConfig): PropertyDecorator;
This could potentially be used with the @CdrProp()
decorator.
Before:
@Component({})
exportclass Foo implements OnDestroy {
private _destroyed: boolean;
public ngOnDestroy(): void {
this._destroyed = true;
}
}
After:
@NgxDecorate()
@Component({})
exportclass Foo {
@TrackDestroyed()
private _destroyed: boolean;
}
API:
/** Set the given property to true when the component is destroyed */exportdeclarefunctionTrackDestroyed(): PropertyDecorator;
Same as @TrackDestroyed()
, but for ngOnInit
This decorator is equivalent to @Complete(),
but instead of Subjects
it works on Subscriptions
.