diff --git a/angular.json b/angular.json index 72c8107..326b9d1 100644 --- a/angular.json +++ b/angular.json @@ -190,6 +190,75 @@ "builder": "@angular-devkit/build-angular:extract-i18n" } } + }, + "angular-redux-auto-dispatch-demo": { + "projectType": "application", + "schematics": {}, + "root": "projects/angular-redux-auto-dispatch-demo", + "sourceRoot": "projects/angular-redux-auto-dispatch-demo/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/angular-redux-auto-dispatch-demo", + "index": "projects/angular-redux-auto-dispatch-demo/src/index.html", + "browser": "projects/angular-redux-auto-dispatch-demo/src/main.ts", + "polyfills": [ + "zone.js" + ], + "tsConfig": "projects/angular-redux-auto-dispatch-demo/tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "projects/angular-redux-auto-dispatch-demo/public" + } + ], + "styles": [ + "projects/angular-redux-auto-dispatch-demo/src/styles.css" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "angular-redux-auto-dispatch-demo:build:production" + }, + "development": { + "buildTarget": "angular-redux-auto-dispatch-demo:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n" + } + } } } } diff --git a/projects/angular-redux-auto-dispatch-demo/public/favicon.ico b/projects/angular-redux-auto-dispatch-demo/public/favicon.ico new file mode 100644 index 0000000..57614f9 Binary files /dev/null and b/projects/angular-redux-auto-dispatch-demo/public/favicon.ico differ diff --git a/projects/angular-redux-auto-dispatch-demo/src/app/app.component.ts b/projects/angular-redux-auto-dispatch-demo/src/app/app.component.ts new file mode 100644 index 0000000..403be28 --- /dev/null +++ b/projects/angular-redux-auto-dispatch-demo/src/app/app.component.ts @@ -0,0 +1,24 @@ +import { Component, effect } from '@angular/core'; +import { injectSelector, injectDispatch } from '@reduxjs/angular-redux'; +import { decrement, increment } from './store/counter-slice'; +import { RootState } from './store'; + +@Component({ + selector: 'app-root', + standalone: true, + template: ` + + {{ count() }} + + `, +}) +export class AppComponent { + count = injectSelector((state: RootState) => state.counter.value); + dispatch = injectDispatch(); + increment = increment; + decrement = decrement; + + _auto_increment = effect(() => { + this.dispatch(increment()); + }); +} diff --git a/projects/angular-redux-auto-dispatch-demo/src/app/app.config.ts b/projects/angular-redux-auto-dispatch-demo/src/app/app.config.ts new file mode 100644 index 0000000..8f1092f --- /dev/null +++ b/projects/angular-redux-auto-dispatch-demo/src/app/app.config.ts @@ -0,0 +1,10 @@ +import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +import { provideRedux } from '@reduxjs/angular-redux'; +import { store } from './store'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRedux({ store }), + ], +}; diff --git a/projects/angular-redux-auto-dispatch-demo/src/app/store/counter-slice.ts b/projects/angular-redux-auto-dispatch-demo/src/app/store/counter-slice.ts new file mode 100644 index 0000000..6f65718 --- /dev/null +++ b/projects/angular-redux-auto-dispatch-demo/src/app/store/counter-slice.ts @@ -0,0 +1,35 @@ +import { createSlice } from '@reduxjs/toolkit'; +import type { PayloadAction } from '@reduxjs/toolkit'; + +export interface CounterState { + value: number; +} + +const initialState: CounterState = { + value: 0, +}; + +export const counterSlice = createSlice({ + name: 'counter', + initialState, + reducers: { + increment: (state) => { + // Redux Toolkit allows us to write "mutating" logic in reducers. It + // doesn't actually mutate the state because it uses the Immer library, + // which detects changes to a "draft state" and produces a brand new + // immutable state based off those changes + state.value += 1; + }, + decrement: (state) => { + state.value -= 1; + }, + incrementByAmount: (state, action: PayloadAction) => { + state.value += action.payload; + }, + }, +}); + +// Action creators are generated for each case reducer function +export const { increment, decrement, incrementByAmount } = counterSlice.actions; + +export default counterSlice.reducer; diff --git a/projects/angular-redux-auto-dispatch-demo/src/app/store/index.ts b/projects/angular-redux-auto-dispatch-demo/src/app/store/index.ts new file mode 100644 index 0000000..0e410ac --- /dev/null +++ b/projects/angular-redux-auto-dispatch-demo/src/app/store/index.ts @@ -0,0 +1,13 @@ +import { configureStore } from '@reduxjs/toolkit'; +import counterReducer from './counter-slice'; + +export const store = configureStore({ + reducer: { + counter: counterReducer, + }, +}); + +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType; +// Inferred type: {counter: CounterState} +export type AppDispatch = typeof store.dispatch; diff --git a/projects/angular-redux-auto-dispatch-demo/src/index.html b/projects/angular-redux-auto-dispatch-demo/src/index.html new file mode 100644 index 0000000..e0cbc09 --- /dev/null +++ b/projects/angular-redux-auto-dispatch-demo/src/index.html @@ -0,0 +1,13 @@ + + + + + AngularReduxDemo + + + + + + + + diff --git a/projects/angular-redux-auto-dispatch-demo/src/main.ts b/projects/angular-redux-auto-dispatch-demo/src/main.ts new file mode 100644 index 0000000..8882c45 --- /dev/null +++ b/projects/angular-redux-auto-dispatch-demo/src/main.ts @@ -0,0 +1,7 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { AppComponent } from './app/app.component'; + +bootstrapApplication(AppComponent, appConfig).catch((err) => + console.error(err), +); diff --git a/projects/angular-redux-auto-dispatch-demo/src/styles.css b/projects/angular-redux-auto-dispatch-demo/src/styles.css new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/projects/angular-redux-auto-dispatch-demo/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/projects/angular-redux-auto-dispatch-demo/tsconfig.app.json b/projects/angular-redux-auto-dispatch-demo/tsconfig.app.json new file mode 100644 index 0000000..e40712b --- /dev/null +++ b/projects/angular-redux-auto-dispatch-demo/tsconfig.app.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/projects/angular-redux/src/lib/inject-selector.ts b/projects/angular-redux/src/lib/inject-selector.ts index a5c3603..d84f4ae 100644 --- a/projects/angular-redux/src/lib/inject-selector.ts +++ b/projects/angular-redux/src/lib/inject-selector.ts @@ -91,13 +91,12 @@ export function createSelectorInjection(): InjectSelector { const { store, subscription } = reduxContext; - const selectedState = linkedSignal(() => selector(store.getState())); + const selectedState = linkedSignal(() => selector(store.getState()), { + equal: equalityFn, + }); const unsubscribe = subscription.addNestedSub(() => { const data = selector(store.getState()); - if (equalityFn(selectedState(), data)) { - return; - } selectedState.set(data); });