별점 시스템에 ionic-rating-component를 이용중이었다.
처음 설치 시에 패키지 충돌이 발생했지만, --legacy-peer-deps
설정을 통해 무시하고 지나쳤다.
뭐 하나를 설치할 때마다 에러가 발생했지만, 역시 --legacy-peer-deps
옵션으로 넘어갔다.
빌드시에 또 다른 에러가 발생했지만, strict mode를 해제하면서 어물쩡 넘어갔다.
그렇게 쌓이고 쌓이다가 결국, ios 배포를 위한 빌드시에 곪은게 터지고 말았다.
기존에 가져가 쓰던 ionic-rating-component의 앵귤러 버전과, 현재 프로젝트의 앵귤러 버전이 맞지 않아 생기는 이슈이다.
이를 해결하려면 라이브러리를 버리고 직접 구현해야 하겠다고 판단했다.
기존에 우선 사용하던 속성들을 먼저 확인했다.
<ionic-rating-component
activeIcon="star"
defaultIcon="star"
halfStar="true"
activeColor="var(--secondary_blue)"
defaultColor="var(--middle_gray)"
readonly="true"
fontSize="1.25rem"
rating="{{ editedRate }}"
(ratingChanged)="onRatingChange($event)"
></ionic-rating-component>
요정도.. 얘네만 동일하게 속성 값을 받는 컴포넌트를 만들면 될 것 같다.
기존의 아이콘과, 별 색상 등은 미리 디자인된 소스를 사용할 것이므로 제외하고 컴포넌트를 작성했다.
조건부로 css class를 할당할 수 있는 ngClass
를 이용해, readonly가 true인 경우엔 비활성화 해주었다.
[ngClass]="readonly ? 'disabled' : null"
.disabled {
pointer-events: none;
cursor: default;
}
아래와 같이 설정해놓은 뒤,
<ion-img
#star
...
><ion-img/>
angular의 ViewChildren
을 이용해 해당 별점의 참조값을 읽어온다.
@ViewChildren('star') starRef: QueryList<HTMLElement>;
그 후 별점 5개의 css 속성의 width
값을 설정해 별들의 크기를 조정한다.
setStarSize() {
this.starRef.map((starEl) =>
starEl['el'].style.setProperty('width', this.starSize)
);
}
단순하게 Input
으로 입력받는다.
@Input() rating: number;
...
updateRate(idx: number) {
this.rating = idx + 1;
for (let i = 0; i <= idx; i++) {
this.stars[i] = 1;
}
for (let i = idx + 1; i < 5; i++) {
this.stars[i] = 0;
}
this.changeDetectorRef.detectChanges();
this.setStarSize();
}
이 때, 배열의 변경을 감지하기 위해 ChangeDetectorRef
를 사용하였고, 배열의 값이 변경되면서 style 값이 초기화 되는 현상이 발생해 size를 다시 설정해주었다.
별점의 변화를 부모 컴포넌트에게 알려주기 위해서 Output
과 EventEmitter
를 사용하였다.
@Output() ratingChanged = new EventEmitter<{ rate: number }>();
별점이 변화되면, 부모 컴포넌트에게 알려주기 위해 위의 ratingChanged에 rate 값을 담아 emit
해준다.
this.ratingChanged.emit({ rate: this.rating });
부모 컴포넌트에서, 사용하는 방법은 아래와 같다.
우선 html에서는, 아래와 같이 컴포넌트를 선언한다.
<app-rating-component
[readonly]="false"
starSize="1.25rem"
rating="3"
(ratingChanged)="onRatingChange($event)"
></app-rating-component>
onRatingChange
함수에서, event 안의 rate 값으로 변화된 별점을 전달해주고 있으므로 아래와 같이 사용하면 된다.
onRatingChange(event) {
console.log(event.rate);
}
기존과 동일하게 사용이 가능하다. 짠!
기존에 npm으로 가져와 쓰던 것과 달리 기본으로 설정된 스타일도 없어져서, css 만지기도 쉬워졌고 패키지 버전들도 내 패키지 버전과 동일하기 때문에 충돌도 안생긴다.
답답하던 무언가가 뚫리는 기분이다. 라이브러리를 덜 쓰자는 다짐을 오늘도 다시 하게 된다.
갈때도 구질구질한 친구야 잘가... 다신 보지말자
전체 코드가 담긴 깃허브는 여기를 누르면 이동할 수 있습니다!
기존에 별점을 입력받고, 별점에 따라 화면에 별점 개수를 렌더링 해주는 함수 로직은 다음과 같다.
public stars = [0, 0, 0, 0, 0];
updateRate(idx: number, initial?: boolean) {
...
for (let i = 0; i <= idx; i++) {
this.stars[i] = 1;
}
for (let i = idx + 1; i < 5; i++) {
this.stars[i] = 0;
}
...
}
쉬운 알고리즘이지만, 직관적이지 않고 for문 2개의 역할을 한번 더 생각해봐야 했다. 또한 나중에 0.5개와 같은 옵션이 생길 경우에는 for문 내부의 로직이 더 길어지며, 가독성이 더욱 좋아지지 않을 것이라고 예상됐다.
이를 고민하다가 오동동 backend 팀원들의 토론을 듣다가 map
에 대한 이야기를 하는 것을 듣고, 아이디어가 떠올랐다.
이를 통해 아래와 같이 리팩토링 했다.
public stars = [0, 0, 0, 0, 0];
setStars(rating: number) {
const starsMap = {
0: [0, 0, 0, 0, 0],
1: [1, 0, 0, 0, 0],
2: [1, 1, 0, 0, 0],
3: [1, 1, 1, 0, 0],
4: [1, 1, 1, 1, 0],
5: [1, 1, 1, 1, 1],
};
this.stars = starsMap[rating];
}
1번
에서 생성한 함수에 별점 정보를 전달만 해주는 것으로 변경하였다.updateRate(idx: number, initial?: boolean) {
...
this.setStars(this.rating);
...
}
기존보다 훨씬 더 가독성이 좋아졌고, 0.5점과 같은 정보가 추가되어도 starsMap
에 데이터만 추가해 주면 되므로, 확장하기에도 용이해 졌다!
글 잘쓰시네요~