Akita+Single State Stream 모드의 원활한 Observable

개시하다


이 글은 lacolaco가 제창한Single State Stream 모드과 Anglar 상태 관리 라이브러리Akita를 조합해 Observable를 시원하게 만들었다는 기사다.
특히 아키타가 로딩 주변에서 다루는 최선의 실천 방법을 권장하지 않고 있다는 점에 주목하고 해설하겠다.
※ 아키타에 대해서는 자세히 설명하지 않습니다.

Akita의 selectLoading 과제


Akita의 loading은 매우 강력하다.서비스와 조회에서 구성 요소가 아닌 상점이 업데이트되고 있는지 판단할 수 있습니다.
class UserService {
  constructor(
    userStore: UserStore,
    userHttpService: UserHttpService,
  )

  fetchUsers(): Promise<void> {
    this.userStore.setLoading(true); // 読み込みを始めるためtrueに
    return this.userHttpService.fetchUsers()
      .pipe(
        tap(users => userStore.set(users)),
	tap(() => this.userStore.setLoading(false)), // 読み込み終了
	mapTo(undefined)
      );
  }
}
서비스 측면은 매우 간단할 것이다.이렇게 사용자를 찾았을 때 상점에서 읽는 중입니다.
그러나 구성 요소의 경우 아래와 같이 구독하는 Observable이 늘어나 지루해졌다.
class UserComponent implements OnDestroy {
  users: User[];
  isLoading: boolean;
  onDestroy$ = new EventEmitter();
  
  constructor(
    userQuery: UserQuery,
    userService: UserService,
  ) {
    this.userQuery
      .selectAll()
      .pipe(takeUntil(this.onDestroy$))
      .subscribe(users => {
        this.users = users;
      });
    
    this.userQuery
      .selectLoading()
      .pipe(takeUntil(this.onDestroy$))
      .subscribe(isLoading => {
        this.isLoading = isLoading
      });
    
    this.userService.fetchUsers();
  }
  
  ngOnDestroy(): void {
    this.onDestroy$.next();
  }
}
또한 async 파이프 모드로 쓰면 다음과 같다.
Observable boolean의 isLoading 달러는 async 파이프로 해결할 수 없습니다.
(=isLoading 자체가 가짜인 경우,ngIf는 가짜평가로 이전의 DOM에 도달할 수 없음)
@Component({
  templateUrl: '
    <ng-container *ngIf="isLoading$ | async as isLoading">
      <ng-container *ngIf="users$ | async as users">
      </ng-container>
    </ng-container>
  ',
})
class UserComponent implements OnDestroy {
  users$: Observable<User[]>;
  isLoading$: Observable<boolean>;
  
  constructor(
    userQuery: UserQuery,
    userService: UserService,
  ) {
    this.users$ = this.userQuery.selectAll();
    this.isLoading$ = this.userQuery.selectLoading();
    this.userService.fetchUsers();
  }
}

Single State Stream 모드를 통한 해결


위에서 말한 바와 같이 Akita loading을 사용하는 데는 두 가지 과제가 있다.
  • 구독 증가 Observable
  • loading은boolean이므로 async 파이프를 통해 해결할 수 없음
  • 이번에는 Single State Stream 모델로 이 과제를 해결할 것이다.
    자잘한 데서 참고 기사를 읽을 수 있었으면 좋겠어요.
    Single State Stream에서는 Observable 관리 상태를 구성 요소로 통합합니다.
    Observable 병합 시 사용combineLatest.
    type UserComponentState = {
      users: User[];
      isLoading: boolean
    };
    
    @Component({
      templateUrl: '
        <ng-container *ngIf="state$ | async state">
          <ng-container *ngIf="!state.isLoading">
            <ul>
    	  <li *ngFor="let user of state.users">
    	    {{user.name}}
    	  </li>
    	</ul>
          </ng-container>
          <ng-container *ngIf="state.isLoading">
            <span>読込中</span>
          </ng-container>
        </ng-container>
      ',
    })
    class UserComponent implements OnDestroy {
      state$: Observable<UserComponentState>;
      
      constructor(
        userQuery: UserQuery,
        userService: UserService,
      ) {
        this.state$ = combineLatest([
          this.userQuery.selectAll(),
          this.userQuery.selectLoading()
        ]).pipe(
          map(value => ({
            users: value[0],
    	isLoading: value[1] // 少し不思議な書き方ですが、型がついているので安心です。
          }));
        );
        this.userService.fetchUsers();
      }
    
    잘 나오네요.총괄해 보면 다음과 같은 효과가 있다고 볼 수 있다.
  • Observable가 하나가 되어 원활해졌습니다
  • async 파이프를 사용할 수 있고 구독 종료 처리를 건너뛸 수 있음
  • 이 모드는 3개, 4개의 Observable를 더 관리해도 하나에 변하지 않기 때문에 끊임없이 사용하고 싶습니다.
    읽어주셔서 감사합니다!

    좋은 웹페이지 즐겨찾기