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.
Not unsubscribing subscriptions
Using type “any” everywhere
Importing unnecessary libraries
Not splitting your code into multiple components
Not using lazy loading for modules scenario
Ignoring Angular best practices
Direct DOM manipulation
Not using Track By for *ngFor
Using default change detection strategy
Not using reactive programming
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.
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()
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.
Importing unnecessary libraries is another common mistake that will increase the bundle size.
It will impact the performance of Angular applications.
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.
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.
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.
Manipulating DOM is a common task while building any application.
For this purpose, some developers use “ElementRef” which 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.
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;
}
}
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:
An event from the same component or one of its children.
The Async pipe linked to the template emits a new value.
Manually triggered the change detection
To trigger it manually, use detectChanges(), Application.tick(), and markForCheck().
detectChanges() will run changeDetection on this view and its children.
markForCheck() marks all onPush ancestors to be checked once, either on the current or next detection cycle
Application.tick() will change detection for the entire application.
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.
You can push values into that Observable by calling its next() method.
It is a "multicast" observable, which means all subscribers of a Subject instance receive the same values from that instance.
Reference: RXJS Library
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:
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
//....
}
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
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
});
}
}
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.
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.
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.
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!
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.