Lesson 22-Classic Project Source Code Analysis

Angular Official Example Project Source Code

Default Project Structure Generated by Angular CLI

Typical Project Structure:

my-angular-app/
├── e2e/                  # End-to-end tests
├── node_modules/         # Dependency packages
├── src/                  # Source code
   ├── app/              # Application code
      ├── app.component.*  # Root component
      ├── app.module.ts    # Root module
      └── ...            # Other components/services
   ├── assets/           # Static assets
   ├── environments/     # Environment configurations
   ├── styles.css        # Global styles
   └── index.html        # Main HTML file
├── angular.json          # Project configuration
├── package.json          # Dependency management
└── tsconfig.json         # TypeScript configuration

Key File Analysis:

  • angular.json: Defines build configurations, asset paths, style preprocessing, etc.
  • tsconfig.json: TypeScript compiler options
  • environment.*.ts: Environment-specific configurations (development/production)

Component and Module Organization and Design

Modular Design Example:

src/app/
├── core/               # Core module (singleton services)
   ├── services/       # Global services
   └── core.module.ts
├── shared/             # Shared module (reusable components)
   ├── components/     # Shared components
   └── shared.module.ts
├── features/           # Feature modules (by functionality)
   ├── user/           # User management module
   └── product/        # Product management module
└── app.module.ts       # Root module

Module Design Principles:

  1. CoreModule: Contains singleton services, imported once in the root module
  2. SharedModule: Contains reusable components/directives/pipes, importable by multiple modules
  3. FeatureModule: Divided by functionality, lazily loaded

Routing and Navigation Implementation

Routing Configuration Example:

// app-routing.module.ts
const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard', component: DashboardComponent },
  { 
    path: 'users', 
    loadChildren: () => import('./features/user/user.module').then(m => m.UserModule)
  },
  { path: '**', component: PageNotFoundComponent }
];

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

Route Guard Implementation:

// auth.guard.ts
@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean {
    if (this.authService.isLoggedIn()) {
      return true;
    }
    this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
    return false;
  }
}

Form Handling Implementation

Reactive Forms Example:

// user-form.component.ts
@Component({
  selector: 'app-user-form',
  templateUrl: './user-form.component.html'
})
export class UserFormComponent {
  userForm = this.fb.group({
    name: ['', [Validators.required, Validators.minLength(2)]],
    email: ['', [Validators.required, Validators.email]],
    age: [0, [Validators.min(18), Validators.max(99)]]
  });

  constructor(private fb: FormBuilder) {}

  onSubmit() {
    if (this.userForm.valid) {
      console.log('Form submitted:', this.userForm.value);
    }
  }
}

Template-Driven Forms:

<!-- login-form.component.html -->
<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)">
  <input name="username" ngModel required>
  <input name="password" type="password" ngModel required>
  <button type="submit" [disabled]="loginForm.invalid">Login</button>
</form>

HTTP Communication Implementation

HTTP Service Encapsulation:

// api.service.ts
@Injectable()
export class ApiService {
  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/api/users').pipe(
      catchError(this.handleError)
    );
  }

  private handleError(error: HttpErrorResponse) {
    // Error handling logic
    return throwError('Something went wrong');
  }
}

Interceptor Implementation:

// auth.interceptor.ts
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = localStorage.getItem('token');
    if (token) {
      const cloned = req.clone({
        headers: req.headers.set('Authorization', `Bearer ${token}`)
      });
      return next.handle(cloned);
    }
    return next.handle(req);
  }
}

NgRx State Management Project Source Code

Store, Actions, and Reducers Implementation

Action Definition:

// user.actions.ts
export const loadUsers = createAction('[User] Load Users');
export const loadUsersSuccess = createAction(
  '[User] Load Users Success',
  props<{ users: User[] }>()
);
export const loadUsersFailure = createAction(
  '[User] Load Users Failure',
  props<{ error: any }>()
);

Reducer Implementation:

// user.reducer.ts
export const userReducer = createReducer(
  initialState,
  on(loadUsers, state => ({ ...state, loading: true })),
  on(loadUsersSuccess, (state, { users }) => ({
    ...state,
    users,
    loading: false
  })),
  on(loadUsersFailure, (state, { error }) => ({
    ...state,
    error,
    loading: false
  }))
);

Store Configuration:

// app.module.ts
@NgModule({
  imports: [
    StoreModule.forRoot({ user: userReducer }),
    EffectsModule.forRoot([UserEffects])
  ]
})
export class AppModule {}

Effects Implementation and Async Handling

Effect Implementation:

// user.effects.ts
@Injectable()
export class UserEffects {
  loadUsers$ = createEffect(() =>
    this.actions$.pipe(
      ofType(loadUsers),
      switchMap(() =>
        this.userService.getUsers().pipe(
          map(users => loadUsersSuccess({ users })),
          catchError(error => of(loadUsersFailure({ error })))
        )
      )
    )
  );

  constructor(
    private actions$: Actions,
    private userService: UserService
  ) {}
}

Async Operation Handling:

  1. switchMap: Cancels previous incomplete requests
  2. mergeMap: Allows concurrent requests
  3. concatMap: Executes requests sequentially
  4. exhaustMap: Ignores new requests until the current one completes

