Lesson 10: Angular Best Practices

we will cover essential best practices for Angular development, ensuring your application is maintainable, performant, and scalable. Adhering to these practices will help you optimize your Angular projects for better performance, efficient organization, and easier testing.


1. Folder Structure and File Organization

A well-organized folder structure is key to maintaining and scaling your Angular applications. Here are some recommended practices for structuring your project:

1.1. Grouping by Features

Instead of organizing files by type (e.g., placing all components in one folder, services in another), organize by feature or module. This keeps related files together and makes it easier to maintain large codebases.

  • App Folder Structure:
Bash
/src/app
  ├── /core             # Core services and singleton components (e.g., AuthenticationService)
  ├── /shared           # Shared components, directives, and pipes
  ├── /feature-modules  # Feature-specific modules and components (e.g., Dashboard, User)
  ├── /models           # Interfaces and models
  ├── /services         # Shared services that are used across multiple components
1.2. Using Feature Modules

Break your app into feature modules. This promotes modularity and helps with performance through lazy loading.

Bash
ng generate module feature-name

  • Example: A User module might have its components, services, and models in its own directory (/src/app/user).
1.3. Separate Core and Shared Modules
  • Core Module: This module should contain singleton services (services that should have a single instance) such as authentication, guards, or HTTP interceptors.
  • Shared Module: This should contain reusable components, pipes, and directives that can be shared across multiple feature modules.

2. Performance Optimization Tips

Optimizing the performance of Angular applications is crucial, especially for large-scale projects. Below are some tips to keep your app fast and responsive:

2.1. Lazy Loading

Lazy loading allows you to load feature modules on demand, reducing the initial load time of the application.

  • How to Implement Lazy Loading: Define routes in the app-routing.module.ts that load modules lazily using loadChildren:
TypeScript
const routes: Routes = [
  {
    path: 'user',
    loadChildren: () => import('./user/user.module').then(m => m.UserModule)
  }
];

This ensures that the UserModule is only loaded when the user navigates to the “user” route, improving performance on the initial load.

2.2. Use OnPush Change Detection

The default change detection strategy in Angular checks for changes in all components on every data change. This can negatively impact performance in large applications. Using the OnPush strategy can help optimize change detection.

  • How to Use OnPush: In the component decorator, set the change detection strategy to OnPush
TypeScript
@Component({
  selector: 'app-my-component',
  templateUrl: './my-component.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})

With OnPush, Angular will only check for changes when an input property changes or when an event such as a user interaction occurs.

2.3. Optimize Template Rendering

Minimize the number of components or elements rendered in the DOM. Use ngIf and ngFor wisely to avoid rendering unnecessary elements.

  • Example: Use trackBy in ngFor to optimize rendering of lists:
HTML
<div *ngFor="let item of items; trackBy: trackById">{{ item.name }}</div>

This ensures that Angular only re-renders list items that have actually changed.

2.4. Avoid Using Functions in Templates

Avoid binding functions in templates as Angular will re-evaluate these functions on every change detection cycle, which can degrade performance.

  • Bad Practice:
HTML
<div>{{ calculate() }}</div>

  • Good Practice: Pre-calculate values in the component and bind to variables instead.

3. Unit Testing with Angular

Writing unit tests is a critical part of Angular development to ensure your components and services behave as expected. Angular provides testing tools out of the box, such as Karma (test runner) and Jasmine (testing framework).

3.1. Testing Components

When testing components, focus on testing the template, component logic, and interaction with child components.

  • Setting up a Component Test
TypeScript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyComponent } from './my-component.component';

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ MyComponent ]
    })
    .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should render title', () => {
    const compiled = fixture.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('My Title');
  });
});
3.2. Testing Services

Testing services typically involves testing methods and ensuring the correct HTTP calls are made.

  • Service Test Example:
TypeScript
import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

describe('UserService', () => {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [UserService]
    });
    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  it('should fetch users', () => {
    const mockUsers = [{ name: 'John' }, { name: 'Jane' }];
    service.getUsers().subscribe(users => {
      expect(users.length).toBe(2);
      expect(users).toEqual(mockUsers);
    });

    const req = httpMock.expectOne('api/users');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);
  });

  afterEach(() => {
    httpMock.verify();
  });
});
3.3. Testing Directives and Pipes

Test your directives and pipes to ensure that they work as expected in different use cases. Angular’s testing utilities provide ways to test directives in isolation and within components.


4. Conclusion

Following Angular best practices for folder structure, performance optimization, and unit testing is essential for building robust, maintainable, and scalable applications. By organizing your project logically, optimizing for performance, and ensuring reliable tests, you set your Angular app up for long-term success.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top