😆

ReduxがAngularに公式ポーティングされた

2024/09/17に公開

2024/9/14に下記のXのポストを発見しました。
https://x.com/crutchcorn/status/1835270356069978226

なんと、Redux公式でAngularへのポーティングされました。
筆者はこれまでNgRxを使ってきましたが、React民でRedux Toolkitがお気に入りだったのでこれは朗報でした。

早速、使ってみます。

セットアップ

公式のGetting Startedの通り進めていきます。

Angularプロジェクトの作成

Angularのプロジェクトを作ります。
Angular 17.3 以上である必要があります。

> npx -p @angular/cli ng new angular-redux-example
Need to install the following packages:
@angular/cli@18.2.4
Ok to proceed? (y) y
? Which stylesheet format would you like to use? Sass (SCSS)     [ https://sass-lang.com/documentation/syntax#scss]
? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? no
CREATE angular-redux-example/README.md (1080 bytes)
~~~~~~
⠸ Installing packages (bun)...
✔ Packages installed successfully.
    Successfully initialized git.

angular reduxのインストール

下記コマンドでangular-reduxをインストールします。

> npx ng add @reduxjs/angular-redux@latest
✔ Determining Package Manager
  › Using package manager: bun
✔ Loading package information from registry
✔ Confirming installation
✔ Installing package
CREATE src/app/store/counter-slice.ts (648 bytes)
CREATE src/app/store/index.ts (291 bytes)
UPDATE src/app/app.config.ts (423 bytes)
UPDATE package.json (1149 bytes)
✔ Packages installed successfully.

下記パッケージがインストールされます。

package.json
{
    "@reduxjs/angular-redux": "^1.0.0",
    "@reduxjs/toolkit": "^2.2.7",
    "redux": "^5.0.0"
}

また、Reduxのcountサンプルを作ってくれます。

src/app/app.config.ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { provideRedux } from '@reduxjs/angular-redux';
import { store } from './store';

export const appConfig: ApplicationConfig = {
  providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideRedux({ store })]
};
src/app/store/counter-slice.ts
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counter-slice'

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
src/app/store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counter-slice'
import {injectDispatch, injectSelector} from "@reduxjs/angular-redux";

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

export const injectAppDispatch = injectDispatch.withTypes<AppDispatch>();
export const injectAppSelector = injectSelector.withTypes<RootState>();

サンプル実装

counterReducerのサンプルコードを使って、最小限のコードを書いてみます。

src/app/app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import {decrement, increment} from "./store/counter-slice";
import {injectAppDispatch, injectAppSelector, RootState} from "./store";

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: `
    <h1>
      <button (click)="increment()">+1</button>
      Count: {{$count()}}
      <button (click)="decrement()">-1</button>
    </h1>
  `,
  styleUrl: './app.component.scss'
})
export class AppComponent {
  title = 'angular-redux-example';
  $count = injectAppSelector((state) => {
    return state.counter.value
  });

  dispatch = injectAppDispatch();

  increment = () => {
    this.dispatch(increment());
  }

  asyncIncrement = async () => {
    await this.dispatch(async (dispatch, state) => {
      dispatch(increment());
      await Promise.resolve();
      dispatch(increment());
    });
    console.log('done');
  }

  decrement =  () => {
    this.dispatch(decrement());
  }
}

injectAppSelector()でstateは取り出せます。
戻り値はSignalになっています。

injectAppDispatch()でdispatch()できます。
非同期Actionも使えます。

上記コードはGithubのリポジトリにあります。

Discussion