How @taiga-ui/cdk can help you simplify your working with Angular: our 5 best practices

Post Editor

I decided to pick my favorite 5 tools from taiga-ui/cdk and explain how they can in any Angular app

6 min read
post

How @taiga-ui/cdk can help you simplify your working with Angular: our 5 best practices

I decided to pick my favorite 5 tools from taiga-ui/cdk and explain how they can in any Angular app

post
post
6 min read

CDK is the basic package of the component library Taiga UI.

It has nothing to do with the visual part of the library (actual UI) but is rather a set of useful tools to simplify working with Angular and to make it faster.

You'll find many useful tools inside and I decided to pick my favorite 5 and write about them here. I use these tools in all my projects and already cannot imagine how to develop Angular apps without them because they save me a lot of time every day.

Disclaimer about package weight
Link to this section

You may ask: “Why do I have to add such a big multitool into my application if I need a couple of simple functions?”. Well, here's an answer for you.

Taking a look at bundlephobia we get the following picture:

23 KB is not a terrible result but it is still a considerable size. The thing is, CDK uses Secondary Entry Points (you can also read a great article by Kevin Kreuzer) and hence fully tree-shakable. That means that we’ll get all the 23 KB in our bundle only if we import and use all the entities from the package. If we need two functions, they will be the only ones in our final bundle and they will add less than 1 KB to it.

tuiPure is an advanced memoization
Link to this section

This is a decorator that can be used with getters and pure class methods. Let’s see both situations:

As a getter
Link to this section

We can add `tuiPure` decorator to a getter. In this case we can make a lazy calculation. If a user does not press a button to make “show” property true, getter will never be calculated.

Example #1: showing and hiding 40th Fibonacci number when user clicks a button.

<>Copy
// template <div *ngIf="show">fibonacci(40) = {{ fibonacci40 }}</div> // component @tuiPure get fibonacci40(): number { return calculateFibonacci(40); }

When the user asks for a number the first time, the function calculates the 40th Fibonacci element with a method. All next requests will return the previously calculated number.

Example #2: we have a Pull-To-Refresh component that emulates native iOS and Android behavior. One of its streams should be called on Android only and not for iOS. If we just create a stream in a constructor, it will be made on both OS. Let’s wrap it with tuiPure getter and this way we’ll not create an unused Observable on iOS.

<>Copy
<tui-mobile-ios-loader *ngIf="isIOS; else angroidLoader" ></tui-mobile-ios-loader> <ng-template #angroidLoader> <tui-mobile-android-loader [style.transform]="loaderTransform$ | async" ></tui-mobile-android-loader> </ng-template> @tuiPure get loaderTransform$(): Observable<string> { return this.pulling$.pipe( map(distance => translateY(Math.min(distance, ANDROID_MAX_DISTANCE))), ); }

Or we can also use “changes” of ContentChild / ContentChildren. If we call such a getter from a template, we can be sure that all the content is ready because Angular renders all the content before its view. And taking care of the order, we can also do it with ViewChild / ViewChildren.

As a method
Link to this section

@tuiPure can also be used with a method to add memoization. In this case it will calculate the value after the first method call. All the following calls will be returned with the same result immediately until one of the arguments changes. If it changes, the result will be recalculated.

This is very useful for compliant math calculations or in combination with a getter to create non-primitive values like objects and arrays.

<>Copy
get filteredItems(): readonly string[] { return this.computeFilteredItems(this.items); } @tuiPure private computeFilteredItems(items: readonly string[]): readonly string[] { return items.filter(someCondition); }

When we call the getter from our template, the pure method filters an items array once and returns the same filtered array until this.items changes. It helps to avoid repeating recreation of new arrays on every cycle of change detection and solve potential problems with different references to an array that we bind in the template. Using this pattern, we do not need to manually sync filtered array in ngOnChanges if “items” are an input of component.

tuiPure documentation

It is a function that patches getter’s or method’s TypedPropertyDescriptor and returns its new version with wrapped memoization. See the implementation to explore it in more detail. It can be also easily extended with memoization of all values, not only the last one.

*tuiLet
Link to this section

This is a simple strutural directive that allows to define local variables in templates:

<>Copy
<ng-container *tuiLet="timer$ | async as time"> <p>Timer value: {{time}}</p> <p> It can be used many times: <tui-badge [value]="time"></tui-badge> </p> <p> It subsribed once and async pipe unsubsribes it after component destroy </p> </ng-container>

You can also use *ngIf instead of *tuiLet if you do not need to show falsy values (or if it is not applicable). But if we work with numbers, for example, zero is most likely one of the supported values. This is a case for *tuiLet because it will show zero while *ngIf will hide the element.

tuiLet documentation

Here you can find the implementation. It is a good simple example of how to make a structural directive.

Metapipes tuiMapper and tuiFilter
Link to this section

We created a pipe to avoid creating other pipes and it is called tuiMapper.

It is a pure pipe that takes a pure transformation function and any number of arguments for it. So, we get the following template:

<>Copy
{{value | tuiMapper : mapper : arg1 : arg2 }}

