[NGXS] Selector

CheolHyeon Park·2021년 7월 20일
0

Angular

목록 보기
5/7

Select

global state에서 state의 일부분을 선택할 수 있는 function이다. 데이터를 읽고 검색하기 위해 select를 사용한다.

NGXS에서는 state를 select하는데 두가지 방법이 있다.

  • **Store 서비스에 있는 select 메소드 이용하기**
  • 데코레이터인 @Select 이용하기

Select Decorators

Select 데코레이터를 통해 데이터를 선택할 수 있다.

아래와 같은 몇가지 방법들이 존재한다.

import { Select } from '@ngxs/store';
import { ZooState, ZooStateModel } from './zoo.state';

@Component({ ... })
export class ZooComponent {

	// state class 이름에서 state의 이름 읽기
	@Select(ZooState) animals$: Observable<string[]>;

	// pandas memoized selector 이용하기. 오직 판다만 가져온다.
	@Select(ZooState.pandas) pandas$: Observable<string[]>;

	// select method와 같은 function도 받는다.
	@Select(state => state.zoo.animals) animals$: Observable<string[]>;

	// parameter로부터 state의 이름을 읽는다.
	@Select() zoo$: Observable<ZooStateModel>;

}

Store Select Function

import { Store } from '@ngxs/store';

@Component({ ... })
export class ZooComponent {
  animals$: Observable<string[]>;

  constructor(private store: Store) {
    this.animals$ = this.store.select(state => state.zoo.animals);
  }
}

store select function은 static하게 select를 선언할 수 없을 때 이용한다.

혹은, selectOnce와 같은 메소드는 현재 상태만을 체크하기를 원하는 곳인 route guard 같은곳에서 사용한다.

Snapshot Selects

selectSnapshot은 raw value를 사용할 수 있다. observable이 아닌 static value를 이용할 때 사용한다.

가장 좋은 예시는 interceptor이다.

@Injectable()
export class JWTInterceptor implements HttpInterceptor {
  constructor(private store: Store) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = this.store.selectSnapshot<string>((state: AppState) => state.auth.token);
    req = req.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });

    return next.handle(req);
  }
}

Memoized Selectors

자주 같은 selector를 여러번 사용할 것이다. 혹은 복잡한 selector들을 사용할 것이다.

이때 사용하는것이 @Selector 이다. @Selector 는 퍼포먼스를 위해 function을 memoize 한다.

import { Injectable } from '@angular/core';
import { State, Selector } from '@ngxs/store';

@State<string[]>({
  name: 'animals',
  defaults: []
})
@Injectable()
export class ZooState {
  @Selector()
  static pandas(state: string[]) {
    return state.filter(s => s.indexOf('panda') > -1);
  }
}

위의 selector 를 아래와 같이 컴포넌트에서 사용한다.

@Component({ ... })
export class AppComponent {
  @Select(ZooState.pandas) pandas$: Observable<string[]>;
}

Memoized Selectors with Arguments

Lazy Selectors

lazy Selector를 만들기 위해서는 function을 return 하면 된다. selector에 의해 return 된 function은 자동적으로 memoize 될것이고 function안에 있는 로직은 selector가 실행 될 때, lazy하게 실행 될것이다.

ex)

@State<string[]>({
  name: 'animals',
  defaults: []
})
@Injectable()
export class ZooState {
  @Selector()
  static pandas(state: string[]) {
    return (type: string) => {
      return state.filter(s => s.indexOf('panda') > -1).filter(s => s.indexOf(type) > -1);
    };
  }
}

위와 같이 function을 return한다. 그리고 컴포넌트에서

import { Store } from '@ngxs/store';
import { map } from 'rxjs/operators';

@Component({ ... })
export class ZooComponent {
  babyPandas$: Observable<string[]>;

  constructor(private store: Store) {
    this.babyPandas$ = this.store
      .select(ZooState.pandas)
      .pipe(map(filterFn => filterFn('baby')));
  }
}

[store.select](http://store.select) 에 pipe, map을 이용하여 evaluate한다.

Dynamic Selectors

dynamic selectorsms createSelector 함수를 이용하여 생성된다. 얼핏보면 일반적인 selector와 차이가 없지만, selector에게 인자를 전달할 수 있다.

@State<string[]>({
  name: 'animals',
  defaults: []
})
@Injectable()
export class ZooState {
  static pandas(type: string) {
    return createSelector([ZooState], (state: string[]) => {
      return state.filter(s => s.indexOf('panda') > -1).filter(s => s.indexOf(type) > -1);
    });
  }
}

@Select 를 통해서 파라미터로 전달가능하다

import { Store } from '@ngxs/store';
import { map } from 'rxjs/operators';

@Component({ ... })
export class ZooComponent {

  @Select(ZooState.pandas('baby'))
  babyPandas$: Observable<string[]>;

  @Select(ZooState.pandas('adult'))
  adultPandas$: Observable<string[]>;

}

위의 각각의 selector는 서로 분리된 memoization을 가지고 있다. 그래서 서로 전혀 영향을 미치지 않는다.

Join Selectors

selector를 정의할 때, 다른 selector를 join 시킬 수 있다.

@State<PreferencesStateModel>({ ... })
@Injectable()
export class PreferencesState { ... }

@State<string[]>({ ... })
@Injectable()
export class ZooState {

  @Selector([PreferencesState])
  static firstLocalPanda(state: string[], preferencesState: PreferencesStateModel) {
    return state.find(
      s => s.indexOf('panda') > -1 && s.indexOf(preferencesState.location)
    );
  }

  @Selector([ZooState.firstLocalPanda])
  static happyLocalPanda(state: string[], panda: string) {
    return 'happy ' + panda;
  }

}

firstLocalPanda selecltor를 만들 때, 두번째 parameter로 다른 state를 받는다.

Meta Selectors

기본적으로 selector는 state에 바인딩 되어있다. 때때로 서로 관련없는 state를 join할 때가 필요하다. meta selector는 n개의 selector들을 하나의 state stream으로 리턴할 수 있게 해주는 것이다.

export class CityService {
  @Selector([Zoo, ThemePark])
  static zooThemeParks(zoos, themeParks) {
    return [...zoos, ...themeParks];
  }
}

The Order of interacting Selectors

Inheriting Selectors

유사한 구조의 state들이 있을 때, 상속할 수 있는 base class로부터 shared selector를 추출할 수 있다. 만약 여러개의 state들의 엔티티를 가지고 있다면, dynamic selector(createSelector)를 통해 상속할 수 있는 형태를 만들 수 있다.

export class EntitiesState {
  static entities<T>() {
    return createSelector([this], (state: { entities: T[] }) => {
      return state.entities;
    });
  }

  //...
}

Entities클래스를 상속한다.

export interface UsersStateModel {
  entities: User[];
}

@State<UsersStateModel>({
  name: 'users',
  defaults: {
    entities: []
  }
})
@Injectable()
export class UsersState extends EntitiesState {
  //...
}

export interface ProductsStateModel {
  entities: Product[];
}

@State<ProductsStateModel>({
  name: 'products',
  defaults: {
    entities: []
  }
})
@Injectable()
export class ProductsState extends EntitiesState {
  //...
}

사용 할 때

@Component({ ... })
export class AppComponent {

  @Select(UsersState.entities<User>())
  users$: Observable<User[]>;

  @Select(ProductsState.entities<Product>())
  products$: Observable<Product[]>;

}

// OR

this.store.select(UsersState.entities<User>());

출처: 공식문서

profile
나무아래에 앉아, 코딩하는 개발자가 되고 싶은 박철현 블로그입니다.

0개의 댓글