Top 5 Angular Mistakes - You Must Know Them
Top 5 Angular Mistakes - You Must Know Them

This are 5 most popular angular mistakes that you need to know and avoid.

Subscribe /promises instead of streams

The most popular mistake that I see is bringing ways of working with Javascript inside Angular. Angular leverages RxJs and streams a lot so we must write all our code in streams if we want to make it scalable, easy to support and effecient.

export class UsersComponent implements OnInit, OnDestroy {
  users: string[]
  usersSubscription: Subscription

  constructor(private usersService: UsersService) {}

  ngOnInit(): void {
    this.usersSubscription = this.usersService.getUsers().subscribe((users) => {
      this.users = users
    })
  }

  ngOnDestroy(): void {
    this.usersSubscription.unsubscribe()
  }
}

Here we use getUsers function from services which returns us observable. We just use subscribe and assign the result to local property. This code will work but it has several problems.

  1. We use here subscribe to stream which means we need to unsubscribe from it by hands
  2. Our code is imperative and not declarative. We say directly what needs to be done on success
  3. We don't leverage RxJs and Angular but just write code as a plain Javascript

And we can make this code even worse. When people don't know anything about streams but they jump to Angular they want to convert stream to promises because they know how to work with promises. So then you can see such code.

this.usersService
  .getUsers()
  .toPromise()
  .then((users) => {
    this.users = users
  })

This is the worst thing that you can do and you should never do this in Angular.

How we can write this code correctly? We need to use RxJs stream directly instead of subscribe.

export class UsersComponent {
  users$: Observable<string[]>

  constructor(private usersService: UsersService) {
    this.users$ = this.usersService.getUsers()
  }
}

This is how our code looks with streams. So we just store a reference in additional property. We don't need subscribe so we don't need to unsubscribe. Also we set our stream in constructor so we won't have an undefined value by default. Now we just need to use async pipe in order to render data and angular will unsubscribe everything by itself.

<div *ngFor="let user of users | async">
  {{ user }}
</div>

Unsubscribe

As I already mentioned in previous mistake we need to unsubscribe from stream every single time when we write subscribe. If we just write subscribe and switch to other page our component will be destroyed but our subscribe will not because Angular doesn't do anything about it. And this is extremely important for good performance in your applications.

Here are 2 solutions to this problem. Either you use async pipe and Angular do unsubscribe for you. This is the best approach or you unsubscribe yourself. The easiest way for this is to store result of subscribe and call unsubscribe in onDestroy.

ngOnInit(): void {
  this.usersSubscription = this.usersService.getUsers().subscribe((users) => {
    this.users = users
  })
}

ngOnDestroy(): void {
  this.usersSubscription.unsubscribe()
}

Not using enough typescript

The next mistake is not using enough Typescript. When you just jump to Angular you can write Typescript code as Javascript or even use any in every single place. Both approaches are bad because you need to spend more time writing your types than your code to make your application safe und understandable.

Even in our small example we could just create variables without data type.

export class UsersComponent implements OnInit, OnDestroy {
  users
  usersSubscription
}

But actually this just means that this variables are implicitly any. This means that we get 0 help from Typescript and 0 autocomplete. To avoid this we must always provide data types and create interfaces for all our entities.

In this case if we write not correct type we directly get a compile error.

this.users = 1
Type '1' is not assignable to type 'string[]'

God object modules

The next popular mistake is creating a God object modules. It means that you don't split your module enough in small chunks but just throw in the module a bunch or components, services and modules. The most popular module for this is sharable. You start it as a small sharable module and you throw more and more stuff inside until it becomes unusable.

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild(routes),
    StoreModule.forFeature('article', reducers),
    EffectsModule.forFeature([GetArticleEffect, DeleteArticleEffect]),
    LoadingModule,
    ErrorMessageModule,
    TagListModule,
    UserModule,
    DateModule,
  ],
  declarations: [
    ArticleComponent,
    ArticleImageComponent,
    PostComponent,
    ListComponent,
    TopBarComponent,
    UsersListComponent,
    UserComponent,
    DatesComponent,
    DateComponent,
  ],
  providers: [
    ArticleService,
    SharedArticleService,
    PostService,
    ListService,
    DatesServices,
    LocalStorageService,
  ],
})
export class SharedModule {}

I highly recommend you to avoid god modules especially sharable because it can fast because a "throw everything inside" module. The same problem can be with a god service which can be a 5 thousand lines and does all possible things not related to one another.

Performance: trackBy

In Angular loops, you have to provide a function that keeps track of the items, to see whether or not they have changed. If you don't do this, Angular doesn't know which items are different. So when there's a change, it will re-render the entire thing instead of only the changed item(s).

The main problem is that Angular doesn't warn you if you don't provide a trackBy in comparison to React for example and it's really easy to forget this.

<div *ngFor="let user of users; trackBy: myTrackingFn | async">
  {{ user }}
</div>
export class UsersComponent implements OnInit, OnDestroy {
  ...
  myTrackingFn(index, value): number {
    return value
  }
}

As you can see we tell Angular how to track our array and what is unique there. In this case Angular can rerender elements efficient.

Bonus Performance: onPush change detection strategy

One of the benefits of a framework like Angular, is that it can do a lot for you. An important aspect of this is keeping track of changes. However, by default, Angular has very aggressive change detection, meaning it will check for changes and potentially re-render on every small change (even a scroll event). This is nice when prototyping, but in production this may lead to issues.

Personally, I believe that the default change detection should be OnPush. It will only re-render when inputs change, events fire or when manually triggered. Often, OnPush just works. On some occassions, for instance if you have a few computed properties that need to be displayed (say, you have a calculation that you are doing in your .ts file and need to show it in the template), you will have to manually trigger a change detection cycle.

How to enable OnPush? In the component decorator, add the following line (and import ChangeDetectionStrategy from @angular/core):

changeDetection: ChangeDetectionStrategy.OnPush

So this were top 5 angular mistakes that you should be aware of.

Also if you want to improve your programming skill I have lots of full courses regarding different web technologies.