[Angular 마스터하기] Day 16 - Signal 고급, Effect와 Computed 마스터
[Angular 마스터하기] Day 16 - Signal 고급, Effect와 Computed 마스터
이제와서 시작하는 Angular 마스터하기 - Day 16 “Signal의 고급 기능으로 더 강력한 앱을 만들어봅시다! ⚡”
오늘 배울 내용
- Effect 심화 활용
- asReadonly()로 상태 보호
- untracked() 패턴
- 실전: 다크모드, LocalStorage 연동
1. Effect - 부수 효과 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Component, signal, effect } from '@angular/core';
@Component({
selector: 'app-logger',
template: `
<button (click)="count.set(count() + 1)">클릭: {{ count() }}</button>
`
})
export class LoggerComponent {
count = signal(0);
constructor() {
// Effect: count가 변경될 때마다 실행
effect(() => {
console.log(`카운트: ${this.count()}`);
// LocalStorage에 저장
localStorage.setItem('count', this.count().toString());
});
}
}
Effect Cleanup
1
2
3
4
5
6
7
8
9
10
11
12
effect((onCleanup) => {
const value = this.searchTerm();
// API 호출
const controller = new AbortController();
fetch(`/api/search?q=${value}`, { signal: controller.signal });
// Cleanup: 다음 실행 전 정리
onCleanup(() => {
controller.abort();
});
});
2. asReadonly() - 상태 보호
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Injectable({ providedIn: 'root' })
export class ThemeService {
private _isDark = signal(false);
// Public: 읽기 전용
isDark = this._isDark.asReadonly();
toggle() {
this._isDark.update(v => !v);
this.applyTheme();
}
private applyTheme() {
document.body.classList.toggle('dark', this._isDark());
}
}
// 사용
@Component({
template: `
<button (click)="themeService.toggle()">
{{ themeService.isDark() ? '🌙' : '☀️' }}
</button>
`
})
export class AppComponent {
themeService = inject(ThemeService);
}
3. untracked() - 의존성 제외
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { untracked } from '@angular/core';
@Component({
template: `
<input [(ngModel)]="searchTerm" placeholder="검색">
<p>결과: {{ results().length }}개</p>
`
})
export class SearchComponent {
searchTerm = signal('');
results = signal<any[]>([]);
searchCount = signal(0);
constructor() {
effect(() => {
const term = this.searchTerm();
// untracked: searchCount 변경해도 effect 재실행 안 됨
untracked(() => {
this.searchCount.update(c => c + 1);
console.log(`검색 횟수: ${this.searchCount()}`);
});
// API 호출
this.search(term);
});
}
search(term: string) {
// 검색 로직
}
}
4. 실전 예제: 다크모드 + LocalStorage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Injectable({ providedIn: 'root' })
export class ThemeService {
private _isDark = signal(false);
isDark = this._isDark.asReadonly();
constructor() {
// 저장된 테마 불러오기
this.loadTheme();
// 테마 변경 시 자동 저장
effect(() => {
const dark = this._isDark();
localStorage.setItem('theme', dark ? 'dark' : 'light');
document.body.classList.toggle('dark', dark);
});
}
toggle() {
this._isDark.update(v => !v);
}
private loadTheme() {
const saved = localStorage.getItem('theme');
this._isDark.set(saved === 'dark');
}
}
📝 정리
Effect 사용 시기
| 사용처 | 예시 |
|---|---|
| LocalStorage | 자동 저장 |
| 로깅 | 상태 변화 추적 |
| DOM 조작 | 다크모드 적용 |
| 외부 API | 동기화 |
체크리스트
- Effect를 활용할 수 있나요?
- asReadonly()로 상태를 보호할 수 있나요?
- untracked()를 사용할 수 있나요?
📚 다음 학습
- 이전: Day 15: 에러 처리
- 다음: Day 17: 성능 최적화
“Signal 고급 기능으로 더 강력한 앱을 만드세요!” ⚡
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.