Selector Optimization

Basic Selectors:

// user.selectors.ts
export const selectUserState = (state: AppState) => state.user;

export const selectAllUsers = createSelector(
  selectUserState,
  (state: UserState) => state.users
);

export const selectUserLoading = createSelector(
  selectUserState,
  (state: UserState) => state.loading
);

Advanced Selectors:

// Derived selector
export const selectActiveUsers = createSelector(
  selectAllUsers,
  (users) => users.filter(user => user.isActive)
);

// Parameterized selector
export const selectUserById = (userId: string) => createSelector(
  selectAllUsers,
  (users) => users.find(user => user.id === userId)
);

Selector Optimization Techniques:

  1. Memoization: Avoids redundant computations
  2. Composed Selectors: Reuses existing selectors
  3. Parameterized Selectors: Dynamically fetches data

State Management Debugging and Testing

Redux DevTools Integration:

// store.module.ts
@NgModule({
  imports: [
    StoreModule.forRoot(reducers, {
      metaReducers,
      runtimeChecks: {
        strictStateImmutability: true,
        strictActionImmutability: true
      }
    }),
    StoreDevtoolsModule.instrument({
      maxAge: 25,
      logOnly: environment.production
    })
  ]
})
export class AppStoreModule {}

Unit Test Example:

// user.reducer.spec.ts
describe('User Reducer', () => {
  it('should handle loadUsersSuccess', () => {
    const initialState: UserState = { users: [], loading: false };
    const action = loadUsersSuccess({ users: [{ id: 1, name: 'Test' }] });
    const state = userReducer(initialState, action);

    expect(state.users).toEqual([{ id: 1, name: 'Test' }]);
    expect(state.loading).toBeFalse();
  });
});

State Management Performance Optimization

Performance Optimization Strategies:

  1. OnPush Change Detection: Reduces change detection scope
  2. TrackBy Function: Optimizes ngFor rendering
  3. Lazy Loading Modules: Reduces initial bundle size
  4. Selector Memoization: Avoids redundant computations

State Normalization:

// Normalized state structure
{
  users: {
    byId: {
      '1': { id: 1, name: 'User 1' },
      '2': { id: 2, name: 'User 2' }
    },
    allIds: ['1', '2']
  }
}

Angular Material Component Library Source Code

Component Design and Implementation

Dialog Component Architecture:

MatDialog
├── MatDialogContainer   // Container component
├── MatDialogRef         // Reference object
├── MAT_DIALOG_DATA      // Injection token
└── dialog-config        // Configuration options

Table Component Implementation Key Points:

  1. Virtual Scrolling: cdk-virtual-scroll-viewport
  2. Sorting: MatSort directive
  3. Pagination: MatPaginator component
  4. Filtering: MatTableDataSource filtering methods

Theme and Style Customization

Theme System Implementation:

// Custom theme
@use '@angular/material' as mat;

$primary: mat.define-palette(mat.$indigo-palette);
$accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$warn: mat.define-palette(mat.$red-palette);

$theme: mat.define-light-theme((
  color: (
    primary: $primary,
    accent: $accent,
    warn: $warn
  )
));

@include mat.all-component-themes($theme);

Style Override Strategies:

  1. ::ng-deep: Penetrates component style encapsulation (deprecated)
  2. ViewEncapsulation.None: Disables style encapsulation
  3. Global Style File: Overrides in styles.css

Component Accessibility and Internationalization

Accessibility Implementation:

  1. ARIA Attributes: Automatically adds ARIA roles and attributes
  2. Keyboard Navigation: Supports keyboard operations
  3. Focus Management: Ensures correct focus order

Internationalization (i18n) Support:

// Using Angular built-in i18n
@Component({
  template: `
    <p i18n="@@welcomeMessage">Welcome to our app</p>
  `
})
export class AppComponent {}

// Using TranslateService (ngx-translate)
constructor(private translate: TranslateService) {
  translate.use('en');
}

Component Performance Optimization

Virtual Scrolling Optimization:

<cdk-virtual-scroll-viewport itemSize="50" class="list-container">
  <div *cdkVirtualFor="let item of items" class="list-item">
    {{ item.name }}
  </div>
</cdk-virtual-scroll-viewport>

Change Detection Optimization:

  1. OnPush Strategy: Reduces detection frequency
  2. Immutable Data: Uses immutable data structures
  3. TrackBy Function: Optimizes list rendering

Component Testing and Debugging

Component Test Example:

// dialog.component.spec.ts
describe('DialogComponent', () => {
  let dialogRef: MatDialogRef<DialogComponent>;
  let component: DialogComponent;
  let fixture: ComponentFixture<DialogComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [MatDialogModule],
      declarations: [DialogComponent]
    }).compileComponents();
  });

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

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

Debugging Techniques:

  1. Augury Tool: Angular application debugging tool
  2. NgProbeToken: Custom debugging extensions
  3. Component Tree Inspection: Using browser developer tools

By deeply analyzing the source code of these classic projects, developers can master Angular best practices and design patterns, applying these advanced techniques and architectural concepts to their own projects.

Share your love