Common Mistakes Developers Make in Angular Projects and How to Avoid Them

Angular is powerful, but small early mistakes can slowly hurt performance, maintainability, and developer experience. Here are common Angular mistakes and practical ways to avoid them.

Hero image for Angular mistakes article
Angular is powerful — small habits early on make a big difference later.

Angular is a powerful framework, but many developers struggle with it because of small mistakes made early in a project. These mistakes slowly affect performance, maintainability, and developer experience.

Based on real project experience, here are some common Angular mistakes and how you can avoid them.

Not Following a Proper Folder Structure

Many developers put everything inside one folder. Components, services, and models get mixed together. This becomes hard to manage as the app grows.

How to avoid it:

  • Organize by feature, not by file type
  • Keep each feature in its own folder
  • Separate core, shared, and feature modules
  • Use an index.ts (barrel) file for clean exports

Example folder structure (feature-based)

text
src/app/
├── core/
│   ├── auth/
│   └── http/
├── shared/
│   ├── ui/
│   └── constants/
└── features/
    ├── blog/
    │   ├── pages/
    │   ├── components/
    │   ├── services/
    │   └── models/
    └── projects/
        ├── pages/
        ├── components/
        └── services/

A clean structure makes your project easier to scale and understand.

Overusing Logic Inside Components

Angular components should handle UI logic only. Many developers add API calls, heavy calculations, and business logic directly inside components.

How to avoid it:

  • Move business logic to services
  • Keep components simple and readable
  • Use services for API calls and shared logic
  • Use pure pipes for simple template transformations

Example: move API calls to a service

ts
// blog.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface BlogPostDto {
  id: string;
  title: string;
}

@Injectable({ providedIn: 'root' })
export class BlogService {
  constructor(private readonly http: HttpClient) {}

  getPosts(): Observable<readonly BlogPostDto[]> {
    return this.http.get<readonly BlogPostDto[]>('/api/posts');
  }
}

// blog.component.ts
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { BlogService, type BlogPostDto } from './blog.service';

@Component({ /* ... */ })
export class BlogComponent {
  readonly posts$: Observable<readonly BlogPostDto[]> =
    this.blogService.getPosts();

  constructor(private readonly blogService: BlogService) {}
}

This makes components easier to test and reuse.

Ignoring Unsubscribing From Observables

One of the most common Angular mistakes is forgetting to unsubscribe from observables. This leads to memory leaks and performance issues.

How to avoid it:

  • Use the async pipe when possible
  • Unsubscribe manually in ngOnDestroy
  • Use operators like takeUntil with a subject
  • Use takeUntilDestroyed (Angular 16+)

Example: safe subscription (Angular 16+)

ts
import { Component, DestroyRef, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({ /* ... */ })
export class ExampleComponent {
  private readonly destroyRef = inject(DestroyRef);

  constructor(private readonly route: ActivatedRoute) {
    this.route.paramMap
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(params => {
        // safe: auto-unsubscribes on destroy
        console.log(params.get('id'));
      });
  }
}

Example: async pipe (no manual subscribe)

html
<!-- component.html -->
<li *ngFor="let post of posts$ | async">{{ post.title }}</li>

Managing subscriptions properly keeps your app fast and stable.

Using Any Type Everywhere

Using any removes the biggest advantage of Angular and TypeScript. It hides errors and makes code unsafe.

How to avoid it:

  • Define proper interfaces and models
  • Use strict typing
  • Avoid any unless absolutely necessary
  • Use unknown if the type is truly unknown

Example: use a typed interface (no any)

ts
export interface User {
  id: string;
  name: string;
  email: string;
}

// Typed HTTP response
getUser(id: string) {
  return this.http.get<User>(`/api/users/${id}`);
}

Type safety helps catch bugs early and improves code quality.

Poor State Management

Handling state inside multiple components without a plan leads to messy code and bugs.

How to avoid it:

  • Use services for shared state
  • Use RxJS subjects (BehaviorSubject) wisely
  • Use Signals for UI-local state (Angular 16+)
  • For large apps, use NgRx or NGXS

Example: shared state via a service (BehaviorSubject)

ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class UiStateService {
  private readonly _loading = new BehaviorSubject<boolean>(false);
  readonly loading$ = this._loading.asObservable();

  setLoading(value: boolean): void {
    this._loading.next(value);
  }
}

Clear state management keeps data flow predictable.

Not Optimizing Change Detection

Angular apps can become slow if change detection is not handled correctly, especially in large applications.

How to avoid it:

  • Use OnPush change detection where possible
  • Avoid unnecessary function calls in templates
  • Use pure pipes for calculations
  • Keep templates clean and simple

Example: OnPush + avoid calling functions in templates

ts
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';

@Component({
  selector: 'app-user-card',
  templateUrl: './user-card.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserCardComponent {
  @Input({ required: true }) name!: string;
}

Template example (bind to values, not functions)

html
<!-- Good -->
<h3>{{ name }}</h3>

<!-- Avoid -->
<!-- <h3>{{ getName() }}</h3> -->

Small optimizations make a big difference in performance.

Skipping Code Reusability

Copy pasting components or logic is a common habit, but it increases bugs and maintenance effort.

How to avoid it:

  • Create reusable components
  • Use shared modules or standalone components wisely
  • Write generic components with Content Projection
  • Use Directive for shared behavior

Example: a reusable UI component

ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-section-title',
  template: '<h2 class="section-title">{{ text }}</h2>',
  standalone: true,
})
export class SectionTitleComponent {
  @Input({ required: true }) text!: string;
}

Reusable code saves time and improves consistency.

Over-complicating RxJS Streams

RxJS is powerful, but nested subscriptions or giant operators chains can be impossible to debug.

Pro-tip: Use descriptive operators and avoid "Nested Subscriptions" (Subscription inside another subscribe). Use flattening operators like switchMap instead.

Good: Flattening with switchMap

ts
// Avoid this:
 this.user$.subscribe(user => {
   this.orderService.get(user.id).subscribe(orders => ...)
 });

// Do this:
this.orders$ = this.user$.pipe(
  switchMap(user => this.orderService.get(user.id))
);

Not Using trackBy with *ngFor

When you update a list, Angular re-renders the entire DOM by default. trackBy helps Angular identify which items changed and only update those.

Example: trackBy for performance

html
<!-- component.html -->
<li *ngFor="let item of items; trackBy: trackById">
  {{ item.name }}
</li>

Component side

ts
trackById(index: number, item: any): string {
  return item.id;
}

Direct DOM Manipulation

Manipulating the DOM directly using document.querySelector or native elements breaks Angular's abstraction and can lead to bugs with SSR or testing.

Instead, use Renderer2, ElementRef (carefully), or template variables.

Final Thoughts

Angular is not hard, but it requires discipline. Most problems come from rushed decisions and lack of structure.

If you focus on clean architecture, proper typing, and good practices early, Angular becomes a joy to work with.

If you found this useful, share it with another Angular developer.

Stay tuned for more real-world frontend development insights.