
We’re excited to hear your project.
Let’s collaborate!
“Learn from your mistakes” should be every developer's life mantra. But that doesn't mean that they should be all your mistakes, right? With a list including the most common Angular mistakes at hand, you'd get to narrow down the number of possible pitfalls that you risk falling into to the... uncommon, the less predictable ones.
So, what are the traps that Angular developers fall into most often as they build their applications?
We've run our own "investigations" for you:
… and trimmed down our list to 5 most frequent mistakes that Angular developers make:
"I have used OnPush strategy for my component and no bindings have changed, but the ngDoChecklifecycle hook is still triggered. Is the strategy not working?"
I bet that you've stumbled across this question (maybe put into different words) on various developer forums. It could easily win any “popularity” test there since it keeps coming up again and again.
But, before we give it a straight answer and settle this matter for good, let's focus on these 2 “change detecting” lifecycle hooks in Angular. And see where the problem stems from:
As you well know it, AngularJs has the watch feature, which “alerts” you whenever a value changes. Angular, on the other hand, has replaced the watch and scop feature with component inputs as properties. Moreover, it has added the ngOnChanges lifecycle hook, as well.
And here's where things get “tricky” and risk to turn into the trap that many Angular developers fall into:
The OnChanges event doesn't emit if/when a deep field of the input property changes. Hence, the value of the input is the reference of the object.
How do you approach this problem? You have several methods at hand, in fact, but I'm going to focus on the most common one:
Using the ngDoCheck lifecycle hook once you run the change detection process!
Now here's the answer that I've promised, to the above-mentioned common developer question:
The onPush strategy for detecting changes in Angular does work: the hook is triggered by the design itself!
Note: another ridiculously frequent mistake that developers make is to take "Angular" for "AngularJS"! Be better than that: the term "AngularJS" should be used only when you refer to the old Angular and to Angular 1, while Angular 2/2+ 4 and 5 is just... "Angular". Mind you don't delve deep into this confusion yourself, too!
“Forgetting” or simply ignoring this issue.
And you have no “excuse”, you know, for overlooking to clean up your subscriptions in Angular. Considering that there are methods and libraries developed precisely for handling these unsubscriptions once you've finished using an event or an observable in JavaScript.
Why does this “clumsiness” risk to grow into a major issue? Because lingering subscriptions cause memory leaks in your system.
And when it comes to the unsubscription process, there are 2 scenarios:
To recap: Remember to unsubscribe when you no longer use a service/component!
One of Angular's (compared to AngularJS) key improvements is its Hierarchical Dependency Injection, agree?
This means that now you're free to instantiate a service several times (services used to be singletons back in the AngularJS's “glory days”, remember?).
Now that being said, let's have a look at a more than likely scenario:
Imagine yourself fetching your heroes using a heroes service:
@Injectable() export class HeroesService { heroes: Hero[] = []; constructor(private http: Http) { this.http.get('http://give-me-heroes.com').map(res => { return res.json(); }).subscribe((heroes: Hero[]) => { this.heroes = heroes; }); } getHeroes() { return this.heroes; } }
Nothing unexpectedly wrong till here:
Now, let's take a look at the hero component, too:
@Component({ selector: 'hero', template: '...', providers: [HeroesService] }) export class HeroComponent { constructor(private heroesService: HeroesService) {} } @NgModule({ declarations: [HeroComponent] } export class HeroesModule { ... }
As you can see, first the HeroComponent declares the HeroesService provider in the @Component.providers array, next it incorporates it in the constructor.
All well and good till you realize that each HeroComponent instance instantiates a new instance of the HeroesService.
To put it more simply:
Your HeroesService will be fetching the data (by HTTP request) several times, for each and every HeroComponent instance!
And this is due to the Hierarchical DI in Angular.
The solution for avoiding this issue — no doubt one of the most common Angular mistakes?
Declaring the service in the @NgModule.providers instead:
@Component({ selector: 'hero', template: '...' }) export class HeroComponent { constructor(private heroesService: HeroesService) {} } @NgModule({ declarations: [HeroComponent], providers: [HeroesService] } export class HeroesModule { ... }
There! The provider will now be instantiated once and for all. “For all” the HeroComponent instances I mean.
“How come?” You might ask yourself.
Because the provider (declared in the NGModule now) is a singleton now. Therefore all the other modules can use it.
Although I've already said this about another mistake from this list, I now tend to consider this one here instead as being one of the most common Angular mistakes:
Manipulating the DOM directly; oftentimes from the controller itself!
And this is one of those top 10 Angular mistakes that any developer stands a high risk of making. Since manipulating the DOM is such a common task when using this platform:
Then... you fall right into it: you take the easy way out and hack the DOM directly!
Now what I feel like pointing out it here is that:
Angular's grown from a web framework into a platform!
And this can only mean that you're now free to decouple your Angular application's codebase from the renderer and:
And decoupling opens the door to other possibilities for you to explore and to tap into, such as using web workers or AOT (ahead of time compilation).
Speaking of the latter:
AOT enables you to compile your app's templates in the build time of your server. Moreover, you won't need to use the oversized @angular/compiler package in your bundle, which leads to a lower size and load time.
Yet, for future-proofing these “facilities” that Angular provides, you'll need to abide by... some sort of rules at least. And one of them is:
Restraining yourself from manipulating the DOM directly, using jQuery, the global document object or the ElementRef.nativeElement.
Instead, use the Renderer2 service (or Renderer for Angular 2):
It's 2 things that this wrapper to the view mutation layer will enable:
In short: Resist “temptation” and never ever touch the DOM directly!
And declaring a component in multiple NGModule-s is one of those most common Angular mistakes that end up throwing an error “at” you.
“Why, so?”
Because you need to declare each component in its own NgModule — and to list it in the @Ngmodule.declarations array — in order for it to be available for the views.
If you do need to declare the very same component in multiple modules, you'll need to consider the relationship between those modules:
Is it a parent-child one, maybe?
If this is the case, then:
If not (if we can't be talking about a parent-child module relationship), then you'll need to declare another NgModule as the module of the shared data.
It's only by knocking your head against unexpected issues while building your projects on this platform, that you'll get to grow as an Angular developer.
And still, instead of embracing the “fail fast, fail often” concept, wouldn't it be best if you:
We’re excited to hear your project.
Let’s collaborate!