16

6 Useful Decorators to use in your Angular projects

 4 years ago
source link: https://blog.bitsrc.io/6-useful-decorators-to-use-in-your-angular-projects-777e9b4c8c62
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

6 Useful Angular Decorators

Useful Decorators to use in your Angular projects

AzQrYzb.png!web

Little was known of these advanced JS features until Angular 4 was released. It formed the basis of modules in Angular via @NgModule, the basis of components by the @Component, DI via @Injectable and many others.

Even though Angular has provided us with plenty of built-in Decorators, you’ll most likely find yourself needing to create your own custom ones.

In this post, I’ll list and implement 6 decorators that will be very useful in our Angular apps.

Tip:Use Bit to share and reuse Angular components across different projects. Collaborate over shared components as a team to build apps faster together. Let Bit do the heavy lifting so you can easily publish, install and update your individual components without any overhead. Click here to learn more .

aIjqqii.jpg Example: components shared on bit.dev

1. Unsubscribe

The first in the list is the Unsubscribe decorator.

How is this useful?

Angular has a high usage of RxJS Observables, this is a reactive library for JS, it is used to add event-based programming to JS. NgRX mixed it with Redux to bring Observable-powered Redux state management to Angular. One of the best practices in using Observables is to unsubscribe from them when done with them to prevent memory leaks.

Leaving subscriptions open when they are no longer used causes a needless memory reference in the heap, this will seriously cause a high-memory usage in our apps and can cause bugs and laggy systems.

The best practice is to use the ngOnDestroy hook to unsubscribe from the subscriptions in our directives.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnDestroy {
    products$ : Observable
    users$: Observable
    constructor(private store: Store) {
        this.products$ = this.store.select("products").subscribe(...)
        this.user$ = this.store.select("users").subscribe(...)
    }    ngOnDestroy() {
        this.products$.unsbscribe()
        this.users$.unsubscribe()
    }
}

Devs know this, but writing the code to do that might be lengthy and most devs will be too lazy to do that.

Now, a quick way to do that will be to use a Decorator function. There will be no need for implementing and defining ngOnDestroy method. Just pass your Observables in an array to the Decorator.

See, the Unsub decorator does the job for us. First, this Unsub is a class decorator, meaning it will be applied to classes only.

It takes the Observables member property of the class it was applied in an array, obs$ . The function it returns will take the instance of the class it is applied to in the constructor param. It saves the original ngOnDestroy method. We construct our own ngOnDestroy method. Inside it, we loop through the Observable member properties, and call unsubscribe() method on each of them. This unsubscribes the Observable member properties. Then, the saved original ngOnDestroy is called.

...
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
@Unsub(["products$", "users$"])
export class AppComponent implements OnDestroy {
    products$ : Observable
    users$: Observable
    constructor(private store: Store) {
        this.products$ = this.store.select("products").subscribe(...)
        this.user$ = this.store.select("users").subscribe(...)
    }
}

There is no need for the unsubscription in the ngOnDestroy hook.

See, even if the dev implements ngOnDestroy but doesn’t unsubscribe his Observables, they will be unsubscribed and his implementation will also be run :)

Isn’t it very useful?:grin:

2. Memo

One of the oldest and most effective optimization techniques in the books.

Memoization is the act of remembering the output to an input in a function and returning the output when the inputs occur again skipping the computation of the output.

This is effective when the function/method is an expensive one. A set of input(s) will be computed once and the output will be saved so that they will be returned when the input(s) are passed again to the function, this will make the function run faster after the initial time.

We may have a pure method in our Component, or Directive, or Pipe or Injectable we want to apply memoization on.

For example, we have a Pipe class:

@Pipe({
    name: 'fib',
    pure: true
})
export class FibPipe implements PipeTransform {
    transform(val: any, args: any) {
        return this.fib(val)
    }
    
    fib(num) {
        if(num == 1 || num ==2 ) return 1
        return fib(num -1 ) + fib(num - 2)
    }
}

We know that calculating fibonacci series of numbers is highly CPU-intensive, we might try memoizing the fib method like this:

function memoize(fn: any) {
    return function () {
        var args =
Array.prototype.slice.call(arguments)
        fn.cache = fn.cache || {};
        return fn.cache[args] ? fn.cache[args] : (fn.cache[args] = fn.apply(this,args))
    }
}function fib(num) {
    if(num == 1 || num ==2 ) return 1
    return fib(num -1 ) + fib(num - 2)
}const memoizedFib = memoize(fib)@Pipe({
    name: 'fib',
    pure: true
})
export class FibPipe implements PipeTransform {
    transform(val: any, args: any) {
        return memoizedFib(val)
    }
}

Just because of memoization, we removed the fib method from the class outside as a standalone function.

Using Decorators it will be very easy, let’s have our memoization decorator:

The memo is the decorator factory function that will be applied to methods. Of course, it is a method decorator.

See, inside the memo function, we save the old function and create a new function by calling the old function on the memoize function, this new function will be a memoized version of the old function. Next, we set a new function on the method decorated and in the body, we call the memoized new function.

With this our pipe example will be this:

@Pipe({
    name: 'fib',
    pure: true
})
export class FibPipe implements PipeTransform {
    transform(val: any, args: any) {
        return this.fib(val)
    }    @memo()
    fib(num) {
        if(num == 1 || num ==2 ) return 1
        return fib(num -1 ) + fib(num - 2)
    }
}

