Top 10 Common Angular Mistakes to Avoid in 2024

    Monday, March 4, 202410 min read2171 views
    Top 10 Common Angular Mistakes to Avoid in 2024

    In software development, not being aware of common mistakes and not knowing how to avoid them can result in issues that are costly and time-consuming. It can have a significant impact on your project performance and manageability.

    As an Angular developer, it is necessary to write production-level code that is maintainable and easy to debug. However, various common mistakes make it difficult to achieve this.

    In this blog, we will look at the 10 most common mistakes made by Angular developers and what you can do to solve them. We'll also look at the best practices Angular developers should follow for improved Angular application development.

    List of 10 Angular Development Mistakes

    1. Not unsubscribing subscriptions

    2. Using type “any” everywhere

    3. Importing unnecessary libraries

    4. Not splitting your code into multiple components 

    5. Not using lazy loading for modules scenario

    6. Ignoring Angular best practices

    7. Direct DOM manipulation

    8. Not using Track By for *ngFor

    9. Using default change detection strategy

    10. Not using reactive programming

    What is Angular and What is it Used For?

    Let’s start with what exactly Angular development is and what it is used for. Angular is an open-source JavaScript framework which is used to build single-page applications using TypeScript and HTML. It is a powerful front-end framework that facilitates the development of user-interactive web products due to its advanced features and well-designed folder structure. 

    Whether you are an entry-level programmer or a pro-Angular developer, the framework can sometimes be tricky and lead to some common mistakes. However, like any other technology, certain bad practices can affect the effectiveness and maintainability of projects in Angular development.

    Let's take a closer look, one by one, at these common mistakes that Angular developers should be aware of, and what you need to do instead.

    1. Not unsubscribing subscriptions

    • It is a common mistake that can lead to memory leaks.

    • When we subscribe to an Observable, we create a connection between the Observable and the subscriber.

    • The Observable will continue to emit values until we unsubscribe from it, even if the same component that subscribed to it is no longer in use.

    • This will create performance issues in production.

    export class AppComponent implements OnInit {
    subscription: Subscription;
    ngOnInit () {
              let observable = Rx.Observable.interval(1000);        
                   this.subscription = observable.subscribe(x =>
    console.log(x));    
    } 
    /* ngOnDestroy() {
            this.subscription.unsubscribe()
        }  
    */
    }

    To avoid this bad practice, you need to manually unsubscribe from all the Observables after you’ve stopped using the component. This can be done using

    • subscription.unsubscribe() 

    • async Pipe

    • takeUntil()

    • take()

    • takeUntilDestroy()

    2. Using type 'any' everywhere

    • Using ‘any’ everywhere will not make a difference between JavaScript and typescript.

    • Typescript has the ability to report errors when the types do not match. Using ‘any’ can make our code difficult to understand and debug.

    import { Component } from '@angular/core';
    interface User {
     name: String,
     gender: String,
     age: Number,
     address?: String
    }
    @Component({
    template: ''
    // ...
    })
    
    export class TestComponent {
    users: User[] = []
    }

    Using specific types ensures that code is type-safe, understandable, and maintainable.

    • Errors can be handled at compile time, thus reducing error handling at run time.

    • To add optional properties to a type, add the ? modifier to the property. 

    • Code gets secured avoiding unnecessary fields getting added.

    3. Importing unnecessary libraries

    • Importing unnecessary libraries is another common mistake that will increase the bundle size.

    • It will impact the performance of Angular applications.

    • Ways to remove unused libraries in VS Code

    • VS Code also has a ‘Remove Unused Imports’ command if you only need to remove the unused imports without sorting.

      Press:

      • Ctrl + Shift + P on Windows and Linux

      • Command + Shift + P on macOS

      • Type remove unused and select Remove Unused Imports.

    4. Not splitting the code into multiple components

    • The DRY(Do not repeat yourself) principle is the best practice to follow while coding.

    • When we don’t split our code into multiple components, we may end up with a large, unbroken component that does too many things.

    • If the same component becomes too complex, it becomes difficult to maintain and reuse the code which leads to performance issues.

    <header>
     <nav-bar></nav-bar>
    </header>
     <router-outlet></router-outlet>
    <footer>
     <app-footer></app-footer>
    </footer>

    • To avoid this bad practice, we should split our code into multiple components whenever possible.

    • Each component should be designed to handle a specific piece of functionality or user interface element and should be reusable and maintainable.

    • Create separate components for specific functionalities.

    5. Not Using Lazy Loading For Modules Scenario

    • By default, Angular provides an eager loading strategy, which means all the components are loaded unnecessarily. Due to this, application startup time increases and performance reduces.

      const routes: Routes = [
      {     path:    '     ',     loadChildren     :      ()       =>
      import('./home.component').then(m => m. HomeComponent)},
      {     path:    'demo',      loadChildren     :      ()       =>
      import('./demo.component').then(m => m. DemoComponent)},
      {     path:    'live',      loadChildren     :      ()       =>
      import('./live.component').then(m => m.LiveComponent)}];

    • To avoid such issues, Angular developers have an option of a lazy loading strategy. This strategy allows you to load the modules and components only when needed.

    • The implementation of this for multiple components is shown above in the code snippet. Here, the loadChildren property is used in the route configuration. The loadChildren property specifies a function that dynamically loads the components with the help of the import() statement.

    6. Not optimizing DOM manipulations 

    • Manipulating DOM is a common task while building any application.

    • For this purpose, some developers use “ElementRefwhich gives direct access to the DOM, bypassing Angular. 

    • Angular has safer methods to make changes. Angular provides a “Renderer2 class that contains many methods to work with DOM.

    import { Component, Renderer2, ElementRef, OnInit } from '@angular/core';
    @Component({
     selector: 'app-test',
     template: '<h1 #test>Hello World!</h1>'
    })
    export class TestComponent implements OnInit {
    constructor(  private renderer: Renderer2, private el: ElementRef) { }ngOnInit() {  this.renderer.addClass(this.el.nativeElement, 'test');}}

    Reference: Renderer2

    Major drawbacks of ElementRef:

    • Angular keeps the Component & the view in Sync using Templates, data binding & change detection. All of them are bypassed when we update the DOM Directly.

    • DOM Manipulation will not work in a web worker, Server (Server-side rendering), Desktop, mobile app, etc., where there is no browser.

    • The DOM APIs don’t sanitize the data. Hence, it is possible to inject a script, which opens an easy target for the XSS injection attack.

    Use ElementRef and ViewChild to get the reference to the DOM element that you want to manipulate.

    7. Not using Trackby for *ng For:

    • The trackby option improves the Performance of the ngFor if the collection has a large number of items and keeps changing.

    • Angular uses the object identity to track the elements in the collection to the DOM nodes. Hence, when you add or remove an item, Angular will track it and update only the modified items in the DOM.

    • But if you refresh the entire list from the back end, it will replace the objects in the movie collection with the new objects. Even if the movies are the same, Angular will not detect as the object references have changed. Hence, it considers them to be new and renders them again.

    • The Trackby option assigns a unique ID for each item. The ngFor will use the unique ID to track the items. Now, if we refresh the data from the back end, the unique ID will remain the same, and the list will not be rendered again.

    The trackBy function has two arguments: ‘item’ and the ’current item’. It must return an ID that uniquely identifies the item. The following example returns the name as the unique ID.

    import { Component } from '@angular/core';
    import { Item } from '../types/Item';
    @Component({
     selector: 'app-test',
    template: '<li *ngFor="let item of items; let i=index; trackBy trackByFn;">',
    })
    export class TestComponent {
    
     items: Item[] = [];
    
     trackByFn(index: number, item: Item) {
      return item.name;
      }
    }

    8. Using default Change detection strategy

    • Every component in Angular has its change detection cycle, and Angular apps are made of a hierarchical tree of components. 

    • Whenever change detection is triggered, Angular walks down this tree of change detectors to determine if any of them have reported changes.

    • Angular has two CD strategies: 

    1) Default and 2) OnPush

    import    {    Component,    ChangeDetectionStrategy    }   from '@angular/core';
    
    @Component({
     selector: 'app-test',
     template: '',
     changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class TestComponent {
    //...
    }

    • With OnPush strategy, we can skip checks for components that use the OnPush strategy and all of its child components.

    • With this strategy, Angular will only update the component if one or more of these conditions happens:

    1. An event from the same component or one of its children.

    2. The Async pipe linked to the template emits a new value.

    3. Manually triggered the change detection

    • To trigger it manually, use detectChanges(), Application.tick(), and markForCheck().

    1. detectChanges() will run changeDetection on this view and its children.

    2. markForCheck() marks all onPush ancestors to be checked once, either on the current or next detection cycle

    3. Application.tick() will change detection for the entire application.

    9. Not using Rxjs Library

    • Rxjs is a library for reactive programming based on observables that help in using asynchronous code and callbacks. 

    • Angular uses RxJS as a core part of it. RxJS provides a wide range of operators for working with asynchronous data streams, making it a powerful tool for reactive programming in Angular applications.

    import { Component } from '@angular/core';
    import { of, map } from 'rxjs';
    
    @Component({
     selector: 'app-test',
     template: ''})
    
    export class TestComponent {
     /** creates observable. */
     numbers = of(1, 2, 3);
    
     /** A new squares observable created by piping through the `map` operator. */
     squares = this.numbers.pipe(
      map(num => num num)
    );
    
    // subscribe to the new observable
    result = this.squares.subscribe(value =>
      console.log(value)
    );
    // Logs
    // 1
    // 4
    // 9}

    • Also, Rxjs Subject is a popular way to create and control an observable of your own.

    • A Subject is a special kind of observable.

    1. You can push values into that Observable by calling its next() method.

    2. It is a "multicast" observable, which means all subscribers of a Subject instance receive the same values from that instance.

    Reference: RXJS Library

    10. Ignoring Angular Best Practices

    Angular provides specific guidelines for naming conventions, file structure, component architecture, and much more. Avoiding these guidelines can cause inconsistency, as well as reduce the readability of the code.

    Here are some examples of ignoring Angular best practices:

    A. Not following the Angular guidelines

    It becomes more challenging to understand the code if the developer does not follow the naming conventions for components, services, and variables. It becomes difficult to understand why a particular functionality is implemented, or a variable is used. It can also lead to naming conflicts and make it difficult to identify bugs (if any) and implement new additional features.

    Incorrect naming conventions:

    /* Example of not following the Angular style guide for naming conventions Ignoring the guideline for using PascalCase for component class names and kebab-case for component selectors */
    @Component({
    selector: 'appproductExample', // Not using kebab-case for selector 
    templateUrl: './productexample.component.html',
    styleUrls: ['./productexample.component.css']
    })
    export class productexampleComponent { // Not using PascalCase for class name 
    // ...
    }

    Correct naming conventions:

    @Component({
    selector: 'app-product', // Using kebab-case for selector
    templateUrl: './product.component.html',
    styleUrls: ['./product.component.css']
    })
    export class ProductComponent { // Using PascalCase for class name
    //....
    }

    B. Not sticking to the recommended folder structure

    Angular recommends a specific folder structure for organising different types of components, services, directives, pipes, and other files. Avoiding implementation of this folder structure makes it difficult to locate and manage files. When the application is bulky or complex, it will be hard to handle the code. For example, not separating components, services, and other files into their respective folders can result in a cluttered and confusing directory structure.

    Folder structure to avoid at any cost:

    // Example of not sticking to recommended folder structure/* Ignoring the guideline for organizing components in a      separate folder and not  using a clear folder structure for other files */
     app/
      components/
       product.component.ts // Not organizing components in a separate folder
    services/product.service.ts // Not organizing services in a separate folder       
    product.module.ts // Not using folder structure for files
    product.component.css
    product.component.html

    Folder structure to be implemented:

    src/
      app/
      services/
        demo.service.ts
        auth.services.ts
      components/
      productList/
        productList.component.ts
        productList.component.css
        productList.component.html

    C. Not using Angular features appropriately

    Angular has various features and APIs to simplify the development process, improve performance, and enhance maintainability. Ignoring or misusing these features can result in reducing readability in code. It is difficult to optimise and hard to maintain a code base.

    For example, not implementing Angular’s built-in Dependency Injection (DI) system can result in tightly coupled and hard-to-test code.

    Incorrect Usage of Angular’s Dependency Injection System:

    @Component({
    selector: 'app-example',
    Template: `template code`
    providers: [Demo Service] // Not using DI to inject the service
    })
    export class ExampleComponent {
    constructor() {
    this.demoService = new DemoService (); // Not using DI to inject the service
     }
    }

    Correct Usage of Angular’s Dependency Injection System:

    export class ProductListComponent {
    constructor ( private demoService: DemoService) {
    // Fetch products from demo service directly
    this.demoService.getProducts().subscribe (products => {
    // Handle products
    });
        }
    }

    D. Employ lazy loading

    By loading modules only when necessary, the lazy loading strategy can enhance the speed of your Angular application. This method can enhance the user experience overall and shorten the time it takes for your programme to launch initially.

    Angular's lazy-loading approach allows us to divide the application into smaller feature modules that can be loaded as needed when the user navigates the application. This can substantially cut the application's initial load time and increase speed by just loading the modules that are needed.

    E. Use Smart and Dumb Components

    Angular's "smart" and "dumb" components are a method for putting together application components. This pattern is called the "Container/Presentation" pattern.

    A "smart" component, also referred to as a container component, manages the application's state and orchestrates component interactions. A smart component is often connected to a service or store that handles the application's state. It comprises the application's business logic and manages data retrieval, transformation, and manipulation. Smart components handle user interactions and propagate state changes to their descendant components.

    F. Use the OnPush Change Detection Strategy

    Change detection is a fundamental topic in Angular that can influence the performance of the application. Change detection refers to the process of recognising changes in your application's data and changing the display accordingly. Angular's default change detection approach is "Default", which might be resource-intensive. Angular developers can, however, improve the speed of the application by implementing the "OnPush" change detection approach. The "OnPush" technique only detects changes when a component's input attributes change or an event occurs.

    Summary

    To summarise, providing the best Angular development service involves avoiding common but serious mistakes. Remember to,

    • Unsubscribe from Observables to prevent memory leaks by cleaning up subscriptions.

    • Use Specific Types. Don't use "any" everywhere; use specific types for safer and clear code.

    • Cut down unused libraries and import what you need to keep your app lean and fast.

    • Split code into components following the DRY principle; smaller components are easier to manage and reuse.

    • Improve startup time and performance by loading what's necessary.

    • Optimise DOM Manipulation using Angular's Renderer2 for safer and more efficient changes.

    • Use TrackBy for ngFor and improve performance when dealing with dynamic collections.

    • Choose Change Detection Strategy. Consider OnPush for better performance.

    • Embrace RxJS and leverage its power for reactive programming in Angular.

    • Stick to Angular Best Practices by following guidelines for naming, folder structure, and feature usage.

    By avoiding these mistakes and following best practices, Angular developers can build Angular apps that are cleaner, faster, and more maintainable. Happy coding!

    24

    Related articles

    This website uses cookies to analyze website traffic and optimize your website experience. By continuing, you agree to our use of cookies as described in our Privacy Policy.