Angular + Redux

English Description Added due to increasing acquisition from USA! (26 Jul 2018)


Terminology / Preface

  • 옛날 Angular (JavaScript Based): angular.js
  • 요즘 Angular (TypeScript Based): Angular

2년 전 angular.js + Redux로 작업한 적이 있었다. 최근에 Ionic으로 작업하면서 Angular와 Redux를 같이 사용할 기회가 있었고 적당히 컨벤션을 정리해본다. 이전과 가장 달라진 점이라면 역시 사용하는 프로그래밍 언어다. Action Creator와 Dispatcher가 구분되어있는 느낌이었는데, TS의 decorator를 사용하여 Action을 생성함과 동시에 dispatch 할 수 있어서 훨씬 편리해졌다.

이 글에서는 Angular에서 Redux를 사용하여 Model, Action, Reducer, Saga를 정의해야 하는 방법과 그것을 Angular Template (html)과 Angular Component (code)에서 사용하는 방법을 설명한다.


  • Let’s say old JavaScript based angular as angular.js
  • and TypeScript based new angular as Angular

I Worked with angular.js + Redux tow years ago. While working with Ionic recently, I had the opportunity to use Angular and Redux together, and try to organize the convention in moderation. The most different thing between them is the programming language used. With JavaScript, Action Creator and Dispatcher was separated, but it is much more conveinient to create an action by using the decorator with TypeScript.

In this article, I demonstrate the method to define Model, Action, Reducer and Saga in Angular using Redux, and How to use them in Angular Template (html) and Angular Component (code).


Model

Type을 정의한다. 내가 주로 다룰 데이터의 정의, 데이터를 들고 있을 redux state의 정의, 그리고 Back-end API 호출에 쓰이는 redux action에 대한 payload, 내부 redux action에 대한 payload 등을 정의한다. 간단한 예제를 살펴보자. 내가 만들고 싶은 Model은 어떤 시각에 기분이 어땠는지를 기록하는 간단한 Log이다. 이 데이터를 MoodLog라 부르자.

interface MoodLog: 내가 다룰 데이터

interface MoodState: 이 데이터가 저장될 redux state

…그 밑으로는 payload들

Post, Put, Delete는 각각 단일 로그에 대한 생성, 수정, 삭제 API 호출이다.

Push: 단일 로그를 moodLogList에 append

Update: moodLogList 자체를 다른 배열로 갱신

Assign: moodLogList 내에 로그 하나를 갱신

Remove: moodLogList 내에 로그 하나 삭제


Let’s define Types. Definition of a redux state to hold the data, payload action to be used in the back-end API call, etc. Let’s look at a simple example. The model I want to build is a simple log That records how i felt at specific time. Let’s call this data MoodLog.

(Code above)

interface MoodLog: The example type

interface MoodState: Redux state that holds MoodLog

Post, Put, Delete: The creation, modification, and deletion API calls for a single log.

Push: append a single log into moodLogList

Update: Swap whole moodLogList with another array.

Assign: Update a single log inside the moodLogList

Remove: Remove a single log inside the moodLogList


Action

위에서 정의한 Payload들로 다음과 같이 Action을 정의한다.

flux-standard-action의 도움으로 손쉽게 Action의 타입을 만들 수 있다.

FluxStandardAction의 첫 번째 파라미터는 model 파일에서 정의한 payload type을 넣어주면 되고, 뒤에 들어가는 null은 payload가 아닌 부가적인 정보들을 넘긴다고 정의된 Meta 필드이다. 복잡한 예제가 아니므로 여기서는 패스.

뒤 이어 나오는 class MoodActions는 Angular Component에서 Dependency Injection으로 불러와 사용하게 될 바로 그 부분이다. 매우 비슷한 코드가 반복적으로 사용되고 있다. 주의할 점은 static readonly로 쓰는 변수들의 실제 문자열 값을 unique하게 설정해야한다. 이 값을 기준으로 redux reducer에서 switch 분기를 할 예정이므로!


Let’s define Actions with Payload that we defined so far.

(Code above)

With flux-standard-action, we can easily create types of actions.

(Code above 2)

The first parameter of FluxStandardAction a payload type defined in the model file, and the next one is Meta which is not a payload, some extra data. I will skip this one because it is not that complicated example.


Reducer

앞서 정의한 Action들을 이용해 redux의 알파이자 오메가, reducer를 구현한다. reducer의 두 번쨰 파라미터로 받아오는 action을 as 을 이용하여 명시적으로 캐스팅하고 있는 것, redux에서 state는 immutable하기 때문에 deepCopy를 사용하여 새로운 state를 생성한 것, 그리고 model에서 선언한 MoodState를 첫 번째 파라미터의 타입으로 지정한 것이 주요 포인트.