We just decorated the fib method with the memo decorator. This will make fib memoized.

Let’s see usage in Component:

@Component({
  selector: 'app-root',
  template: `
    <div>
        <h3>
            Factorial of {{number}}! is {{result}}
        </h3>
        <input #input (change)="changeFn" placeholder="Enter Number" />
    </div>
  `,
  style: []
})
export class AppComponent {
    number: number
    result: number    changeFn(evt) {
        this.number = evt.target.value
        this.result = this.factorial(this.number)
    }    @memo()
    factorial(num) {
        if(num <= 1)
            return num
        return num * factorial(num - 1)
    }
}

See, this component calculates the factorial of any number typed in the input textbox and displays the result.

Factorial just like fibonacci will become expensive when the number to calculate rises. So, we used the memo decorator to memoize the factorial method. This will make the factorial method to return cached results when input occurs again.

This memo decorator is very useful, no need to extract the method in order to memoize it.

3. Throttle

Throttling is a trick we use to stop a function/method from rapidly firing. The trick is to extend the time of the function call at a specified time, to execute once in every specified time.

Rapidly executing a function will cause memory overload in the browser and will lead to performance problems. The common example of a function rapidly firing is the function callback on a body scroll event.

From example:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {    @HostListener("document:scroll")
    scrollFn(evt) {
        // ...
    }
}

See, the scrollFn is attached to the document.scroll event. Every time the document is scrolled the scrollFn is fired. Just dragging the scroll bar a little can easily fire the scrollFn at over 30 times per second.

We can throttle the scrollFn method to guarantee it is executed per the specified time to decrease the number of times it is executed per sec.

We can simply use a throttle decorator:

We saved the original method first. We created a new function and assigned it to a throttled version of the original method by calling _throttle function on it. The _throttle function defers the execution of a function by using the setTimeout API.

A new method is created and in the body, the throttled function is called.

To use this in our Component we simply do this:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {    @HostListener("document:scroll")
    @throttle(1000)
    scrollFn(evt) {
        // ...
    }
}

With this the scrollFn, will be called at 1s interval.

4. Debounce

Similar to throttling but debouncing aggregates calls to a function over a specified time and executes it once the specified time has elapsed.

For example, see the below Component:

@Component({
  selector: 'app-root',
  template: `
    <div>
        <input (change)="changeFn" placeholder="Search words here..." />
        <div>
            <div *ngFor="let result of results | async">
                <!-- -->
            </div>
        </div>
    </div>
  `,
  style: []
})
export class AppComponent {
    changeFn() {
        //... perform GET reguest
    }
}

Let’s say this is the place that users type in to search for words in our search engine. Once a word is typed in the input textbox a GET Http request is performed to get the results from our server.

Now, if the users rapidly type words to search in the input field like 30 times per sec. We will see that our server will be called 30 times for a single word!!

If we type in “books”, GET Http request will be performed for each letter in the word:

b -> GET Http
o -> GET Http
o -> GET Http
k -> GET Http
s -> GET Http

We can aggregate the calls to the changeFn to execute after a specified time has elapsed.

This can be easily done using decorator:

The core work is in the _debounce function, it uses setTimeout to execute the function after an ms time after have elapsed, and during the time of the setTimeout, we use inProgress to defer other calls to the function to stop from executing.

Now, we can decorate the changeFn in the Component with debounce :

@Component({
  selector: 'app-root',
  template: `
    <div>
        <input (change)="changeFn" placeholder="Search words here..." />
        <div>
            <div *ngFor="let result of results | async">
                <!-- -->
            </div>
        </div>
    </div>
  `,
  style: []
})
export class AppComponent {
    @debounce(1000)
    changeFn() {
        //... perform GET reguest
    }
}

With this, the changeFn will be fired after 1s has elapsed. During this time, calls to it will be ignored.

If we type “books”, maybe we typed in under half a sec, no GET Http request will be performed until after we are done typing.

5. Readonly

Sometimes we don’t want to have our property variable to be readonly, not to be overwritten.

We might do this:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'ng-spinners';
  constructor() {
        Object.defineProperty(this,"title", {
            writable: false
        })
  }
}

Extra code, we can simply use a decorator:

Then apply it to the title member variable:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  @readonly title = 'ng-spinners';
}

That’s it. Cool!

6. Time

We might be tempted to really measure the time it takes for a method to run. Crude implementation of it will be this:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
    methodToMeasure() {
        const start = performance.now()
        // ... the method logic runs
        const stop = performance.now()
        console.log(`Metrics stats:`, (stop - start).toFixed(2))        
    }
}

This could be done better with a decorator.

We decorate the methodToMeasure with @time :

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
    @time()
    methodToMeasure() {
        // ... the method logic runs
    }
}

Simple and neat, no excess code.

Conclusion

There are many more to the above list. The few I mentioned were the ones that always helped me fasten my development in Angular.

Decorators are very useful, no doubt. They magically shave off excess code from our implementation to help us focus on the core implementation.

You can think of cool tricks that can be done with Decorator.

If you have any questions regarding this or anything I should add, correct or remove, feel free to comment, email or DM me.

Thanks !!!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK