앵귤러 RouteReuseStrategy로 컴포넌트 재사용하기

노요셉·2020년 10월 7일
1

요구사항: 리스트 -> 상세페이지 -> 리스트 와 같이 사용자가 움직일때 이전에 스크롤위치를 기억해달라.

위와 같은 요구사항에 대응하기 할때
여러가지 방법 (cookie에 위치를 기억한다든지, 앱 자체에서 스크롤 위치를 상태관리하고 리스트에 돌아왔을때 scrollTo를 통해 스크롤 위치를 바꿔준다든지 하는 방법) 이 있는데요.

그 중에서 Angular 기능을 최대한 사용하는 RouteReuseStrategy 기능을 이용하겠습니다.

리스트 -> 상세페이지 즉 컴포넌트 이동시 Router를 통해 이동하잖아요. 그리고 그럴때마다 컴포넌트를 생성합니다. RouteReuseStrategy를 이용하면 한번 생성한 컴포넌트를 살려두고 Router로 이동시 기존 컴포넌트를 다시 사용하는 거에요. 아주 효율적이고 직관적인 방법입니다.

이렇게 하기 위해서 해야할 것들은
최상단 Router 모듈에 RouteReuseStrategy 인터페이스를 구현한 Strategy를 넣어요. 아래와 같이 생겼고요. 이 Strategy를 모듈의 service로 붙입니다.

export class CustomRouteReuseStrategy extends RouteReuseStrategy {
  private cache = new Map<string, DetachedRouteHandle>();

  shouldDetach(route: ActivatedRouteSnapshot) {
    if (getPath(route).startsWith('list')) {
      return true;
    }

    return false;
  }

  store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle) {
    this.cache.set(getPath(route), detachedTree);
  }

  shouldAttach(route: ActivatedRouteSnapshot) {
    const path = getPath(route);

    if (path.startsWith('list') && this.cache.has(path)) {
      return true;
    }

    return false;
  }

  retrieve(route: ActivatedRouteSnapshot) {
    return this.cache.get(getPath(route));
  }

  shouldReuseRoute(
    future: ActivatedRouteSnapshot,
    curr: ActivatedRouteSnapshot
  ) {
    return future.routeConfig === curr.routeConfig;
  }
}

공식문서에도 RouteReuseStrategy에 대한 내용은 친절하지 않은데, 메서드에 대한 내용이 잘 나온 레퍼런스를 통해 구현할 수 있었습니다.
레퍼런스 : https://itnext.io/cache-components-with-angular-routereusestrategy-3e4c8b174d5f

리스트 - 상세에 대한 간단한 예제 : Stackblitz

문제

이 기능을 회사 프로젝트에 적용할때에는 약간의 문제가 있었는데요.
Lazy Module로 구성하여 Route들을 분리해놨는데, LazyModule에 Service로 ReuseStrategy를 붙이니까. serivce가 intialized가 되질 않아요.

해결

그래서 최상단 Router 모듈에 RouteReuseStrategy 붙이게 되었고, 모든 라우터에 반응하지만
RouteReuseStrategy 내부에서 분기처리하여 원하는 컴포넌트만 재사용하는 방법으로 문제를 해결했습니다.

메서드 설명

출처: https://itnext.io/cache-components-with-angular-routereusestrategy-3e4c8b174d5f

RouteReuseStrategy Angular의 Route, component lifecycle의 행동을 제어하게됩니다.

컴포넌트간 navigate를 통해 route를 이동하게 되면, Angular는 이전 컴포넌트를 제거하고, 새로운 컴포넌트를 생성합니다. 컴포넌트에서 http requests같은 I/O관련 작업이 있다면 이건 효율적이지 않을 것 같습니다.

그래서 어떻게 컴포넌트를 부시고 살리지 않고 캐싱을 할 수 있느냐?

RouteReuseStrategy inteface는 다섯개 메서드를 갖습니다.

  • shouldReuseRoute이 메서드는 routes간 navigate할때마다 동작합니다.
    매개변수 설명
    future: source route
    curr: target route

    이 메서드가 ture를 리턴하면 route 안합니다. 변화가 없겠죠.
    false를 리턴하면 원래대로 route가 이동하고 컴포넌트 죽이고 살리고 angular lifecycle대로 동작합니다.

shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
  // default action 
  return future.routeConfig === curr.routeConfig;
}
  • shouldAttach는 route가 열리?자마자 바로 호출됩니다. 컴포넌트가 load되면 이 메서드가 호출됩니다.
    true를 반환하면 retrieve 메서드가 호출됩니다.
    그렇지 않으면 컴포넌트가 처음부터 생성됩니다.
shouldAttach(route: ActivatedRouteSnapshot): boolean;
  • retrieve는 shouldAttach가 true를 반환하면 호출됩니다.
    target route가 파라미터로 전달되고, 저장된 routeHandle?를 반환합니다.
    null을 반환하면 아무런 일이 없고, RouteHandle을 수동으로 저장하려고 할때 이 메서드를 씁니다.
    이 메서드는 framework에서 자동으로 관리되지 않아요.
    개발자에게 책임 위임합니다.
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null;
  • shouldDetach는 현재 route를 나갈때 호출됩니다.
    true를 반환하면 store 메서드가 호출 됩니다.
shouldDetach(route: ActivatedRouteSnapshot): boolean;
  • store는 shouldDetach 메서드가 true를 리턴하면 호출됩니다.
    여기서 RouteHandle를 어떻게 저장할지 정합니다.
    여기서 저장한건 retrieve에서 사용됩니다.
    현재 route를 매개변수로 받습니다.
store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void;

if shouldAttach return true -> retrieve called

if shouldDetach return true -> store called

레퍼런스 : https://javascript.plainenglish.io/angular-route-reuse-strategy-b5d40adce841

profile
서로 아는 것들을 공유해요~

0개의 댓글