(Code above)

Let’s define reducer using Action we defined so far. Look carefully the followings. Casting explicitly the second parameter (= action) with as. Creating new state with deepCopy() since redux state should be immutable. Adding first parameter type as MoodState we defined previously.


Saga

비동기 액션 처리를 위해 개인적으로 무척 redux-saga를 선호하는 편이다. (이보다 더 직교성 높은 라이브러리는 본 적이 없다.)

여기서 눈 여겨볼 것은 우리가 앞선 MoodActions 클래스를 Angular의 DI를 활용할 수 없는 곳에서 DI를 하기 위해 ReflectiveInjector를 사용하는 부분, 그리고 redux-saga의 put effect를 사용하지 않는 부분이다. 그 외에 이해가 가지 않는 부분은 redux-saga의 문서를 읽어보는 것을 추천한다. redux-saga의 put 함수를 부르지 않는 이유는 우리의 MoodActions가 Action Creator임과 동시에 Action Dispatcher이기 때문이다. 더 자세한 설명은 @angular-redux/store의 dispatch decorator를 참고.


(Code above)

I personally prefer the redux-saga for handling asynchronous actions. (I’ve never seen a library more orthogonal than this.)

Important parts: Using ReflectiveInjector to use MoodActions where we cannot use Angular’s Dependency Injection feature. Not using put effect from redux-saga. I didn’t use it because our MoodAction is Action Creator and Action Dispatcher at the same time. You can find more details in dispatch decorator from @angular-redux/store.


Root Reducer

이제 우리가 만든 mood store를 Angular에 붙여줄 차례다. 일단 위와 같은 코드로 하나 이상의 reducer를 결합하여 rootReducer를 만든다. 위 코드에서는 Form Reducer, 그리고 내가 만든 custom reducer인 moodReducer를 합치고 있다. 이와 같은 구조로 또 다른 reducer를 짰다면 moodReducer 바로 밑에 추가해준다.


(Code above)

Now it is time to attach mood store to Angular. We can create a root reducer using code above. We combine form reducer, mood reducer (our custom reducer). You can add more custom reducers of course.


Store Module

이제 우리의 MoodActions 클래스를 Angular가 DI할 수 있게 provider에 추가하고, 우리의 saga를 등록하는 일을 한 뒤에…


Now we add our MoodActions as provider for Angular DI, and register our sagas…


App Module

위와 같이 Angular App의 Root Module에서 import 해주면 이제 redux를 사용할 수 있다!


We are ready to use redux! (using import like code above)


Usage

이제 열심히 만든 Redux Action을 사용하기만 하면 된다. App Module 작성 후에 열심히 Reference를 정리하다가 이 부분이 빠졌다는 것을 깨달았다 (…)

redux state로 부터 원하는 녀석을 고르는 부분이다. Observable 변수는 끝에 달러 표시를 붙이는 관례가 있다기에 따랐다. This is how we select variables from our redux state. It is a general convention for Observable variable names to be ended with $.

만약에, select와 정렬을 동시에 하고 싶다면, If you want to sort while you select,

select의 인자로 문자열 배열이 아닌 함수를 넘겨주면 된다. You can pass function instead of array of strings.

여기서 주의할 점은 listSelector가 arrow function이 아니어야 한다는 것, 그리고 export function이어야 한다는 것이다. 이걸 굳이 지키지 않아도 개발 환경에서는 정상적으로 잘 돌아가지만, production build에서는 꼬이는 것을 확인했다. 위와는 다른 코드로 Production build에서 오류가 발생한다면, 이를 의심해보자.

You should not use arrow function for listSelector, and it should be export function. If you don’t follow this one, The production builded app will be crushed.


위 코드는 우리가 정의한 MoodAction의 getLogs를 호출하는 예제 코드다. 이렇게 가져온 mLogList$를 html에서 사용하고 싶다면,

위와 같이 사용하면 된다. 중요한 부분은 첫 번째 줄의 mLogList$ | async이다.

If you want to use Observable variables (mLogList$) in your template, please use | async


코드 상에서 mLogList$를 사용하려면,

전자는 mLogList$가 업데이트될 때 마다 callback을 받는다.

후자는 이미 mLogList$에서 바로 값을 가져올 때 사용하면 좋다.

더 자세한 사용법은 rxjs를 참고.

If you want to use mLogList$ in your angular components, use the code above. You can find more details on document of rxjs.

Reference

Leave a Reply