[Angular] Must-have Utils to Improve your code.

kelly woo
3 min readDec 19, 2021
Photo by Elena Rouame on Unsplash

I love util services.
It makes my job easier and keeps code neat.
It saves my time and others.
The followings are basic utils you can add to your project.

| Debounce | Throttle | Audit click

What if a user clicks toggle button too soon too many? Yes, your first thought would be throttle, and it is quite troublesome to add throttle logic in components.
But with this directive no trouble to do that.
Also it can run outside of angular. (audit is throttle with trailing)

the codes are here.

| className

ngClass is convenient but still not that convenient. since it receives static className, we face the situations to concat string to make a className.
Also only few lines to add.

import { PipeTransform } from '@angular/core';
import { Pipe } from '@angular/core';
@Pipe({
name: 'uiClassName',
})
export class UiClassName implements PipeTransform {
transform(classNames: any[]) {
return classNames.filter((v) => Boolean(v)).join(' ');
}
}
// use
<h1 [className]="['is-title', null, false, false && 'hello', true && 'is-ok'] | uiClassName">UiClick Directive<h1>

| custom-If

Sometimes you get asked to control the appearance of several views all over the app for supporting or authority issues.
You can create each directive or use ngIf with many conditions imported, comparing to them, this custom-if gives less management point.

HowToUse: provide service of condition and support condition can be provided the form of string separated by ‘:(colon)’ and by adding ‘!’ at the front, you can reverse the value.

// template
<li *customIf="true; support: n % 2 === 0 ? 'random:another' : '!random:another'">
{{ n }}</li>

// service injection
@Injectable()
export class CustomIfService implements CustomIfCondition<'random' | 'another'>{
constructor(private randomService: RandomService) {}
supports(conditionSet: Set<string>) {
return combineLatest([
conditionSet.has('random') ? this.randomService.show$ : of(null),
of(true),
]).pipe(
map(([random, another]) => {
return { random, another };
})
);
}
}
// directive => see following link

| activatedParamsServive

activatedRoute references different route by the location it is injected to.
It can be pain in the ass to figure out the params of parent route or children route.. so it might be easier to have a param storage for every activatedRoutes.

route = [{ 
path: 'a/:id1',
children: [ { path: '/:id2' } ]
}];
url = a/kelly/blogactivatedParamsService.paramMap$.subscribe((v) => {
console.log(v); //{id1: 'kelly', id2: 'blog'}
})

Be careful, it unifies two params to one, if they have same param-name and they are delivered after navigationEnd.

@Injectable({ providedIn: 'root' })
export class ActivatedParamsService {
private param$ = new BehaviorSubject<Record<string, string>>({});
private path$ = new BehaviorSubject<Array<string>>([]);
get paramMap$() {
return this.param$.asObservable();
}
get pathSegment$() {
return this.path$.asObservable();
}
private isShallowEqual<A = string[] | Record<string, string>>(a: A, b: A) {
...
}
constructor(private activatedRoute: ActivatedRoute, private router: Router) {
merge(
of(this.activatedRoute),
this.router.events.pipe(
filter((e) => e instanceof NavigationEnd),
map((v) => this.activatedRoute)
)
).subscribe((activatedRoute) => {
const paths = this.router.routerState.snapshot.url
.replace(/^\//, '').replace(/(\?|#).*$/, '').split('/');
let snapshot = activatedRoute.snapshot;
const paramMap: Record<string, string> = {};
while (snapshot) {
Object.assign(paramMap, snapshot.params);
snapshot = snapshot.children?.[0];
}
// add leaf Child params;
if (!this.isShallowEqual(this.param$.getValue(), paramMap)) {
this.param$.next(paramMap);
}
if (!this.isShallowEqual(this.path$.getValue(), paths)) {
this.path$.next(paths);
}
});
}
}

Hope these can save your time :)

--

--