Git Product home page Git Product logo

ngrx-store-freeze's Introduction

ngrx-store-freeze

npm version CircleCI

ngrx-store-freeze is a meta-reducer that prevents state from being mutated

  • Recursively freezes the current state, the dispatched action payload if provided and the new state.
  • When mutation occurs, an exception will be thrown.
  • Should be used only in development to ensure that the state remains immutable.

Installation

npm i --save-dev ngrx-store-freeze

OR

yarn add ngrx-store-freeze --dev

Setup

import { StoreModule, MetaReducer, ActionReducerMap } from '@ngrx/store';
import { storeFreeze } from 'ngrx-store-freeze';
import { environment } from '../environments/environment'; // Angular CLI environment

export interface State {
  // reducer interfaces
}

export const reducers: ActionReducerMap<State> = {
  // reducers
}

export const metaReducers: MetaReducer<State>[] = !environment.production ? [storeFreeze]: [];

@NgModule({
  imports: [
    StoreModule.forRoot(reducers, { metaReducers }),
  ]
})
export class AppModule {}

Additional Documentation

Credits

redux-freeze - Redux middleware that prevents state from being mutated
Attila Egyed - The original maintainer of this project

ngrx-store-freeze's People

Contributors

aegyed91 avatar awerlang avatar brandonroberts avatar kwintenp avatar maikdiepenbroek avatar mattijarvinen-ba avatar omelhoro avatar sfabriece avatar swseverance avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ngrx-store-freeze's Issues

Exceptions are not thrown when frozen objects are changed by an Angular template

I realise this is more than likely going to be an issue on the side of Object.freeze() rather than this module but I was hoping by posting here there might be something that the module could do to compensate.

