Achieve A Better Angular Application Using TypeScript Decorators

Both Angular and TS are great tools. Using both helps us achieve a great application. Combining the advantages of TS features together with Agular’s advanced functionality can leverage your app even more.
One of the advanced features TS has to offer is the Decorator. Combining Decorators together with Angular can make your application cleaner, more readable and can reduce code duplications.
So what are TS decorators anyway?
Official defenition-
A Decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. Decorators use the form
@expression
, whereexpression
must evaluate to a function that will be called at runtime with information about the decorated declaration.
Unofficial defenition -
Decorator is a simple function that wraps and give us the ability to manipulate our function (At least for me it was the easiest way to understand this feature 😀).
Decorators can be applyed on a — Class |Method | Accessor | Property | Parameter.
Let’s take a look on some examples in order to get some intuition on how decorators works.
Below is a class with one simple method that prints a sum of three numbers to a console -
export class AppComponent { constructor() {
this.sum(100, 200, 300);
} sum(a:number, b:number, c:number): void {
console.log(‘Result’, a + b + c);
}}

Imagine that during the development proccess we want to print all the given arguments to the console, to achive this, we need a function which will receive our function as argument and iterate over all given arguments in order to print them to the console.
We will implement decorator that will look like a below -
export function PrintGivenArguments(
target: object,
methodName:string,
propertyDescriptor:PropertyDescriptor) {// Some code...}
And the decorator will be applyed like this → on top of our function
export class AppComponent { constructor() {
this.sum(100, 200, 300);
} @PrintGivenArguments
sum(a:number, b:number, c:number): void {
console.log(‘Result’, a + b + c);
}}
When decorator function is applayed, three arguments passed to the decorator function -
target
(An object which represents a class that our method is member of)
methodName
(A string representing our method name)
propertyDescriptor
(PropertyDescriptor is an object that contains the actual method logic)
Now we can add some code to our decorator function -
export function PrintGivenArguments(
target: object,
functionName:string,
propertyDescriptor:PropertyDescriptor) {// (1) Keep reference to the original function
const originalFunction = propertyDescriptor.value;// (2) Overide your function
propertyDescriptor.value = function (...args: any[]) {for (let index = 0; index < args.length; index++) {
const consoleOutput: string = 'Argument number ' + index + ' is ---> ' + args[index];
}// (3) Invoke the original function
originalFunction.apply(this, args);}
Ok, so after we’ve put some logic we can rerun our code and take another look at the console…

As we can see, we are now able to print the given arguments without messing up our function! 🤩
Let’s take it one step further -
How we can pass a property to our decorator? for example- we want to pass an argument to our decorator which indicates the log level we are interested in (info, warning or error?)
In this case we will need to use a pattern called Decorator Factory -
If we want to customize how a decorator is applied to a declaration, we can write a decorator factory. A Decorator Factory is simply a function that returns the expression that will be called by the decorator at runtime.
lets apply this pattern on our decorator -
export function PrintGivenArguments(logLevel?: number) {
return function (
target: object,
methodName: string,
propertyDescriptor: PropertyDescriptor) { // (1) Keep reference to the original function
const originalFunction= propertyDescriptor.value; // (2) Overide your function
propertyDescriptor.value = function (...args: any[]) { for (let index = 0; index < args.length; index++) {
const consoleOutput: string = 'Argument number ' + index + ' is ---> ' + args[index]; switch (logLevel) {
case 1:
console.warn(consoleOutput);
break;
case 2:
console.error(consoleOutput);
break;
default:
console.log(consoleOutput);
break;
}
} // (3) Invoke the original function
originalFunction.apply(this, args);
}
}
}
As we can see our decorator can now recieve parameters 😀


Ok, now that we have some intuition on how decorators work, we are ready to combine decorators with some Angular functionality —
First thing first — To enable experimental support for decorators, you must enable the
experimentalDecorators
compiler option either on the command line or in yourtsconfig.json
:Command Line:
tsc --target ES5 --experimentalDecorators
tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
@RunOutsideAngular Decorator
One of the optimization techniques in Angular is to run functions outside Angular zone.
Running functions via runOutsideAngular allows you to escape Angular’s zone and do work that doesn’t trigger Angular change-detection (angular.io).
Below is a simple function which simulate a healthCheck function, this function runs every 1 sec.
export class AppComponent implements DoCheck {
source = interval(1000);
color: boolean = false;
constructor(private ngZone: NgZone) {
this.healthCheck();
}
healthCheck() {
this.source.subscribe(() => {
console.log('Health check passed succesfully');
});
} ngDoCheck(): void {
this.color = !this.color;
}
}
Running the code above will trigger change detection every sec.

As we can see from the console output, and the text on the screen, after execution of healthCheck function, Angular does performing change detection.
How can we get rid of this redundant change detection cycle? Run healthCheck outside Angular!
export class AppComponent implements DoCheck {
source = interval(1000);
color: boolean = false;
constructor(private ngZone: NgZone) {
this.healthCheck();
}
healthCheck() {
this.ngZone.runOutsideAngular(() => {
this.source
.subscribe(() => {
console.log('Health check passed succesfully');
});
})
}ngDoCheck(): void {
this.color = !this.color;
}
}
Running code above will not trigger change detection every sec.

As we can see from the console output, and the text on the screen, after execution of healthCheck function, Angular does not perform change detection.
Ok, but do we need to wrap each time our functions with this runOutsideAngular code? No! lets make it simple!
let’s write another decorator -
export function RunOutsideAngular(
target: object,
methodName: string,
propertyDescriptor: PropertyDescriptor) { // (1) Keep reference to the original function
const originalFunction = propertyDescriptor.value; // (2) Overide your function
propertyDescriptor.value = function(...args: any[]) {
this.ngZone.runOutsideAngular(() =>
originalMethod.call(this, ...args));
};return propertyDescriptor;
}
Applaying this decorator on our function will achive the same functionality but in a much cleaner way, in addition we can use this decorator on every function that doesn’t need to run inside Angular and run redundant change detection cycles.
export class AppComponent implements DoCheck {
source = interval(1000);
color: boolean = false;
constructor(private ngZone: NgZone) {
this.healthCheck();
}@RunOutsideAngular
healthCheck() {
this.source.subscribe(() => {
console.log('Health check passed succesfully');
});
}ngDoCheck(): void {
this.color = !this.color;
}
}

@Memoization Decorator
In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again (Wikipedia)
Below we have an example of a ‘heavy’ and stateless function, in this case we will use fibonacci to simulate some ‘heavy’ calculation that can cause bad performance to our application.
export class AppComponent { constructor() {
console.time('heavyCalculation');
console.log('#1 - ' ,this.heavyCalculation(45));
console.timeEnd('heavyCalculation');
console.time('heavyCalculation');
console.log('#2 - ', this.heavyCalculation(45));
console.timeEnd('heavyCalculation');
}
heavyCalculation(fibNumber: number): number {
const result = this.fibonacci(fibNumber)
return result;
}
fibonacci(fibNumber: number) {
if(fibNumber < 2) {
return fibNumber;}
else {
return this.fibonacci(fibNumber-1) + this.fibonacci(fibNumber-2);
}
}
}
Running the fibonacci function twice with the same input without using memoization is not smart…below is a result of this run -

As we can see fibonacci function runs twise. Each run took ~16 sec.
Can we do better? Of course! We can memoaize our function, in order to avoid redundant calculations.
export function Memoization(
target: Object,
methodName: string,
descriptor: PropertyDescriptor) {const cache: Map<any, any> = new Map<any, any>()
// Keep reference to the original function
const originalFunction = descriptor.value;
// Overide the original function
descriptor.value = function (...args: any[]) {
const [key] = args;
if (cache.has(key)) {
return cache.get(key);
} else {
const result = originalFunction.apply(this, args);
cache.set(key, result);
return result;
}
}
}
Now we will apply Memoization decorator on our ‘heavy’ function…
export class AppComponent {constructor() {
console.time('heavyCalculation');
console.log('#1 - ' ,this.heavyCalculation(45));
console.timeEnd('heavyCalculation');
console.time('heavyCalculation');
console.log('#2 - ', this.heavyCalculation(45));
console.timeEnd('heavyCalculation');
}
@Memoization
heavyCalculation(fibNumber: number): number {
const result = this.fibonacci(fibNumber)
return result;
}
fibonacci(fibNumber: number) {
if(fibNumber < 2) {
return fibNumber;}
else {
return this.fibonacci(fibNumber-1) + this.fibonacci(fibNumber-2);
}
}
}
let’s run this once again and take another look at the console -

As we can see — the first ‘‘heavy’’ function execution took ~16 sec while the second took less than 1 sec!
The second (and more interesting, IMO) way to use decorator is to apply it on a Class, it is a similar to method decorator, it is like a method decorator, but in the case of a class, we’re exposed to all class members.
When decorator function is applayed on a class, only one argument passed to the decorator function
target
(An object which represents a whole class)
This time we will implement decorator which will help us to recognize changes in collection like objects while we use ChangeDetectionStrategy.OnPush
Using ChangeDetectionStrategy.OnPush is a great way to optimize angular components, for a comprehensive guide please read this great article →
A Comprehensive Guide to Angular onPush Change Detection Strategy by Netanel Basal)
The case — We have two components, parent and child component. Child component is set to ChangeDetectionStrategy.OnPush meaning that the change detection on a child component will run only when our @Input referance changed.
@Component({
selector: 'app-root',
template: `
<div class="greeding">
<button type="button" class="btn btn-success button-style" (click)="onAddSmile()">Add 😍</button>
<child-component [arrayOfSmiles]="arrayOfSmiles"></child-component></div>`,
styleUrls: ['./app.component.css']
})export class ParentComponent {
arrayOfSmiles = [];
onAddSmile() {
this.arrayOfSmiles.push('😍')
}
}@Component({
selector: 'child-component',
template: `
<div *ngFor="let smile of arrayOfSmiles">{{smile}}
</div>`,
changeDetection: ChangeDetectionStrategy.Default
})
export class ChildComponent {
@Input() arrayOfSmiles: string[] = [];
}

At this point everyting works as expected, we can click on a button in ParentComponent which pushes a new item into an arrayOfSmiles, and finaly the ChildComponent rerendering the Input() arrayOfSmiles.
But what will happen if you will change the ChildComponent from ChangeDetectionStrategy.Default to ChangeDetectionStrategy.OnPush?
@Component({
selector: 'child-component',
template: `
<div *ngFor="let smile of arrayOfSmiles">{{smile}}
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})export class ChildComponent {
@Input() arrayOfSmiles: string[] = [];
}

As expected, using ChangeDetectionStrategy.OnPush on the ChildComponent and then pressing the button will not trigger any changes!
Why does this happen?
When we using OnPush strategy we’re telling Angular that the component only depends on its Inputs() and needs to be checked only if the Input
reference changes. In our case, we add a new item to the array but the reference is kept the same.
So how can we use the advantage of OnPush strategy to optimaize the app from one hand, but still track changes in array like objects on the other hand?
@TrackChanges Decorator
This decorator will help us to track changes inside components using OnPush strategy.
Side note: in order to deal use cases like above we can —
- Simply create a new instance of array on each push to array, that will trigger change detection.
- Use immutable.js — immutable.js provides many Persistent Immutable data structures including:
List
,Stack
,Map
,OrderedMap
,Set
,OrderedSet
andRecord
.
export function TrackChanges(propertiesToTrack: string[]) {
return function (target: Function) {
//(1) Keep reference to the original function
const original = target.prototype['ngDoCheck'];
//(2) Overide your function
target.prototype['ngDoCheck'] = function () { if (!this.empDiffer) {
this.empDiffer = this.itrDiffers.find([]).create(null);
} //(3) Apply original functionality
original.apply(this, arguments);
const changes = this.empDiffer.diff(this[propertiesToTrack[0]]);
if (changes) {
this.cdr.markForCheck();
}
};
}
}
- We overiting DoCheck hook in order to monitor changes that occur where ngOnChanges() won't catch them.
- To check if our array has a new items we will use IterableDiffers class.
- Finaly, in case there are changes, we will trigger changeDetection() manualy using markForCheck() function.
Now we can apply our decorator and pass the name of property to track.
@Component({
selector: 'child-component',
template: `
<div *ngFor="let smile of arrayOfSmiles">{{smile}}</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
@TrackChanges(['arrayOfSmiles'])
export class ChildComponent {
@Input() arrayOfSmiles: string[] = [];constructor(
private itrDiffers: IterableDiffers,
private cdr: ChangeDetectorRef)
{}
ngDoCheck(): void {}
}

Cool! as we can see above, pressing the button will trigger change detection manualy (after performing a diff using IterableDiffers class) and will bring back the smiles 😍
Side note: as of today there is no way to inject classes/services inside decorator function, so in order to use services like ngZone, IterableDiffers, ChangeDetectorRef etc.. we need to inject directly to the compenent class and then we will have a reference to the injected classes/services inside the decorator function.
Summary:
As I mentioned at the beginning of the article- the decorator is a very powerful tool. In this article you have seen several practical examples which I hope will make you curious and explore this subject further.