Skip to content
angular-module-design logo

Angular Module Design

angular-module-design

Design Angular modules using feature modules, lazy loading, and dependency injection. Use when organizing large Angular applications with proper separation of concerns.

NeverSight/skills_feed0installs213stars

SKILL.md

Full skill instructions

Angular Module Design

Overview

Architect scalable Angular applications using feature modules, lazy loading, services, and RxJS for reactive programming patterns.

When to Use

  • Large Angular applications
  • Feature-based organization
  • Lazy loading optimization
  • Dependency injection patterns
  • Reactive state management

Implementation Examples

1. Feature Module Structure

// users.module.ts
import { NgModule } from '@angular/​core';
import { CommonModule } from '@angular/​common';
import { ReactiveFormsModule } from '@angular/​forms';
import { UsersRoutingModule } from './​users-routing.module';
import { UsersListComponent } from './​components/​users-list/​users-list.component';
import { UserDetailComponent } from './​components/​user-detail/​user-detail.component';
import { UsersService } from './​services/​users.service';

@NgModule({
  declarations: [UsersListComponent, UserDetailComponent],
  imports: [CommonModule, ReactiveFormsModule, UsersRoutingModule],
  providers: [UsersService]
})
export class UsersModule {}

2. Lazy Loading Routes

// app-routing.module.ts
import { NgModule } from '@angular/​core';
import { RouterModule, Routes } from '@angular/​router';
import { DashboardComponent } from './​components/​dashboard/​dashboard.component';

const routes: Routes = [
  { path: '', component: DashboardComponent },
  {
    path: 'users',
    loadChildren: () => import('./​features/​users/​users.module')
      .then(m => m.UsersModule)
  },
  {
    path: 'products',
    loadChildren: () => import('./​features/​products/​products.module')
      .then(m => m.ProductsModule)
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

3. Service with RxJS

// users.service.ts
import { Injectable } from '@angular/​core';
import { HttpClient } from '@angular/​common/​http';
import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { map, catchError, tap } from 'rxjs/​operators';

interface User {
  id: number;
  name: string;
  email: string;
}

@Injectable({ providedIn: 'root' })
export class UsersService {
  private usersSubject = new BehaviorSubject<User[]>([]);
  public users$ = this.usersSubject.asObservable();

  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/​api/​users').pipe(
      tap(users => this.usersSubject.next(users)),
      catchError(error => {
        console.error('Error fetching users:', error);
        return throwError(() => new Error('Failed to load users'));
      })
    );
  }

  getUserById(id: number): Observable<User> {
    return this.http.get<User>(`/​api/​users/​${id}`);
  }

  createUser(user: Omit<User, 'id'>): Observable<User> {
    return this.http.post<User>('/​api/​users', user).pipe(
      tap(newUser => {
        const currentUsers = this.usersSubject.value;
        this.usersSubject.next([...currentUsers, newUser]);
      })
    );
  }

  updateUser(id: number, user: User): Observable<User> {
    return this.http.put<User>(`/​api/​users/​${id}`, user).pipe(
      tap(updatedUser => {
        const currentUsers = this.usersSubject.value;
        const index = currentUsers.findIndex(u => u.id === id);
        if (index !== -1) {
          currentUsers[index] = updatedUser;
          this.usersSubject.next([...currentUsers]);
        }
      })
    );
  }

  deleteUser(id: number): Observable<void> {
    return this.http.delete<void>(`/​api/​users/​${id}`).pipe(
      tap(() => {
        const currentUsers = this.usersSubject.value;
        this.usersSubject.next(currentUsers.filter(u => u.id !== id));
      })
    );
  }
}

4. Smart and Presentational Components

// users-list.component.ts (Smart)
import { Component, OnInit } from '@angular/​core';
import { Observable } from 'rxjs';
import { UsersService } from '../​../​services/​users.service';

interface User {
  id: number;
  name: string;
  email: string;
}

@Component({
  selector: 'app-users-list',
  template: `
    <div>
      <h2>Users</​h2>
      <button (click)="addUser()">Add User</​button>
      <app-user-item
        *ngFor="let user of users$ | async"
        [user]="user"
        (delete)="deleteUser($event)"
      ></​app-user-item>
    </​div>
  `
})
export class UsersListComponent implements OnInit {
  users$: Observable<User[]>;

  constructor(private usersService: UsersService) {
    this.users$ = this.usersService.users$;
  }

  ngOnInit(): void {
    this.usersService.getUsers().subscribe();
  }

  addUser(): void {
    // Navigation or modal logic
  }

  deleteUser(id: number): void {
    this.usersService.deleteUser(id).subscribe();
  }
}

// user-item.component.ts (Presentational)
import { Component, Input, Output, EventEmitter } from '@angular/​core';

interface User {
  id: number;
  name: string;
  email: string;
}

@Component({
  selector: 'app-user-item',
  template: `
    <div class="user-item">
      <h3>{{ user.name }}</​h3>
      <p>{{ user.email }}</​p>
      <button (click)="onDelete()">Delete</​button>
    </​div>
  `,
  styleUrls: ['./​user-item.component.css']
})
export class UserItemComponent {
  @Input() user!: User;
  @Output() delete = new EventEmitter<number>();

  onDelete(): void {
    this.delete.emit(this.user.id);
  }
}

5. Dependency Injection and Providers

// config.service.ts
import { Injectable } from '@angular/​core';

interface AppConfig {
  apiUrl: string;
  environment: string;
}

@Injectable({ providedIn: 'root' })
export class ConfigService {
  private config: AppConfig = {
    apiUrl: 'https://api.example.com',
    environment: 'production'
  };

  get(key: keyof AppConfig): any {
    return this.config[key];
  }
}

// app.module.ts with providers
import { NgModule } from '@angular/​core';
import { BrowserModule } from '@angular/​platform-browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/​common/​http';
import { ConfigService } from './​services/​config.service';
import { AuthInterceptor } from './​interceptors/​auth.interceptor';

@NgModule({
  imports: [BrowserModule, HttpClientModule],
  providers: [
    ConfigService,
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
  ]
})
export class AppModule {}

Best Practices

  • Organize by feature modules
  • Use lazy loading for large features
  • Implement smart/​presentational component pattern
  • Use services for data and business logic
  • Leverage RxJS for reactive patterns
  • Use dependency injection for loose coupling
  • Implement HTTP interceptors for global handling
  • Use typed services and models

Resources