Code Quality Help

Angular / TypeScript Standards

This section covers language-specific and framework-specific practices for Angular applications written in TypeScript. They complement the general naming and formatting rules from Sections 3 and 4.

6.1 TypeScript Strictness

  • Strict mode: Every TypeScript project must use "strict": true in tsconfig.json. This enables all strict type-checking options (noImplicitAny, strictNullChecks, etc.).

  • Explicit typing: Avoid any unless absolutely necessary (e.g., third-party libraries with incomplete types). When any is used, add a // TODO comment with the Jira ticket for adding proper types. The @typescript-eslint/no-explicit-any rule is set to error.

  • Type annotations: Always annotate function return types and public method signatures. Do not rely on inference for public API boundaries.

    // Good getUser(id: string): Observable<User> { ... } // Bad getUser(id: string) { ... }
  • Interfaces vs Classes: Prefer interfaces for DTOs, input/output models, and service contracts. Use classes only when logic is needed (components, services, guards).

6.2 Component Design

  • One component per file: Each Angular component must reside in its own file.

  • Component lifecycle: Implement lifecycle hooks only when needed. Use implements keyword for hooks to leverage IDE assistance:

    export class MyComponent implements OnInit, OnDestroy { ... }
  • Change detection strategy: Prefer ChangeDetectionStrategy.OnPush by default. Only use Default when justified and documented.

  • Inputs and Outputs:

    • Use @Input() and @Output() decorators, followed by the property.

    • Input types should be narrow and, where possible, primitive; avoid passing complex objects for simple bindings.

    • Use aliases only when the external API name differs from the internal name, and keep alias naming consistent across components.

    • Output event emitters must end with a verb in the past tense (e.g., dataLoaded, selectionChanged).

  • View encapsulation: Default (Emulated) is recommended. If None is used, document the reason and ensure styles do not leak unintentionally.

  • Template size: If a template exceeds ~200 lines, extract child components. This improves readability and testability.

6.3 Templates

  • Data binding syntax: Use [property]="..." for property binding and (event)="handler($event)" for event binding. Avoid on- prefix for events.

  • Avoid function calls in templates: Avoid calling methods directly in data-binding expressions, especially complex ones, as they impact change detection. Use pure pipes or pre-calculated properties instead.

  • Async pipe: Always use the async pipe in templates to subscribe to Observables. This guarantees automatic unsubscription on component destroy.

  • Attribute directives: Prefer functional directives for small DOM manipulations. Use structural directives (*ngIf, *ngFor) sparingly to avoid deep nesting.

  • Local references: Use #name for template reference variables; prefer descriptive names (e.g., #submitButton, #searchInput).

6.4 Observables and RxJS

  • Naming convention: Suffix Observable variables with $ (e.g., users$, searchResults$).

  • Subscription management:

    • In templates, use async pipe – no manual subscription needed.

    • In component classes, when you must subscribe manually, use takeUntil with a Subject<void> and call next/complete in ngOnDestroy. Do not leave open subscriptions.

      private destroy$ = new Subject<void>(); ngOnInit(): void { this.dataService.getData() .pipe(takeUntil(this.destroy$)) .subscribe(data => this.data = data); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); }
    • Alternatively, if only one value is expected, use first() or take(1).

  • Avoid nested subscribes: Use higher-order mapping operators (switchMap, mergeMap, concatMap, exhaustMap) instead of subscribing inside another subscription.

  • Error handling: Always include error handling in observable chains, either with catchError in the pipe, or via error callback in subscribe. Unhandled errors can crash the application.

  • Hot vs Cold observables: Be aware of the difference and document when an observable is shared (e.g., using shareReplay) to avoid side effects.

6.5 Services and Dependency Injection

  • Provided in root: Use @Injectable({ providedIn: 'root' }) for global singleton services. If a service is scoped to a specific module or component, specify the provider at that level.

  • No logic in constructor: The constructor of a service or component must only inject dependencies. Do not perform side effects, HTTP calls, or complex initialization; use ngOnInit or factory methods for that.

  • Avoid service locator: Do not inject Injector or ReflectiveInjector to manually retrieve services. Use proper DI.

6.6 Forms

  • Reactive forms over template-driven: Use reactive forms (FormBuilder, FormGroup, FormControl) for all but the simplest static forms. They provide better type safety and testability.

  • Typed forms: Utilize Angular 14+ typed reactive forms. Define interfaces matching the form structure.

    interface UserForm { name: FormControl<string>; age: FormControl<number>; } form: FormGroup<UserForm> = this.fb.group({ name: ['', Validators.required], age: [0, [Validators.required, Validators.min(0)]], });
  • Validation logic: Keep synchronous validators in separate files for reusability. Async validators should avoid making repeated HTTP requests unnecessarily.

  • Form layout: Prefer formGroup and formControlName directives over manual binding to FormControl instances. This keeps templates clean.

6.7 Additional Best Practices

  • Pipes over methods: For data transformations in templates, use Angular pipes. They are optimized for change detection. If logic is needed in TS, consider pure pipes.

  • Modules vs Standalone: Prefer standalone components, directives, and pipes for new code. This reduces NgModule boilerplate and improves tree-shaking.

  • Lazy loading: Components that are not part of the initial view should be lazy loaded (via routes) to reduce bundle size.

  • Avoid direct DOM manipulation: Use Angular APIs (Renderer2, ViewChild) when DOM access is required. Do not use document.getElementById or elementRef.nativeElement manipulation directly unless strictly necessary.

  • Testing (covered in detail in Testing section): Every component and service must have at least a basic unit test with TestBed.

04 мая 2026