Beware! Angular can steal your time.

Alexander Poshtaruk
Angular In Depth
Published in
7 min readJul 2, 2019

Small gotchas from my(and not only my:) Angular experience.

Pic of Andreas Schau from Pixabay

AngularInDepth is moving away from Medium. This article, its updates and more recent articles are hosted on the new platform inDepth.dev

Angular can do everything — or almost everything. But sometimes this ‘almost’ makes you waste time for coding workarounds or trying to find out why something happens or doesn’t work as expected. I want to save you some time and describe these angular gotchas from my work experience in this article. We are starting.

#1 Your custom directive doesn’t work

So you found a very nice third-party Angular directive and gonna use it on native elements in Angular template. Cool! Let's do this.

It doesn’t work and Angular keeps silence

OK, we start the application and… it doesn’t work. As a normally experienced developer, you check Chrome Dev Tools console. And see nothing:-)

Then some bright head in your team decides to put it in square brackets.

snippet link

And now, after some time waste, we observe the reason:

Ha! We just forgot to import the module with the directive

Now the problem is obvious — we just forgot to import third-party directive module to our Angular application module 😬.

So, the rule of thumb — never use directives without square brackets.

You can check yourself this behavior in this Stackblitz playground.

*Thanks to Alex Okrushko for telling me about this behavior!

#2 ViewChild returns ‘undefined

Say, you put a ref to input element in Angular template

Ref #inputTag

And want to create stream with input text updates with RxJS fromEvent function. For that you need input native element ref which you can get with Angular ViewChild decorator:

Create a stream$ from input element data changes with RxJS fromEvent function

Ok, let's try it:

Nice pic — can help to use it again)

WAT?

Rule of thumb here is - if ViewChild returns undefined — look for*ngIf in a template.

Here it is — our ngIf culprit

Also, check for any other structural directives or ng-template above that element (thanks to Alexey Zuev for pointing that out).

Possible solutions:

  1. Just hide element in a template when you don’t need it. In that case element exists always and ViewChild can return the reference to it in ngAfterViewInit hook function.

2. Another possibility — use setters (method from Alex Okrushko):

snippet link

Here once Angular assign inputTag property with defined value — then we create a stream from input element entered data.

More to read:

  • In Angular 8 ViewChild resolution can be static and dynamic — you can read more about it here
  • Have difficulties with understanding RxJS code? Check my video-course “Hands-on RxJS

#3 How to run code on *ngFor list update (after elements appeared in DOM)

Say, you have some nice custom scroll directive and want to apply it on the list, generated by Angular *ngFor directive.

snippet link

The usual case here can be that if a list is updated we should run scrollDirective.update method to recalculate scroll math since items total height is changed as well.

You may think that we can do it inside ngOnChange hook function:

snippet link

But…it is called before new items list is actually displayed by the browser, so scroll directive recalculation will be wrong 😒.

But how can we make a call just after *ngFor finished its work?

We can do this in 3 simple steps:

a) Put refs to elements where *ngFor is applied (#listItems).

b) Get a list of these elements with ViewChildren Angular decorator. It returns entity by QueryList type.

c) QueryList class provides read-only changes property which emits every time when a list is changed.

snippet link

Problem is solved and you can play with the example on a Stackblitz playground.

#4 ActivatedRoute.queryParam obstacles if params are optional

To understand the next issue lets review this code:

snippet link
  1. We defined routes and add RouterModule to main app module. Routes configured the way that if no route is provided in URL — then we redirect use to /home page.

2. We bootstrap AppComponent in the main module.

3. AppComponent uses <router-outlet> to display respective route components.

4. The main part: we want to get route queryParams from URL to our needs.

Say URL is

https://localhost:4400/home?accessToken=someTokenSequence

then queryParams will be:

{accessToken: ‘someTokenSequence’}

Test app with routing

You may ask — what is the issue? You’ve got params — bingo!…?

Well, if you look closely at a screenshot above you may observe that queryParams are emitted twice. First empty object emission happens as a part of Angular Router initialization process. And only after that we get an object with actual query params ({accessToken: ‘someTokenSequence’} in our case).

The problem is that if no query params are provided in URL — router doesn’t emit anything — no second empty object that will tell you that URL has no params.

No second emission from ActivatedRoute.queryParams

So if your code waits for the second emission to get actual data (empty or not) — it will never run if no query params are provided in the URL.

How to solve it? RxJS can help us here. We will create two observables from ActivatedRoute.queryParams.

  1. First paramsInUrl$ observable will emit if queryParams value is not empty:
snippet link

2) Second noParamsInUrl$ observable will emit empty value only if no queryParams are located in URL.

snippet link

3) Now we have to combine them together with RxJS merge function:

snippet link

Now composed params$ observable emits value only once whether queryParams are present(emits empty object) or not(emits object with queryParams values);

You can check how this code works here.

#5 Low page FPS (or some interactivity delays)

So you have a component, that displays some formatted values, like this:

This component does two things:

  1. display items array (assumed this is done once). Also, it is formatting displayed output by calling formatItem method
  2. display mouse coordinates (definitely value will be updated quite often).

You are not expecting some big performance influence and run a performance test just to comply with the formalities. But performance test shows some strange behavior:

Many formatItem calls and quite a big CPU load.

But why??
This is because each time Angular redraws your template — it calls all functions from template too (formatItem in our case). So if your template functions do some heavy calculations — it will affect CPU load and user experience.

How to fix it? Just make this formatItem calculations forehand and display calculated value already.

Now your performance testing results looks quite decent.

Only 6 calls of formatItem and low CPU load.

Our app works much better now but this solution still has cons:

  • Since we display mouse coordinates in a template — mousemove event still causing change detection triggering. But since we need these coordinates — we couldn’t avoid it here.
  • In case your mousemove event handler should do only calculations (without visual result) — then to speed up the app:

a) You can use NgZone.runOutsideOfAngular inside the handler function to prevent change detection on mousemove (it will affect this specific handler only)

b) You can prevent zone.js patching for some events by providing this line in polyfill.ts (provided by Alexey Zuev). It will affect the whole Angular app.

* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames

Read more on this topic:

  1. Optimize #Angular bundle size in 4 steps
  2. Improve Performance with Web Workers
  3. How To Fix the Most Common Angular Performance Problems Like a Doc
  4. How `runOutsideAngular` might reduce change detection calls in your app

Conclusion

So now after reading this article, you should be 5-gotcha-more clever 😎. Have your own gotchas? Or want to see some new staff in the next articles? Leave your proposals in comments!

Special thanks to Alexey Zuev, Lars Gyrup Brink Nielsen and Siyang Kern Zhao for reviewing the article!

Did you like the article? Tweet about it 🤓

Let's keep in touch on Twitter!

Do you want to see a part2 article of Angular gotchas? Press 👏 more than once! 😉

--

--

Alexander Poshtaruk
Angular In Depth

Senior Front-End dev in Tonic, 'Hands-on RxJS for web development' video-course author — https://bit.ly/2AzDgQC, codementor.io JS, Angular and RxJS mentor