angular-redux2 / store

integrate your redux store into your Angular
Mozilla Public License 2.0
11 stars 4 forks source link

Add handy helpers #2

Closed HarelM closed 2 years ago

HarelM commented 3 years ago

The following code is what I use in my project to improve readability and maintenance:

import { Action } from "redux";
/**
 * This is a type definition to for the decorator
 */
type ReduxActionDescriptor = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;

/**
 * This is a simple helper to allow creating typed actions with payload 
 */
export abstract class BaseAction<TPayload> implements Action {
    constructor(public type: string, public payload: TPayload) { }
}

/**
 * This middleware can be used to send classes instead of objects by copy only the data
 */
export const classToActionMiddleware = (state: any) => (next: any) => (action: any) => next({ ...action });

/**
 * This descriptor allows converting a method in a class to a function in a reducer
 * Use it above a mothed you would like to convert to a reducer function like so:
 * @ReduxAction(SOME_ACTION_TYPE)
 * public someMethod(...) { ... }
 */
export const ReduxAction = (type: string): ReduxActionDescriptor => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
            descriptor.value.type = type;
        };

/**
 * This utility method takes a class and converts it to a reducer
 * It allows writing a class to allow easy reusability 
 */
export const createReducerFromClass = <State>(reducer: new () => any, initialState: State) => {
    const instance = Object.create(reducer.prototype);
    return (lastState: State = initialState, action: Action): State => {
        for (let fn in instance) {
            if (typeof instance[fn] === "function" && (instance[fn] as any).type === action.type) {
                return instance[fn].apply(instance, [lastState, action]);
            }
        }
        return lastState;

    };
};

I think it should be split to a few files and exported in index.ts file. I don't mind sending a pull request if you let me know where would you like each functionality.

If this can be integrated in this lib I can take it and use it in my project, otherwise I'll need to add this file to my project like it was before which I would like to avoid.

HarelM commented 3 years ago

@Garefild any updates on this?

Garefild commented 3 years ago

HI, I did not forget I just did not have the time recently. I will do it as soon as possible.

Garefild commented 2 years ago

I will add doc later. you can use it like you are use to.

@Action(Name) update(..)..

and createReducerFromClass(Reducer, INITIAL_STATE)

HarelM commented 2 years ago

Thanks for the update! I'll try to use it in the next weeks.

HarelM commented 2 years ago