Unfortunately I was caught in a bad way by the freezing of action payloads (I've opened a separate issue to make this clearer for others) and spent a long couple of hours debugging this.

I have an object with 2 properties which are bound to 2 separate ngModels.
I pass this object directly as the payload for an action and it then becomes frozen by this module,
so any further changes in the view no longer affect the frozen object in the controller and there are no exceptions thrown so no indication that the bug is caused by the object being frozen.

Using Object.assign to return a new object to use as the payload resolves the issue easily,
but knowing where the issue arose from in the first place is the tricky part.

Template:

<input [(ngModel)]="credentials.email">
<input [(ngModel)]="credentials.password">

Controller:

import { Component } from '@angular/core';
import { Store } from '@ngrx/store';

@Component({
  selector: 'login',
  template: require('./login.component.html')
})
export class LoginComponent {
  public credentials: any = {};

  constructor(
    private store: Store<any>
  ) { }

  /*
   * once this function is called,
   * updates to the view are no longer reflected in the controller
   */
  public submit = () => {
    this.store.dispatch({
      type: 'login',
      payload: this.credentials
    });
  }
}

  /*
   * using Object.assign() like this fixes the issue
   */
  public submit = () => {
    this.store.dispatch({
      type: 'login',
      payload: Object.assign({}, this.credentials)
    });
  }

TypeError: Cannot add property, object is not extensible

Related to #30

core.js:1673 ERROR TypeError: Cannot add property ad9228ca-0730-4cf2-816b-bddfc0468053, object is not extensible
    at reducer (http://localhost:4444/u/wound/main.js:5612:46)
    at combination (http://localhost:4444/u/wound/vendor.js:87606:35)
    at http://localhost:4444/u/wound/main.js:9619:16
    at freeze (http://localhost:4444/u/wound/vendor.js:137521:25)
    at computeNextEntry (http://localhost:4444/u/wound/vendor.js:86938:21)
    at recomputeStates (http://localhost:4444/u/wound/vendor.js:86969:15)
    at http://localhost:4444/u/wound/vendor.js:87198:30
    at ScanSubscriber.StoreDevtools.liftedAction$.pipe.Object.state [as accumulator] (http://localhost:4444/u/wound/vendor.js:87263:38)
    at ScanSubscriber.push.../../node_modules/rxjs/_esm5/internal/operators/scan.js.ScanSubscriber._tryNext (http://localhost:4444/u/wound/vendor.js:145775:27)
    at ScanSubscriber.push.../../node_modules/rxjs/_esm5/internal/operators/scan.js.ScanSubscriber._next (http://localhost:4444/u/wound/vendor.js:145768:25)

I have imolemented thhe custom serializer as suggested in #30. The error above is caused by another object but I didn't figure out yet why it's not extensible.

See MDN docs on "Can't define property, object not extensible"

Current Behaviour

When the above error occurs, the Angular app gets "stuck" and subsequent actions aren't properly dispatched to the store.

Thus I can click through component views in my app but the state gets broken.

Is it a good error to only print the error to the console? But not throw the error so that the app continues to work?!?

AOT compatible

For those wondering how to use this module within a project that compiles with AOT (or checks with AOT like angular-cli since beta 23) :

const reducers = {
  // pass your reducers here
  ui: UiReducer
};

const developmentReducer = compose(storeFreeze, combineReducers)(reducers);
const productionReducer = combineReducers(reducers);

export function getRootReducer(state: any, action: any) {
  if (environment.production) {
    return productionReducer(state, action);
  } else {
    return developmentReducer(state, action);
  }
}

I didn't find it by myself and credits go to @chrillewoodz

Issue with payload freeze with @ngrx/router-store actions

The recent changes to freeze the payload cause an issue when using the @ngrx/router-store library.
A number of the actions made available by that library allow for the NavigationExtras to be included in the action payload. One of those values can be the ActivatedRoute:

constructor(private _route: ActivatedRoute) {}
... 
someFunction(route: string): void {
    this._store.dispatch(go([route], {}, {relativeTo: this._route}));
}

When the go action is processed by the store reducers, the properties of the ActivatedRoute, including Observables etc, get frozen, which causes all sorts of errors later on.

Not sure if this is really an issue with this library, or bad practice to include these sorts of objects in the action payload. Just thought I'd bring it to your attention.

Emphasise that action payloads are also frozen

It is unclear when reading the readme that action payloads are frozen as well as the store itself.
I think this is something quite crucial and should be mentioned in the readme so as not to cause any confusion when using this module.

No errors in combination with ngModel

I am using the ngrx-store-freeze middleware in my application and noticed that no errors are thrown when the state is directly updated by a two-way data binding using ngModel. I already went down the road of debugging but it seems very weird and I cannot figured out why the state can be updated like this.

In my component I use the following selector:

export const getSelectedContact = createSelector(getContacts, getSelectedContactId, (contacts, id) => {
  let contact = contacts.find(contact => contact.id == id);

  return Object.assign({}, contact);
});

...

this.contact$ = store.select(getSelectedContact);

As you can see, I must copy the object because otherwise the view will directly update my application state.

In my template I have the following:

<md-input-container fxFlex>
  <input mdInput placeholder="Name" name="name" [(ngModel)]="contact.name">
</md-input-container>

I use ngIfAs to create a local template variable in combination with the async pipe like so *ngIf="contact$ | async as contact", in case you are wondering why I am using contact$ and contact.

Update to NgRx 4

It appears that ngrx-store-freeze is incompatible with NgRx 4.

deep freeze only part of state

Assuming a state:

const reducers = {
  router: routerReducer,
  dashboard: dashboardReducer,
  auth: authReducer,
  streams: streamsReducer
}
interface State {
  router: RouterState;
  dashboard: DashboardState;
  auth: AuthState;
  streams: StreamsState;
}

Each key is handled by its own reducer and they are combined in dev mode as shown below.

const developmentReducer: ActionReducer<State> = compose(storeFreeze, combineReducers)(reducers);

I was wondering if there is a way to use the store freeze, only for certain reducers.

For example:

const pureReducers = {
  router: routerReducer,
  dashboard: dashboardReducer,
  auth: authReducer
}
const mutableReducers = {
  streams: streamsReducer
}

interface State {
  router: RouterState;
  dashboard: DashboardState;
  auth: AuthState;
  streams: StreamsState;
}

// Something like this.
const protectedPureReducers = compose(storeFreeze, combineReducers)(pureReducers)

const developmentReducer: ActionReducer<State> = combineReducers({
  ...protectedPureReducers,
  ...mutableReducers
});

Is this even possible with the current API ?

I have to make a choice whether to use ngrx for my mutable state or use some other type of store for it.

The mutable state changes in an immutable way for UI related updates. For example, addition of a stream to the streams array (inside the reducer).

It is only mutated for low level details that are irrelevant with the UI. For example each stream element of the array can have certain properties mutated at any moment (outside of the reducer).

For those reasons, it does not seem a bad idea to me.
Opposing opinions on this are most welcome, since they might point me to problems that I cannot see at the moment.

Update Peer Dependency to NgRx 5.x

@brandonroberts Thanks for all the hard work on the NgRx ecosystem ๐Ÿฅ‡

Could you kindly update this repo/package to allow for NgRx 5.x peer dependency?
Not sure, if this is limited to a package.json chore, or more.

Thanks in advance!

Native crash on iOS devices

Hi there,

On connecting the reducer with ngrx-store-freeze to the store, I get the following error on all iOS devices:

forEach@[native code]
deepFreeze

forEach@[native code]
deepFreeze

forEach@[native code]
deepFreeze

forEach@[native code]
deepFreeze

forEach@[native code]
deepFreeze


rootReducer
computeNextEntry
recomputeStates


_tryNext
_next
next
_next
next
observe
dispatch
_execute
execute
flush
schedule
schedule
scheduleMessage
_next
next
notifyNext
_next
next
_next
next
notifyNext
_next
next
_next
next
next
next
next
__tryOrUnsub
next
_next
next
notifyNext
_next
next
notifyNext
_next
next
notifyNext
_next
next
_next
next
notifyNext
_next
next
subscribeToResult
_innerSub
_tryNext
_next
next
_next
next
onLoad
invokeTask
onInvokeTask
invokeTask
runTask
invokecomputeNextEntry @ (program):28
recomputeStates @ (program):55
(anonymous function) @ (program):237
(anonymous function) @ (program):77
_tryNext @ (program):98
_next @ (program):91
next @ (program):89
_next @ (program):113
next @ (program):89
observe @ (program):32
dispatch @ (program):50
_execute @ (program):111
execute @ (program):33
flush @ (program):36
schedule @ (program):27
schedule @ (program):43
scheduleMessage @ (program):53
_next @ (program):56
next @ (program):89
.....

Don't even know where to start with this one!

This library assumes that all Action's will have a 'payload' attribute

In earlier versions of ngrx, the Action interface had a payload attribute. This no longer seems to be the case:

https://github.com/ngrx/platform/blob/master/modules/store/src/models.ts#L1-L3

I am wondering if this library needs to be updated to account for this. For instance, in the source code it makes a check if (action.payload):

https://github.com/brandonroberts/ngrx-store-freeze/blob/master/src/index.ts#L19-L21

But what happens when the action uses a different name other than payload?

Relax peerDependency requirements

Currently this lib has a peerDependency on "@angular/core": "^2.4.0". This causes peerDependency warnings when using this library with the Ionic framework, which currently uses Angular 2.2.1.

Is there a possibility this peerDependency could be relaxed? Or, is there even a need for this peerDependency at all? AFAICT the only peerDependency this library seems to need is @ngrx/store.

Cannot freeze Array Buffer views with elements??

Throwing a TypeError Cannot freeze array buffer views with elements.
The effect seems pretty straight forward, so im not sure why im getting this. I am however using Electron and Mongo at the service level. Which is returning an observable of the payload as the http service would.

Effect

 @Effect() login$ = this.actions$
        .ofType(userActions.UserActionTypes.USER_LOGIN)
        .switchMap(action => this.userService.getUserLogin(action.payload))
        .map(payload => {
            let userDb = payload;
            return userDb === null ?
                new userActions.UserLoginFailureAction() : new userActions.UserLoginSuccessAction(userDb);
        })
        .catch((err) => {
            console.log(err);
            return Observable.of(new userActions.UserLoginFailureAction());
        });

Service

 getUserLogin(inputUser: User): Observable<User> {
    return this.mongoService.db.flatMap((db: Db) => {
      return Observable.create((observer: Observer<User>) => {
        db.collection('user_docs').findOne({
          $and: [
            { userName: inputUser.userName },
            { password: inputUser.password }
          ]
        }, ((err: MongoError, doc: User) => {
          err ? observer.error(err) : observer.next(doc);
          console.log(doc);
          observer.complete();
        }));
      });
    });
  }

Cannot add property _resolvedData, object is not extensible

Hi,

I have created reducer, custom serializer using router state. trying to connect StoreRouterConnectingModule through custom serializer in app module.
getting issue.


store-devtools.es5.js:270 Error: unimplemented
at unimplemented (forms.js:1161)
at AbstractControlDirective.get [as validator] (forms.js:1196)
at index.js:10
at Array.forEach ()
at deepFreeze (index.js:7)
at index.js:13
at Array.forEach ()
at deepFreeze (index.js:7)
at index.js:13
at Array.forEach ()
core.js:1350 ERROR Error: Uncaught (in promise): TypeError: Cannot add property _resolvedData, object is not extensible
TypeError: Cannot add property _resolvedData, object is not extensible
at MapSubscriber.project (router.js:3848)
at MapSubscriber../node_modules/rxjs/operators/map.js.MapSubscriber._next (map.js:79)
at MapSubscriber../node_modules/rxjs/Subscriber.js.Subscriber.next (Subscriber.js:89)
at ScalarObservable../node_modules/rxjs/observable/ScalarObservable.js.ScalarObservable._subscribe (ScalarObservable.js:49)
at ScalarObservable../node_modules/rxjs/Observable.js.Observable._trySubscribe (Observable.js:172)
at ScalarObservable../node_modules/rxjs/Observable.js.Observable.subscribe (Observable.js:160)
at MapOperator../node_modules/rxjs/operators/map.js.MapOperator.call (map.js:56)
at Observable../node_modules/rxjs/Observable.js.Observable.subscribe (Observable.js:157)
at Object.subscribeToResult (subscribeToResult.js:23)
at MergeMapSubscriber../node_modules/rxjs/operators/mergeMap.js.MergeMapSubscriber._innerSub (mergeMap.js:132)
at resolvePromise (zone.js:824)
at resolvePromise (zone.js:795)
at zone.js:873
at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:425)
at Object.onInvokeTask (core.js:4620)
at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
at Zone../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:192)
at drainMicroTaskQueue (zone.js:602)

wrongly freeze when an object which has a getter is in the store

Basically what I have is this

 abstract class BaseFilter {
    static filterName = 'Base';

	constructor(public value: any) {
	}

	get displayName() {
		return `${(this.constructor as any).filterName}: ${this.value}`;
	}

}

class FilterSort extends BaseFilter {
    static filterName = 'Sort';

    constructor(value: any, public order: 'ASC' | 'DESC') {
		super(value);
	}

	get displayName() {
		return `Sort by: ${this.value} ${this.order.toLowerCase()}`;
	}
}

let a = new FilterSort('lol', 'ASC');

console.log(a.displayName)

The above code doesn't work as this.value in the getter will somehow be undefined in some cases. Not all cases though, I'm not sure why as I don't have time to pin point the issue. It's resolved by changing the getter to a method call instead.

ngrx store freeze error

I am trying to implement [email protected] to our project and i'm getting this error on app startup:

ERROR Error: Uncaught (in promise): TypeError: Cannot add property _resolvedData, object is not extensible
TypeError: Cannot add property _resolvedData, object is not extensible
    at MapSubscriber.eval [as project] (router.js:3909)
    at MapSubscriber._next (map.js:79)
    at MapSubscriber.Subscriber.next (Subscriber.js:92)
    at ScalarObservable._subscribe (ScalarObservable.js:51)
    at ScalarObservable.Observable._trySubscribe (Observable.js:172)
    at ScalarObservable.Observable.subscribe (Observable.js:160)
    at MapOperator.call (map.js:57)

Any ideas? My store is configured like so:

StoreModule.forRoot(<any>{app: appReducer}, { metaReducers: metaReducers, initialState: initialState })

Project Adoption

@tsm91, @cyrip

I am one of the maintainers of the NgRx project This issue lays out that we plan on adding "freeze" functionality to Store in the future. In the meantime, I would like to take ownership of this project for the foreseeable future.

If you are willing to transfer the project, you can email me using my github email or reply here.

Dependency on deep-freeze seems to confuse SystemJS

I've got a project using ngrx and ngrx-store-freeze. But the dependency on deep-freeze seems to be confusing SystemJS.

If I use this in my SystemJS Builder configuration for packages:

{
  '@ngrx/core': {
    main: 'index.js',
    defaultExtension: 'js'
  },
  '@ngrx/store': {
    main: 'index.js',
    defaultExtension: 'js'
  },
  'ngrx-store-freeze': {
    main: 'dist/index.js',
    defaultExtension: 'js'
  }
}

I get an error during the build:

Error on fetch for deep-freeze.js at file:///C:/Developer/Projects/ngrx-tests/node_modules/deep-freeze.js
        Loading ngrx-store-freeze/dist/index.js

But if I manually add deep-freeze to the SystemJS packages, it finds it OK.

So I've got it working, but it seems very odd that I would have to manually add this package mapping for an internal dependency of a Node module. Is this normal/expected behavior? Or am I just doing something wrong?

Incorrect example in readme

In readme example, shouldn't StoreModule.forRoot(reducers, { metaReducers }), be StoreModule.forRoot(reducers, { metaReducers: metaReducers }),?

The objects are getting frozen too soon

The object is getting frozen even before the reducer returned the new state. That forces me to clone the object when i'm dispatching. That's not smth i'd like to do.

Am I missing something?

Should the storeFreeze replace an undefined state with an empty object?

Hi,

Looking at the code I see that storeFreeze does the following:

state = state || {};
...
const nextState = reducer(state, action);

I am not sure it is acceptable to have the state being replaced by an empty object because often people use a pattern where their reducer are defined as such:

export function reducer(state = { ... some initial state ... }, action: All): State { ... }

Wouldn't it conflicts?

Freezing the payload can make application crash, expecially with RouterStore

I'm already using a CustomStateRouterSerializer, the problem does not rely on the state but on the payload.

Example, when I get a RouterError, it's putting some weirds objects on the Paylod, and we get this.

image

I noticed this PR but I'm struggling trying to understand the point.

I imagine that if in the state we have a reference to the User object, when we freeze the State we also freeze the user object.. correct me if I'm wrong, but the original redux-freeze does not freeze the payload.

If we want to keep this feature (payload freeze), what about having some possibility to filter actions on deepfreezing it, for example filtering on the state type (so I can filter the RouterError, etc.)

Thanks in advance and keep up with the great work!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.