[230516 - TIL] storybook을 이용한 angular 컴포넌트 관리 with [(ngModule)]

Dongwoo Kim·2023년 5월 16일
0

TIL / WIL

목록 보기
105/113

1. 개요

지난 TIL에 이어서 storybook을 통해 angular로 구현한 컴포넌트를 관리하는 과정을 진행했다. 특이사항이 있다면 이번 컴포넌트는 @Input() 뿐만 아니라 @Output() 으로 상위 컴포넌트와 상호작용하는 경우도 있었기 때문에 이를 확인하는 방법과 ngModule로 컴포넌트변수를 html요소값과 양방향 바인딩하는데 있어서 생긴 이슈가 있었다.


2. 기존 Story 개선

기존에 작성한 컴포넌트들의 Story는 종류별로 선언해서 여러개의 Story를 골라 확인해봐야했다. 하지만 Story의 기본 기능으로 type이나 여러 변수값들을 control 할 수 있었기 때문에 부적절하다고 생각했다. 따라서 story 구조개선과 더불어 불필요한 Story들을 제거하고 대표적인 Story에서 변수값들을 control하며 컴포넌트를 확인할 수 있게 했다.

1) 개선 구조

기존에 컴포넌트 별로 stories 폴더에 stories.ts 파일을 작성했던 것을 일괄적으로 관리할 수 있도록 했다. 덕분에 라이브러리에 변경사항을 적용할 때 관리하기 편해졌다.

2) argTypes를 이용한 Story 관리

Story 객체에 argTypes 속성을 통해 control 값을 내가 원하는 옵션으로 선택할 수 있게 관리할 수 있었다. 이로써 여러 종류의 Story를 만들필요가 없어졌고 하나의 Story에서 다양한 종류를 골라서 확인할 수 있게 되었다.

// src\app\shared\common-templates\stories\common-icon.stories.ts

export const Default: Story = {
  args: {
    type: IconType.None,
    color: ColorType.gray500,
    width: 24,
    height: 24,
  },
  argTypes: {
    type: {
      control: 'radio',
      options: IconType,
    }
  }
};


2. @Output 상호작용 확인

EventEmitter 를 이용해 상위 컴포넌트로 이벤트를 전달하는 과정을 storybook에서 어떻게 확인할 수 있을지 고민이었는데 찾아보니 별다른 작업없이 바로 확인할 수 있었다. Action이라는 개념으로 이벤트를 확인할 수 있었는데, Story 객체에서 추가로 적용시킬 이벤트가 아니라 컴포넌트 단계에서 미리 정의된 이벤트들은 바로 확인할 수 있었다.


3. [(ngModule)]을 이용한 양방향 바인딩

사실 오늘 가장 속을 썩인 것은 이녀석이다. 지난 작업 때도 눈에 거슬렸던 것이 Text 컴포넌트에서 input태그와 [(ngModule)]로 양방향 바인딩을한 text의 control값을 변경하면 컴포넌트에 적용이 안되는 현상이 있었다. 그때는 input 이벤트 자체는 잘 실행이 되서 넘어갔었는데 오늘은 확실히 양방향 바인딩이 필요한 컴포넌트가 있어서 집고 넘어갔다.

    <input
        class="common-text-input"
       	...
        [placeholder]="placeholder"
        [maxLength]="maxLength"
        [readOnly]="readOnly"
        [(ngModel)]="text"
        (input)="text_changeEvent($event)"
        (focus)="text_focusEvent()"
        (focusout)="text_focusoutEvent()">

다음과 같이 [(ngModule)]로 양방향 바인딩이 된 input태그를 storybook에서 확인하면 다음과 같은 에러가 발생한다.

NG0303: Can't bind to 'ngModel' since it isn't a known property of 'input' (used in the 'CommonTextComponent' component template).
1. If 'input' is an Angular component and it has the 'ngModel' input, then verify that it is a part of an @NgModule where this component is declared.
2. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.

사실 이유는 간단했다. angular에서 ngModule를 사용하기 위해서는 당연히 해당 모듈에 FormsModule을 import 해야한다.

// src\app\shared\shared.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

...
import { CommonTextComponent } from './common-templates/common-text/common-text.component';

@NgModule({
  declarations: [
    ...
    CommonTextComponent,
	
  ],
  imports: [
    CommonModule,
    FormsModule,	// ngModule 사용을 위해 필요!
  ],
  exports: [
    ...
    CommonTextComponent,
  ]
})
export class SharedModule { }

하지만 이는 전부터 알고 있었고 해당 에러가 났을 때 FormsModule을 잘 임포트했는지부터 확인했다. 다음으로는 임포트한 FormsModule을 제대로 사용하고있는지도 확인해보았는데 해당 컴포넌트를 라이브러리에 포함시켜서 빌드한 후, 패키지로 배포하고, 다른 프로젝트에서 사용한 결과, 정상 동작하였다! 컴포넌트 정의에는 문제가 없었던 것이다! 그럼 무엇이 문제였을까?

바로 Story에 있었다. 어찌보면 당연한 이유인데 여기까지 찾아가기가 쉽지않았다. 지난 TIL에서 컴포넌트간의 의존성을 Story 객체에 알려줘야한다고 했었다. 마찬가지로 컴포넌트를 정의한 모듈과 관계없이 Story 객체에 해당 컴포넌트를 정의할 때는 필요한 모듈도 같이 import해줘야했다!

import { FormsModule } from '@angular/forms';
import { Meta, StoryObj, moduleMetadata } from '@storybook/angular';
?
import { ButtonType, InputType, CommonHeight, CommonState, 
    IconType } from '../../shared.format';
import { CommonTextComponent } from '../common-text/common-text.component';
import { CommonBtnComponent } from '../common-btn/common-btn.component';
import { CommonIconComponent } from '../common-icon/common-icon.component';

const meta: Meta<CommonTextComponent> = {
  title: 'Common-text',
  component: CommonTextComponent,
  decorators: [
    moduleMetadata({
      declarations: [CommonTextComponent, CommonBtnComponent, CommonIconComponent, ],
      imports: [FormsModule,]	// 이부분 필요!!
    }),
  ],
};

export default meta;
type Story = StoryObj<CommonTextComponent>;

이후 정상적으로 양방향 바인딩이 이뤄지는 것을 확인할 수 있었다.

profile
kimphysicsman

0개의 댓글