최근 작업했던 프로젝트에서 유저의 시간대 별 스케줄 데이터를 요일별로 다룰 필요가 있었다. 이때 10진수 값으로 들어오는 요일별 시간 데이터를 2차원 boolean 배열로 변환한 뒤, 실제 화면에 요일 별 몇 시부터 몇 시까지 가능한 스케줄의 범위를 문자열로 출력해줘야 하는 기능이 추가되었다. 이때, 10진수 값으로 구성된 스케줄 객체를 화면에 렌더링 시 요일별 시간대를 보여주도록 변환하는 로직을 Angular의 Custom Pipe를 사용하여 개발하였다. 따라서 유저가 요일별 스케줄을 업데이트하고 나면, 전역에서 관리되는 스케줄 객체가 업데이트되고, 이 값을 참조해서 화면에 바인딩하는 홈 화면에서는 변경을 감지해서 pipe를 실행시켜주어야만 했다. 그런데 최초 스케줄 업데이트 시에는 정상적으로 변경된 값이 반영되지만, 두 번째 업데이트부터는 객체 내부 값이 변경되어도 앵귤러의 Change Detection이 실행되지 않는 문제가 생겼다. 관련 코드는 다음과 같은 구조다.
// pipe 부분
import { Pipe, PipeTransform } from '@angular/core';
import { Helper } from '../helper';
@Pipe({
name: 'scheduleRange',
})
export class ScheduleRangePipe implements PipeTransform {
transform(schedule: ISchedule): string {
const scheduleTable = Helper.table2schedule(schedule);
const result =
Helper.getTimeRangeFromTable(scheduleTable);
return result;
}
}
// 사용처 Template
<div>{{ schedule | scheduleRange }}</div>
파이프의 Change Detection은 어떻게 동작하는가
템플릿에 데이터 바인딩 시 모든 DOM event(keystroke, 마우스 이동, 타이머, 서버 응답 등) 가 발생할 때마다 change detection 프로세스가 실행된다고 한다. 그러나 앵귤러 파이프를 모든 change detection마다 실행시키는 것은 (당연히) 앱의 성능 문제를 발생시킬 것이다. 그래서 앵귤러는 조금 더 빠른 change detection 알고리즘을 사용해서 파이프를 동작시킨다.
원시 타입과 참조 객체에 대한 Change Detection
Pipe 생성 시 기본값은 pure로 설정된다. Pure Pipe의 경우 앵귤러는 오직 인풋값의 순수 변경사항만을 감지하게 된다. 인풋값으로 String, Number, Boolean 등 자바스크립트의 원시 타입(primitive) 데이터가 변경될 경우 변경사항이 감지된다. 또는, Date, Array, Function, Object와 같은 참조 객체가 변경될 경우에도 파이프의 change detection이 돌아간다.
Pure pipe에는 순수함수만이 들어가야 한다. 동일한 인풋이 들어오면 동일한 아웃풋이 나오도록 설계되어야 할 것이다. 앵귤러에서 pure pipe의 변경 감지를 실행할 때 특이한 점이 하나 있는데, 바로 합성 객체(composite objects)의 변경사항을 무시한다는 것이다. 원시 타입 데이터의 변경사항을 체크하는 것은 객체 데이터의 변경사항을 체크하는 것보다 훨씬 빠르다. (속도를 위해 앵귤러는 객체 내부값의 변경사항까지 체크하는 것을 스킵하는 것 같다)
따라서 만약에 배열을 사용해서 pure pipe를 만든다면 의도대로 변경감지가 실행되지 않을 수 있다. 다음과 같은 템플릿이 있다고 해보자.
// heroes.component.html
<div *ngFor="let hero of (heroes | flyingHeroes)">
{{hero.name}}
</div>
만약 여기 들어가는 heros 배열에 새로운 아이템이 다음과 같이 추가되었더라도, change detector는 변경사항을 무시할 것이다. 따라서 파이프는 값을 업데이트해주지 않고 기존의 화면을 그대로 보여주게 된다.
// hero
this.heroes.push(hero);
앵귤러에서 얘를 업데이트해주지 않는 이유는 배열의 참조(reference)값이 바뀌지 않았기 때문이다. 배열이 동일하(다고 인식되)기 때문에 앵귤러는 화면을 업데이트해주지 않는다.
참조 객체의 변경사항을 감지시키는 방법
- 객체 참조 자체를 변경시키면 업데이트가 잘 될 것이다. 배열을 예로 들면, 아이템이 추가될 때마다 배열 자체를 replace시켜주면 객체의 참조값이 바뀌기 때문에 파이프도 얘를 새로운 배열 객체로 인식하게 된다.
- {…object}
- JSON.stringfy, JSON.parse
- 객체를 복사하는 다양한 방법에 대해서는 다음 게시물을 참고해보자.
- 파이프를 impure하게 만드는 방법도 있다. 다음과 같이 파이프 데코레이터의 pure 플래그를 false로 바꿔주면, 파이프가 impure하게 바뀐다. 이렇게 되면 앵귤러는 모든 change detection마다 파이프를 실행하게 된다. 주의해서 사용해야 하는 옵션이고, 여기에 연산이 많이 들어가게 되면 앱의 성능이 드라마틱하게 안좋아질 수 있다.
Reference
https://angular.io/guide/pipes#detecting-changes-with-data-binding-in-pipes
'Web > Angular' 카테고리의 다른 글
Angular 어플리케이션 리팩토링하기 (2) | 2022.06.26 |
---|