I was not able to use the library unfortunately. :-( I did the following: npm install angular-redux2/store and in my code I did: import { NgReduxModule } from "@angular-redux2/store"; I'm getting in my IDE (vs code) the following error: Cannot find module '@angular-redux2/store' or its corresponding type declarations.ts(2307) Am I missing something obvious?

Garefild commented 2 years ago

I have now created a project to test this. And for me, it's working just fine, Tested in webstorm and vscode.

image

try to run: npm install angular-redux2/store --save Or delete ide temp data

HarelM commented 2 years ago

I also tried in stackblitz and failed... Not sure what's not working for me... :-(

Garefild commented 2 years ago

It took me a while but I think I know where it's coming from. I'll make the changes, and update that I'm done.

HarelM commented 2 years ago

Cool, thanks! Let me know when a new version is available.

Garefild commented 2 years ago

Done.

HarelM commented 2 years ago

I'm not sure if this is related to the angular version I'm using or something else, but I'm still getting a lot of errors... :-( The following is the branch I created in order to try and do the migration: https://github.com/IsraelHikingMap/Site/tree/angular-redux2-store The following is the build output: https://ci.appveyor.com/project/IsraelHikingHost/site/builds/41856544/job/fetpre6wvuty9lwj?fullLog=true Not sure what I'm doing wrong here... My gut feeling is that package.json is missing a "main", "entry", or "module" to let webpack know where to look for the relevant entry file, I might be wrong though... but if I compare the node_modules folder for ngx-maplibre-gl which I'm using and this npakcage's ode_module folder it seems like the js file are missing and package.json is not pointing to the js entry point. Again, I might be wrong...

Garefild commented 2 years ago

I think I know why this is happening but I'm not sure. It seems to be due to version differences in Angular (I wrote the new library on 13). Before I make adjustments to 12, I will download your project and try to build it so I can verify the source of the error.

I hope to resolve this within a few hours.

Garefild commented 2 years ago

Ok, this has been fixed. But you have full changes that you need to make to the code. I added quite a few options like syncing between Tabs so I changed the names a bit.

I will soon be writing docs and examples.


    @Action(ADD_SEGMENT)
    public addSegment(lastState: RouteData[], action: AddSegmentAction): RouteData[] {
        return this.doForRoute(lastState,
            action.payload.routeId,
            (route) =>
                ({
                    ...route,
                    segments: [...route.segments, action.payload.segmentData]
                } as RouteData));
    }
HarelM commented 2 years ago

Yes, yes, I'm aware of the changes I need to make (mainly ReduxAction -> Action) although as can be seen in my code Action is already a part of Redux code base so whenever I need to use Action from Redux and Action from this library I'll need to do Action as SomeThingElseToAvoidConflict which I think might not be ideal... BaseAction is not part of this lib yet, right?

Thanks again for all your hard work and fast response!

Garefild commented 2 years ago
  1. You can use - import { AnyAction } from 'redux';

redux "Action" is just a base interface:

export interface Action<T = any> {
  type: T
}

redux "AnyAction":

export interface AnyAction extends Action {
  // Allows any extra properties to be defined in an action.
  [extraProps: string]: any
}
  1. "BaseAction" - Can you explain to me what the benefits of "BaseAction" are? Creating a new instance on each dispatch is not really desirable in terms of memory. I will check what implementation I can think of that will be effective for exporting action templates
HarelM commented 2 years ago

I've updated my branch to use the latest version and the compilation seems to pass now. Thanks! BaseAction is just a simple way to make sure the type and payload is used. I've added it to another class in my project but you might be right in terms of memory etc, I need to check what should be the right way forward. The fact that I'm using classes for actions is also discouraged in Redux, so I'll need to see how to improve that as well. In previous version of typescript this was not easy, maybe now there's a better solution.

Thanks again!

Garefild commented 2 years ago

Any time, I will also be adding updates and improvements to the code in the coming days. And I will write comprehensive documentation.

I leave the thread open if you have any further issues/suggestions. Best regards

HarelM commented 2 years ago

Hmm, I see that I didn't make sure the tests are compiling... :-/ I can't find NgReduxTestingModule, what am I missing?

HarelM commented 2 years ago

It was renamed to MockNgReduxModule right?

Garefild commented 2 years ago

yes

Garefild commented 2 years ago

Hi, does it work? Do you have any feedback?

HarelM commented 2 years ago

I think it works ok, I need to fully test it as it is at the core of my app. I'll report back after I'll merge my branch to main assuming everything will work as expected. Thanks!!

HarelM commented 2 years ago

OK, so the last issue I was facing is the peer dependency of broadcast-channel. Is there a way to make this dependency optional? When I try to build without it, it fails at runtime... While I don't mind adding this dependency in general, I have a lot of dependencies that are getting stale over time in my project so I try to reduce the dependency tree for things that I'm not using, like this feature. Although I might use it in the future, I don't know... In any case, I think I managed to change my codebase enough so that everything is working as expected. Thanks for all the support!

Garefild commented 2 years ago

Yes, I will make it a selectable option. Or I will write it a little more efficiently and in the system itself. I will release an update in the coming days.

broadcast-channel:

Sync redux state across browser tabs

A lightweight middleware to sync your redux state across browser tabs. It will listen to the Broadcast Channel and dispatch the same actions dispatched in other tabs to keep the redux state in sync.

Before you use

Please take note that BroadcastChannel can only send data that is supported by the structured clone algorithm (Strings, Objects, Arrays, Blobs, ArrayBuffer, Map), so you need to make sure that the actions that you want to send to other tabs don't include any functions in the payload.

initState - copy state from order tabs.

class StoreModule {
    /**
     * Constructor
     */

    constructor(ngRedux: NgRedux<IAppState>, devTools: DevToolsExtension) {
        let enhancer: Array<any> = [];

        if (devTools.enhancer() && isDevMode())
            enhancer = [ devTools.enhancer() ];

        ngRedux.configureStore(<any> rootReducer, INITIAL_STATE, [
            reduxSyncMiddleware({ initState: true })
        ], enhancer);
    }
}
/**
 * Sync settings
 */

export interface ConfigSyncInterface {
    initState?: boolean;
    channelName?: string;
    blacklist?: Array<string>;
    whitelist?: Array<string>;
    predicate?: (action: AnyAction) => boolean;
    prepareState?: (action: AnyAction) => any;

    broadcastChannelOption?: {
        type?: BroadcastTypes,
        webWorkerSupport?: boolean
    };
}