It is also convenient to transform data for inputs of other components in template or use them with *ngIf / *tuiLet:

<>Copy
<div *ngIf="item | tuiMapper : toMarkers : itemIsToday(item) : !!getItemRange(item) as markers" class="dots" > <div class="dot" [tuiBackground]="markers[0]"></div> <div *ngIf="markers.length > 1" class="dot" [tuiBackground]="markers[1]" ></div> </div>
Adding of colored marker dots in calendars from @taiga-ui/core

Pure pipes cache previous values and do not recalculate every cycle of change detection that solves all potential performance issues.

So, we can operate with handlers (pure functions that transform value), change them or even pass as an input of component from the outside. We do not need to create a new pipe for each case if we need it once.

We also have a tuiFilter pipe for array filtering. It is actually a special case of mapper but we need it quite often. This is a pure pipe, so there are no issues with performance because pure pipes cache calculated values and do not recreate arrays or objects for every change detection cycle.

mapper documentation / filter documentation

And here you can find tuiMapper and tuiFilter implementations that are actually pure pipes with well-typed transform methods.

destroy$
Link to this section

It is an Observable-based service that simplifies the process of unsubscription in components and directives.

<>Copy
@Component({ // ... providers: [TuiDestroyService], }) export class TuiDestroyExample { constructor(@Inject(TuiDestroyService) private readonly destroy$: Observable<void>) {} // … subscribeSomething() { fromEvent(this.element, 'click') .pipe(takeUntil(this.destroy$)) .subscribe(() => { console.log('click'); }); } }

Everything you need is to add it into “providers” of the component and to inject it in the constructor. I like to write types of DI entities as narrow as possible and it is Observable<void> here. But it can also be written shorter:

<>Copy
constructor(private destroy$: TuiDestroyService) {}

By the way, such service does not bind to the component lifecycle but rather to the components DI injector. It means that it can even be used when subscribing to a stream inside a service or DI factory. Such cases are rare but TuiDestroyService is very helpful in them because there are no simple alternatives. For example, we call markForCheck from the token factory in the article about DI tricks to provide data through components.

destroy$ documentation

And take a look how simple the implementation is! It is just a subject-based service with “ngDestroy” method.

ng-event-plugins
Link to this section

This is actually another library ng-event-plugins that comes with CDK as a dependency (you do not need to install it by yourself). It adds new handlers to the plugin manager in Angular. There are also several useful built-in plugins that simplify working with events in component templates.

For example, .stop and .prevent allow to call stopPropagation and preventDefault on an event in a declarative way.

Before:

<>Copy
<some-input (mousedown)="handle($event)" > Choose date </some-input>
Template
<>Copy
export class SomeComponent { // … handle(event: MouseEvent) { event.preventDefault(); event.stopPropagation(); this.onMouseDown(event); } }
Component

Now:

<>Copy
<some-input (mousedown.prevent.stop)="onMouseDown()" > Choose date </some-input>

Or .silent modifier that allows us to prevent change detection after event comes:

<>Copy
<div (mousemove.silent)="onMouseMove()"> Callbacks to mousemove will not trigger change detection </div>

We can catch events in a .capture phase:

<>Copy
<div (click.capture.stop)="onClick()"> <div (click)="never()"> Clicks will be stopped before reaching this DIV </div> </div>

And all this works also with @HostListener and custom events. You can read about it more in ng-event-plugins documentation or in the detailed article by Alex Inkin.

Finally
Link to this section

We took a look at several entities from @taiga-ui/cdk. I hope that some of them will be useful for you.

By the way, there is also another article about Taiga UI library itself in that I described its other packages and its philosophy.

And do not forget to follow me on Twitter: @marsibarsi. There will be much cool Angular content soon!

Share

About the author

author_image

Frontend Developer at Tinkoff.ru

author_image

About the author

Roman Sedov

Frontend Developer at Tinkoff.ru

About the author

author_image

Frontend Developer at Tinkoff.ru

Looking for a JS job?
Job logo
PDQ team| Senior JavaScript developer (Angular/Node)

SD Solutions

Ukraine
Remote
$60k - $80k
Job logo
Senior Full-Stack Developer (Node+Angular)

A-Listware

Ukraine
Remote
$48k - $78k
Job logo
Senior Full stack (Angular+Node)

Monolith

Ukraine
Remote
$60k - $84k
Job logo
AngularJS Developer/.net Core - Remote Contract

InfoMagnus

United States
Remote
$115k - $134k
More jobs
NxAngularCli
NxAngularCli
NxAngularCli

Featured articles

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
AngularpostTracking user interaction area

13 September 2021

8 min read

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
Angularpost
7 September 202122 min read
Designing Angular architecture - Container-Presentation pattern

Designing architecture could be tricky, especially in the agile world, where requirement changes are frequent. So your design has to support that and provides extendibility without the need for serious modification. In such cases, you will find the Container-Presentation pattern instrumental.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more
micro frontendspostTaking micro-frontends to the next level

6 September 2021

25 min read

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more