AngularJS is an open-source JavaScript framework from Google for developing front-end applications. Its subsequent versions were numbered Angular 2 and onward. Google released AngularJS in 2010. It garnered immediate popularity and support because now static HTML pages could be made interactive. However, soon, other frameworks were released, which began highlighting the drawbacks of AngularJS. Google then went for a complete rewrite and launched Angular, which used TypeScript as the new language to resolve the drawbacks of the previous version. Angular 2 was then introduced in 2016. The decision to shift from JavaScript to TypeScript helped avoid the pitfalls of JavaScript and also introduce a small amount of static typing, a feature that many web developers were demanding.
19 Angular Best Practices
Today, over 40% of front-end developers use Angular. Given its wide popularity, we thought it would be a constructive activity to highlight a few Angular JS best practices that we are appreciative of
1. Use Angular CLI
Angular CLI is one of the most powerful accessibility tools available when developing apps with Angular. Angular CLI makes it easy to create an application and follows all the best practices! Angular CLI is a command-line interface tool that is used to initialize, develop, scaffold, maintain, and even test and debug Angular applications.So instead of creating the files and folders manually, use Angular CLI to generate new components, directives, modules, services, and pipes.# Install Angular CLI
npm install -g @angular/cli
# Check Angular CLI version
ng version
2. Maintain proper folder structure
Creating a folder structure is an important factor we should consider before initiating our project. Our folder structure will easily adapt to the new changes during development.
-- app
|-- modules
|-- home
|-- [+] components
|-- [+] pages
|-- home-routing.module.ts
|-- home.module.ts
|-- core
|-- [+] authentication
|-- [+] footer
|-- [+] guards
|-- [+] http
|-- [+] interceptors
|-- [+] mocks
|-- [+] services
|-- [+] header
|-- core.module.ts
|-- ensureModuleLoadedOnceGuard.ts
|-- logger.service.ts
|
|-- shared
|-- [+] components
|-- [+] directives
|-- [+] pipes
|-- [+] models
|
|-- [+] configs
|-- assets
|-- scss
|-- [+] partials
|-- _base.scss
|-- styles.scss
3. Follow consistent Angular coding styles
To ensure a project adheres to proper coding standards, it's important to follow a set of guidelines that promote readability, maintainability, and consistency. Here are some essential rules to implement:
- - Limit files to 400 Lines of code.
- - Define small functions and limit them to no more than 75 lines.
- - Have consistent names for all symbols. The recommended pattern is feature.type.ts.
- - If the values of the variables are intact, then declare it with ‘const’.
- - Use dashes to separate words in the descriptive name and use dots to separate the descriptive name from the type.
- - Names of properties and methods should always be in lower camel case.
- - Always leave one empty line between imports and modules; such as third-party and application imports and third-party modules and custom modules.
4. Typescript
TypeScript is a superset of JavaScript, designed to develop large-scale JavaScript applications. You don’t have to convert the entire JavaScript code to TypeScript at once; migration can be done in stages. Major benefits of TypeScript include:
- Support for classes and modules
- Type-checking
- Access to ES6 and ES7 features before they are supported by major browsers
- Support for JavaScript packaging and so on
- Great tooling support with IntelliSense
5. Use ES6 Features
ECMAScript is constantly updated with new features and functionalities. Currently, ES6 offers many new features that can be utilized in Angular.
Here are a few ES6 features:
- Arrow Functions
- String Interpolation
- Object Literals
- Let and Const
- Destructuring
- Default Parameters
6. Use trackBy along with ngFor
When using ngFor to loop over an array in templates, it is beneficial to use a trackBy function. This function returns a unique identifier for each DOM item, which helps Angular optimize rendering. Without trackBy, Angular re-renders the whole DOM tree when the array changes. By using trackBy, Angular can identify which element has changed and will only update that specific element in the DOM.
Here is an example of how to use ngFor with trackBy:
export class MyApp {
items: any[] = [];
getItems() {
// Load more items
}
trackByFn(index, item) {
return index; // or item.id
}
}
<div *ngFor="let item of items; trackBy: trackByFn">
{{ item }}
</div>
By returning a unique identifier for each item, trackBy ensures that only the updated items are re-rendered, enhancing performance and efficiency.
7. Break down into small reusable components
This principle can be seen as an extension of the Single Responsibility Principle. Large components are often difficult to debug, manage, and test. When a component becomes too large, it's beneficial to break it down into smaller, reusable components. This reduces code duplication and makes the application easier to manage, maintain, and debug with less effort.
By following this approach, we can achieve more modular and maintainable code, improving the overall quality and performance of the application.
8. Use Lazy Loading
To improve the performance of an Angular application, it's essential to lazy load modules whenever possible. Lazy loading ensures that modules are loaded only when they are needed, reducing the initial load time and improving the application's boot time by not loading unused modules.
Without Lazy Loading in h4
// app.routing.ts
import { WithoutLazyLoadedComponent } from './without-lazy-loaded.component';
{
path: 'without-lazy-loaded',
component: WithoutLazyLoadedComponent
}
With Lazy Loading in h4
// app.routing.ts
{
path: 'lazy-load',
loadChildren: () => import('./lazy-load.module').then(m => m.LazyLoadModule)
}
// lazy-load.module.ts
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LazyLoadComponent } from './lazy-load.component';
@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: LazyLoadComponent
}
])
],
declarations: [
LazyLoadComponent
]
})
export class LazyLoadModule { }
By using lazy loading, you can significantly enhance the user experience by decreasing the initial loading time and optimizing the overall performance of your Angular application.
9. Use Index.ts
Using an index.ts file helps to consolidate all related exports, making the import statements more concise and easier to manage. This approach reduces the complexity of the import statements and keeps the codebase cleaner. Here's an example of how it works:
index.ts File in h4
// /heroes/index.ts
export * from './hero.model';
export * from './hero.service';
export { HeroComponent } from './hero.component';
Import Statement in h4
Instead of importing each module individually, you can import them all using the folder name:
import { Hero, HeroService, HeroComponent } from '../heroes'; // index is implied
This method simplifies the import process and keeps your code more organized, as you don't need to remember the specific file names for each export. It also makes refactoring easier, as changes in the file structure or file names won't affect the import statements as long as the index.ts file is properly maintained.
10. Template Logic Extraction to Component Logic
Extracting all template logic into a component can improve testability and reduce bugs when the template changes. Here's how you can achieve this:
Logic in Templates
This approach has logic directly in the template, which can make testing and maintenance harder:
<!-- template -->
<div>Status: {{ apiRes.status === 'inActive' || 'hold' ? 'Unavailable' : 'Available' }}</div>
// component
ngOnInit(): void {
this.status = apiRes.status;
}
Logic in Component
By moving logic from the template to the component, you can simplify the template and make the logic easier to test:
<!-- template -->
<div>Status: {{ isUnavailable ? 'Unavailable' : 'Available' }}</div>
// component
ngOnInit(): void {
this.status = apiRes.status;
this.isUnavailable = this.status === 'inActive' || this.status === 'hold';
}
By following this approach, you ensure that all business logic resides within the component, leading to cleaner, more maintainable, and testable code.
11. Cache API calls
When making API calls, some responses remain unchanged for extended periods. In such cases, introducing a caching mechanism can enhance application performance and reduce unnecessary network requests. Here’s how you can do it:
- Check Cache Before API Call: Before making an API call, check if the response is already stored in the cache.
- Make API Call if Not Cached: If the response is not found in the cache, make the API call and store the result.
Set Cache Expiry: Introduce a cache expiration time for APIs whose values change infrequently, ensuring that stale data is eventually refreshed.
12. Using Observables with Async Pipe in Angular Templates
Angular's async pipe is a powerful tool for working with observables directly in templates. It simplifies the process of subscribing to and unsubscribing from observables, ensuring efficient memory management and preventing memory leaks.
Without Using Async Pipe
When not using the async pipe, you manually subscribe to observables in the component and handle the subscription lifecycle:
Template:
{{ text }}
Component
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { DataService } from './data.service';
@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent implements OnInit, OnDestroy {
text: string;
private subscription: Subscription;
constructor(private dataService: DataService) {}
ngOnInit(): void {
this.subscription = this.dataService.getData().subscribe(
value => this.text = value
);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
13. Declare safe strings
In TypeScript, you can enforce that a string variable only accepts specific predefined values by using string literal types. This approach enhances type safety and helps catch potential bugs at compile time.
Normal String Declaration
private vehicleType: string;
// Allowed assignments
this.vehicleType = 'four wheeler';
this.vehicleType = 'two wheeler';
this.vehicleType = 'car'; // No compile-time error
With normal string declaration (private vehicleType: string;), TypeScript allows any string value to be assigned to vehicleType, which can lead to unintended bugs if incorrect values are assigned.
Safe String Declaration using Literal Types
private vehicleType: 'four wheeler' | 'two wheeler';
// Allowed assignments
this.vehicleType = 'four wheeler';
this.vehicleType = 'two wheeler';
// Compile-time error
this.vehicleType = 'car'; // Type '"car"' is not assignable to type '"four wheeler" | "two wheeler"'
Using literal types (private vehicleType: 'four wheeler' | 'two wheeler';), TypeScript restricts vehicleType to only accept the specified literal values ('four wheeler' or 'two wheeler'). Any attempt to assign a value outside of these specified literals will result in a compile-time error, providing early detection of bugs.
14. Importance of Proper Typing in TypeScript
In TypeScript, proper typing is crucial for maintaining code reliability and facilitating easier refactoring. Let's examine why declaring variables with specific types is essential, using an example to illustrate its benefits:
Example: Proper Typing and Error Prevention
Consider an interface IProfile that defines specific properties:
interface IProfile {
id: number;
name: string;
age: number;
}
In a component LocationInfoComponent, we intend to assign an object to userInfo based on IProfile:
export class LocationInfoComponent implements OnInit {
userInfo: IProfile;
constructor() { }
ngOnInit() {
// Error: Object literal may only specify known properties, and 'mobile' does not exist in type 'IProfile'.
this.userInfo = {
id: 12345,
name: 'test name',
mobile: 121212 // Error occurs here
}
}
}
By adhering to proper typing practices in TypeScript, developers can significantly reduce bugs, improve code maintainability, and streamline the development process by catching potential issues early. This approach ensures that the application remains robust and scalable as it evolves.
15. State Management
State management is a critical aspect of software development, particularly in Angular, where it facilitates the management of state transitions by storing data states. There are various state management libraries available for Angular, such as NGRX, NGXS, and Akita, each serving different purposes and use cases. Choosing the right one for your application is essential before implementation. Here are some benefits of using state management:
- Facilitates sharing of data between different components.
- Provides centralized control for state transitions.
- Enhances code cleanliness and readability.
- Simplifies debugging processes in case of errors.
- Offers developer tools for tracing and debugging state management operations.
These benefits underscore the importance of adopting effective state management practices to improve application performance and maintainability.
16. Use CDK Virtual Scroll
Loading a large number of elements can significantly slow down browser performance. However, leveraging CDK virtual scroll support provides an efficient solution for displaying extensive lists of elements. Virtual scrolling optimizes rendering by dynamically adjusting the height of the container element to match the total number of items and rendering only those that are currently in view.
Template:
<!-- Example template using CDK virtual scroll -->
<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
<div *cdkVirtualFor="let item of items" class="item">
{{ item }}
</div>
</cdk-virtual-scroll-viewport>
Component:
// Example component implementing CDK virtual scroll
import { Component } from '@angular/core';
@Component({
selector: 'app-cdk-virtual-scroll',
templateUrl: './cdk-virtual-scroll.component.html',
styleUrls: ['./cdk-virtual-scroll.component.css']
})
export class CdkVirtualScrollComponent {
items = [];
constructor() {
// Generate a large array of items for demonstration
this.items = Array.from({ length: 100000 }).map((_, i) => `scroll list ${i}`);
}
}
This structure explains the concept and provides a practical example of implementing CDK virtual scroll in an Angular component.
17. Use environment variables
Angular provides built-in environment configurations to manage variables specific to each environment, such as development and production. Additional environments can be added, or existing ones can be extended with new variables.
Development Environment: environment.ts
// environment.ts - Development environment variables
export const environment = {
production: false,
apiEndpoint: 'http://dev.domain.com',
googleMapKey: 'dev-google-map-key',
sentry: 'dev-sentry-url'
};
Production Environment: environment.prod.ts
// environment.prod.ts - Production environment variables
export const environment = {
production: true,
apiEndpoint: 'https://prod.domain.com',
googleMapKey: 'prod-google-map-key',
sentry: 'prod-sentry-url'
};
During the build process, Angular automatically selects and applies the environment variables defined in the corresponding environment file (environment.ts for development and environment.prod.ts for production).
18. Use lint rules for Typescript and SCSS
tslint and stylelint are powerful tools equipped with various built-in options that enforce cleaner and more consistent code. They are widely supported across modern editors and can be customized with additional lint rules and configurations to meet specific project requirements. This ensures code consistency and enhances readability.
Some built-in options in tslint include: no-any, no-magic-numbers, no-debugger, no-console, and more.
Some built-in options in stylelint include: color-no-invalid-hex, selector-max-specificity, function-comma-space-after, declaration-colon-space-after, among others.
19. Always Document
Documenting code is crucial for project maintainability and developer onboarding. In Angular, it's best practice to document every variable and method extensively. For methods, use multi-line comments to describe their purpose and explain each parameter.
/**
* Converts a number to its string representation.
* @param bar The number to convert
* @returns The string version of the input number
*/
function foo(bar: number): string {
return bar.toString();
}
Tip: Use tools like the 'Document This' Visual Studio extension to automate the generation of detailed JSDoc comments for TypeScript and JavaScript files, ensuring consistent and informative documentation throughout your codebase.
By leveraging Angular's powerful features—such as two-way data binding, modular architecture, and efficient code management—developers can streamline their workflow and enhance the user experience. For businesses seeking expert support in implementing these best practices or developing high-quality software solutions, Ideas2IT offers cutting-edge software development solutions. Our seasoned professionals can help you achieve outstanding results, ensuring your Angular projects are both efficient and scalable.