5가지 일반적인 Angular 함정과 피하는 방법

발행: (2026년 1월 12일 오전 07:49 GMT+9)
6 min read
원문: Dev.to

Source: Dev.to

주의

Angular는 강력한 프레임워크이지만, 경험이 풍부한 개발자조차도 성능, 유지보수성 또는 가독성을 해치는 일반적인 함정에 빠질 수 있습니다. 이러한 작은 문제에 빠지면 익숙한 개발자 고통인 인포스터 신드롬을 은밀히 부추길 수 있습니다.

이 글에서는 실제 애플리케이션에서 자주 보는 다섯 가지 일반적인 Angular 함정을 살펴보고, 실용적인 예시와 모범 사례를 통해 이를 피하는 방법을 보여드리겠습니다.

1. 라이프사이클 훅 오용 (ngOnInit, ngOnChanges)

문제

많은 개발자들이 ngOnInit을 과도하게 사용하거나 ngOnChanges에 무거운 로직을 넣어 불필요한 재렌더링이나 디버깅 복잡성을 초래합니다.

해결책

라이프사이클 훅은 초기화 또는 입력 변화에 실제로 의존하는 변경 감지에만 집중하도록 합니다.

  • 비즈니스 로직을 컴포넌트가 아니라 서비스로 이동합니다.

잘못된 예시

ngOnInit() {
  this.loadData();
  this.processData(); // heavy computation here
}

개선된 예시

ngOnInit() {
  this.loadData();
}

private loadData() {
  this.dataService.getData().subscribe(data => {
    this.processData(data);
  });
}

왜 중요한가

관심사를 분리하면 컴포넌트가 가볍고 테스트 가능하며 유지 보수가 쉬워집니다.

2. 양방향 바인딩([(ngModel)]) 남용하기

문제점

양방향 바인딩은 편리하지만, 특히 큰 폼에서 [(ngModel)]을 남용하면 숨겨진 부작용과 복잡한 검증 로직이 발생할 수 있습니다.

해결책

  • Reactive Forms를 비트리비얼한 경우에 사용하세요.
  • ngModel은 매우 간단한 입력에만 사용하세요.

Reactive forms 예시

import { FormGroup, FormControl, Validators } from '@angular/forms';

form = new FormGroup({
  name: new FormControl('', Validators.required),
  email: new FormControl('', [
    Validators.required,
    Validators.email
  ])
});
<form [formGroup]="form" (ngSubmit)="onSubmit()">
  <label>
    Name:
    <input formControlName="name" />
  </label>

  <label>
    Email:
    <input formControlName="email" />
  </label>

  <button type="submit">Submit</button>
</form>

왜 중요한가

Reactive Forms를 사용하면 앱이 성장함에 따라 검증, 테스트, 디버깅이 훨씬 예측 가능해집니다.

3. Improper State Management (BehaviorSubject vs Signals)

The problem

많은 Angular 애플리케이션이 상태가 단순할 때조차도 모든 상태 관리에 BehaviorSubject에 크게 의존합니다. 이로 인해 불필요한 RxJS 보일러플레이트가 생기고, 실제 필요보다 복잡한 코드가 작성될 수 있습니다.

The solution

  • 간단한 반응형 상태에는 Signals(Angular 16+)를 사용합니다.
  • 공유되거나 복잡한 상태에는 BehaviorSubject 또는 NgRx를 사용합니다.
  • 상태 로직은 서비스에 두고, 컴포넌트에서는 관리하지 않습니다.

Signals example

import { signal } from '@angular/core';

export class CounterService {
  counter = signal(0);

  increment() {
    this.counter.update(value => value + 1);
  }
}

Why it matters

이미 NgRx를 깊게 사용하고 있다면 Signals가 이를 대체하지는 않지만, 로컬 혹은 서비스 수준의 상태 관리에는 매우 적합합니다. Signals는 복잡한 스트림이나 연산자가 필요 없는 상태에서 관찰 가능한 객체의 오버헤드 없이 깔끔하고 가독성 높은 반응성을 제공합니다.

4. *ngFor에서 trackBy를 잊어버리기

문제

리스트를 trackBy 함수 없이 렌더링하면, 리스트가 변경될 때마다 Angular가 DOM 요소를 파괴하고 다시 생성합니다—단 하나의 항목만 업데이트되었더라도 말이죠. 이는 불필요한 재렌더링을 초래하고, 특히 큰 리스트에서는 성능 문제를 일으킬 수 있습니다.

해결책

컬렉션을 순회할 때 항상 trackBy 함수를 제공하세요.

올바른 사용 예시

<div *ngFor="let item of items; trackBy: trackById">
  {{ item.name }}
</div>
trackById(index: number, item: { id: number }) {
  return item.id;
}

왜 중요한가

trackBy를 사용하면 Angular가 실제로 변경된 요소만 업데이트하도록 하여 렌더링 성능을 크게 향상시킵니다.

5. 비효율적인 변경 감지

문제

기본 변경 감지는 컴포넌트 트리 전체에 불필요한 검사를 발생시켜, 규모가 큰 애플리케이션에서 성능 저하를 초래할 수 있습니다.

해결책

  • ChangeDetectionStrategy.OnPush 사용.
  • 컴포넌트에 불변 데이터를 전달.
  • 불필요한 템플릿 바인딩을 피함.

예시

import { ChangeDetectionStrategy, Component, Input } from '@angular/core';

@Component({
  selector: 'app-user-card',
  templateUrl: './user-card.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserCardComponent {
  @Input() user!: User;
}

왜 중요한가

OnPush는 불필요한 변경 감지 사이클을 크게 줄여 주어 UI를 빠르고 반응성 있게 유지하는 데 도움이 됩니다.

Back to Blog

관련 글

더 보기 »