amcdnl / ngrx-actions Goto Github PK
View Code? Open in Web Editor NEW⚡️ Actions and Reducer Utilities for NGRX
License: MIT License
⚡️ Actions and Reducer Utilities for NGRX
License: MIT License
When doing something like that with ngrx:
files$: Observable<I18nFile[]> = this.store.select(getI18nFilesEntities);
I get a type check that the store will return something of type I18nFile[]
.
But if I change it to that:
@Select('i18nFiles.entities') files$: Observable<I18nFile[]>;
It doesn't type check that it's of type I18nFile[]
.
I'm not sure if it's possible to type check with decorators, but that would be nice.
feat(actions): less boilerplate! breaks compatibility with NgRx standard that requires a readonly "type" to be defined in Action types.
This is required for many reasons:
LoadSuccess
) in two different application contextsLoadPizzasSuccess
=> ab
); if two Action classes are minimized to the same name, you can have an action type conflict that leads to unknown behavioursI'm asking to revert that change.
Thank you
Make the action decorator accept strings (not just classes).
Currently its REALLY annoying to have to return a function with createReducer inside of it because of AoT.
I think providing our own NgrxActions.forRoot({ foo: FooStore })
would allow us to work around this and just wrap the StoreModule
actions.
Need to think more though...
Since Angular 5 is compatible with strictNullCheck, can you consider adding strict: true to tsconfig.json?
Can you consider changing return type OperatorFunction<Action, Action> to OperatorFunction<Action, T>?
Line 10 in 6ed55d5
Then user could use like this
@Effect()
getFooBar$ = this.actions$.pipe(
ofAction<GetFoo | GetBar>(
GetFoo, GetBar
),
map(action /* action has infer type GetFoo | GetBar */ => { ... })
@Effect(MyAction)
decorator is not working, effect functions are never called.
Using @ngrx/effects @Effect()
works as expected.
Versions:
"@angular/core": "~7.0.0", "@ngrx/store": "^6.1.2", "@ngrx/effects": "^6.1.2", "ngrx-actions": "^4.0.0",
Example code
@Store(initialAppState)
export class AppStore {
@Action(TestAction)
test(state: AppState, action: TestAction) {
state.test = 1;
// this runs after dispatch
}
@Action(TestComplete)
complete(state: AppState) {
state.test = 2;
// never runs
}
@Effect(TestAction)
testEffect() {
//never runs
return of(new TestComplete());
}
}
I have tried this with every combination of EffectsModule.forRoot()
i could think of, passing empty array, array containing the store class, no EffectsModule import, etc..
Version 7.x of the ngrx plattform is out. And this package needs an update. I can provide a pull request for the update if you want.
The project is missing a license 😉
I have some selectors that composed from URL and entities, how can I convert it to use with @Select
?
Can we do something like this?
export function Select(selector?: string | MemoizedSelector<any, any>): any {
return function(target: any, name: string, descriptor: TypedPropertyDescriptor<any>): void {
if (delete target[name] && typeof selector === 'string') {
Object.defineProperty(target, name, {
get: () => {
if (!NgrxSelect.store) {
throw new Error('NgrxSelect not connected to store!');
}
const fn = memoize(state => getValue(state, selector || name));
return NgrxSelect.store.select(fn);
},
enumerable: true,
configurable: true,
});
} else if (delete target[name] && typeof selector === 'function') {
Object.defineProperty(target, name, {
get: () => {
if (!NgrxSelect.store) {
throw new Error('NgrxSelect not connected to store!');
}
return NgrxSelect.store.select(selector);
},
enumerable: true,
configurable: true,
});
}
};
}
Hi Austin
I found that comparing ofAction vs ofType when passing more than one type gives two different result.
For instance having ofType('type1', 'type2') will work when both actions are invoke so uses the AND operator, however ofAction(type1, type2) works when any of those actions are invoked using the OR operator.
If you were to write something like:
@Select(state => {
console.log('here', this);
return this._mapFolders(state.folders.items)
}) items$: Observable<TreeNode[]>;
The this
would refer to something else rather than the intended class. Upon attempting to resolve this in select decorator fn like:
else {
fn = selectorOrFeature.bind(target);
}
The binding here is correctly happening but for whatever reason at runtime it is not resolved correctly. I'm wondering if this is a TypeScript compilation issue?
A suitable workaround is to use anonymous functions rather than class methods.
Hello @amcdnl , is not really an issue but why not implement a dispatch decorator, it would be nice :).
Great work by the way !
Add the ability to support multiple actions in a store definition.
@Store({ ... })
export class MyStore {
@Action(Action1, Action2)
myfn() { ... }
}
Selects are awesome but they dont provide a way to map values from one object to another. For instance if i were to write:
@Select(state => state.map(i => 'foo')) foo$;
It would run constantly because the compare results is not the same. I want a way that I can select something from the store and then map it into a completely different object.
Potentially API could be very similar just using a @Map
decorator. Could look at using a distinctUntilChanged
rx operator and then performing the map.
As the title says, currently ngrx-actions is not ng6 compatible (withouth rxjs-compat).
Is there any ETA for a new release?
Hello, is there a feature that would allow nesting reducers using NgrxActionModule
forRoot
/forFeature
?
Example would look like that:
NgrxActionsModule.forRoot({
levelA: {
levelB: {
myReducer: MyStore
}
}
})
Thank you in advance.
Memoize the select decorator.
Are there plans to publish version 4.0.1 to NPM? I am still only seeing 4.0.0 as being available.
After upgrading to 2.0.5, ofAction has error: Argument of type 'typeof LoginAction' is not assignable to parameter of type 'new () => Action'.
with action class:
export class LoginAction implements Action {
readonly type = '[Auth] Login';
constructor(public payload: { email: string; password: string }) {}
}
This is because LoginAction class constructor has 1 parameter while type new () => Action has none.
Create a pipe that will accept a path and return the select.
<li *ngFor="let todo of ('todos' | select | async)">{{todo.name}}</li>
Not sure if this is a good idea but sounds cool. Idea from: https://github.com/HarmoWatch/ngx-redux-core/blob/master/src/harmowatch/ngx-redux-core/pipes/redux-select.pipe.ts
When I import with forRoot call everything seem to work fine:
NgrxActionsModule.forRoot({ myStore: MyStore })
But when I try to import with forFeature call instead, it breaks:
NgrxActionsModule.forFeature('myFeature', { myStore: MyStore })
I am looking at upgrading out project to RxJs 6. Going through the code, I have made all changes that were having compile issues. However, every single Effect I have is showing the same error. Prior to RxJs 6 (up to and including 5.5.10), all worked as expected.
@Injectable()
export class WidgetSearchEffects {
@Effect()
detectWidgetDataRequested$ = this.actions$.pipe(
ofAction(OnWidgetDataRequested),
mergeMap((action: OnWidgetDataRequested) => this.widgetSearchService.searchById(action.payload)
.pipe(map((data: WidgetSearchResult) => WidgetSearchActions.onWidgetDataReceipt(data)))),
catchError(error => this.onError(error))
);
constructor(
private actions$: Actions,
private widgetSearchService: WidgetSearchService
) {}
private onError = (error: Error) => {
return of(WidgetSearchActions.serverError(error));
}
}
I get the same error on every effect:
Property 'pipe' does not exist on type 'Actions<Action>'.
Any ideas of where I went wrong or is this a compatibility issue with ngrx-actions and RxJs 6
Reading the class's constructor name as the type, breaks:
Idea could be to decorate the Actions w/ a type.
@Action('[Foo] bar')
MyActionClass { }
// cc: @MikeRyanDev
Hey guys!
I'm using @select decorator for ngrx-actions and it works pretty fine. Although, when the app and the store are pretty big, it's always very often the probability that the selector which is actually a string, to be wrong types.
Ex:
let's suppose we have: @select("myStore.myPieceOfStore") public pieceOfStore$: Observable;
It's very often a probability like 'myStore.myPieceOfStore' string to be wrong typed and we are not going to have any errors to tell us this.
Do you think we improve this?
Thanks!
If I were to implement #33 it would be possible to DI.
Hey @amcdnl , I'm trying to figure out the best way to unit test a service that uses the @Select
decorator provided by ngrx-action. Currently I'm using the TestBed from @angular/core, but I'd like to not have to spin up an angular module in order to test the service.
Is there any way we can "mock out" the @Select
decorator to avoid having to start up TestBed to test the interactions with the store?
Here is the service I'm trying to unit test:
import { Injectable } from '@angular/core';
import { Select } from 'ngrx-actions';
import { Observable } from 'rxjs/Observable';
import { first } from 'rxjs/operators';
import { Dispatch } from '../../decorators/redux/dispatch';
import { ConfigurationActions } from './configuration.actions';
import { ConfigurationSelectors } from './configuration.selectors';
@Injectable()
export class ConfigurationStateService {
@Select(ConfigurationSelectors.apiUrl) apiUrl$: Observable<string>;
@Select(ConfigurationSelectors.adminApiPath) adminApiPath$: Observable<string>;
@Select(ConfigurationSelectors.httpCallTimeout) httpCallTimeout$: Observable<number>;
@Select(ConfigurationSelectors.techDiffUnhandledExceptions) techDiffUnhandledExceptions$: Observable<boolean>;
@Select(ConfigurationSelectors.isConfigurationSet) private isConfigurationSet$: Observable<boolean>;
@Dispatch() private configurationRequest = ConfigurationActions.onConfigurationRequest;
onConfigurationRequest = () => {
this.isConfigurationSet$.pipe(
first((isLoaded: boolean) => isLoaded === false))
.subscribe(() => {
this.configurationRequest();
});
}
}
Here is the spec testing that service:
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { Store, StoreModule } from '@ngrx/store';
import { NgrxActionsModule } from 'ngrx-actions';
import { NgSelect } from '../../decorators/redux/ng-select';
import { ConfigurationState } from '../configuration/models/configuration-state.interface';
import { ConfigurationStateService } from './configuration-state.service';
import { configurationReducer } from './configuration.reducer';
import { ApplicationConfiguration } from './models/application-configuration.interface';
import { OnConfigurationReceived } from './models/on-configuration-received.interface';
import { OnConfigurationRequested } from './models/on-configuration-requested.interface';
import { OnConfigurationResetRequested } from './models/on-configuration-reset-requested.interface';
let store: Store<ConfigurationState>;
let ngSelect: NgSelect;
let appConfig: ApplicationConfiguration;
const initializeDependencies: () => void = () => {
TestBed.configureTestingModule({
imports: [
NgrxActionsModule,
StoreModule.forRoot({ configurationState: configurationReducer})
],
providers: [
NgSelect,
ConfigurationStateService
]
});
store = TestBed.get(Store);
ngSelect = TestBed.get(NgSelect);
ngSelect.connect(store);
};
const createComponent: () => ConfigurationStateService = () => {
const service = TestBed.get(ConfigurationStateService);
return service;
};
describe('State Service: Configuration: ', () => {
beforeEach(() => {
initializeDependencies();
});
describe('with isConfigurationSet = false', () => {
beforeEach(() => {
store.dispatch(new OnConfigurationResetRequested());
});
it('should dispatch a configuration request', fakeAsync(() => {
spyOn(store, 'dispatch').and.callThrough();
createComponent().onConfigurationRequest();
tick();
const action = new OnConfigurationRequested();
expect(store.dispatch).toHaveBeenCalledWith(action);
}));
});
describe('with isConfigurationSet = true', () => {
beforeEach(() => {
appConfig = {
adminApiPath: 'xyz',
techDiffUnhandledExceptions: true,
apiUrl: 'zyx',
httpCallTimeout: 0
};
store.dispatch(new OnConfigurationReceived(appConfig));
});
it('should not dispatch a configuration request', fakeAsync(() => {
spyOn(store, 'dispatch').and.callThrough();
createComponent().onConfigurationRequest();
tick();
expect(store.dispatch).not.toHaveBeenCalled();
}));
});
});
Hi,
Firstly, thanks very much for this library.
I have a problem when using Vscode insider and importing items from your library.
When importing from 'ngrx-actions' typescript reports it cannot find the export. However, when importing form 'ngrx-actions/public_api' everything work fine.
Older versions of typescript dont have a problem but cannot correctly indent the arguments of the pipe method.
Regards,
Tarek
README and article samples are looking like we can mutate state object directly and don't have to return anything from the reducer function. It doesn't seem to work...
If I do something like:
@Action(LoadAction)
public load(state: IState, action: LoadAction) {
state.isLoading: true,
state.isLoaded: false,
state.hasError: false,
state.error: null
}
It wouldn't work throwing exception about read-only variable modifications prohibited in the strict mode.
But if I change it to normal pure-function reducer, it will work just fine:
@Action(LoadAction)
public load(state: IState, action: LoadAction) {
return Object.assign({}, state, {
isLoading: true,
isLoaded: false,
hasError: false,
error: null
});
}
Tbh, the original idea of reducers as pure-functions is great... Sample code in the README looks like if it's a side-effect function with state mutation... if that's the case why then separate @effect from @action ? They are essentially the same...
When an action class is used in multiple action decorators on the same store, only one of the decorated methods is being called for this action. While the limitation itself makes sense, an error or warning would make it easier to detect bugs related to this.
Example:
@Store({
})
export class ExampleStore {
@Action(ExampleAction)
load(state: AppState, action: ExampleAction) {
alert('Message 1');
}
@Action(ExampleAction)
load(state: AppState, action: ExampleAction) {
alert('Message 2');
}
}
Only "Message 2" will be shown when ExampleAction
is dispatched.
My experience with decorators in Typescript is limited, but if it's okay, I would try myself on a PR for this.
Hi Guys,
I just updated to version 4 and now I am unable to build the project using Angular CLI 6.0.7
I get the following error - Cannot find module 'ngrx-actions'
I have deleted node_module and downloaded everything again. Reverting back to v3.1.6 allows me to build again.
I am using Angular 6, CLI 6.0.7 and NGRX 6.0.1
Regards,
Tarek
I cannot get it working with aot mode.
I've got a simple reducer going
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { SecurityState } from './security.interfaces';
import { createReducer, Store, Action } from 'ngrx-actions';
import { Action as StoreAction } from '@ngrx/store';
import { LoginSuccess, Logout } from './security.actions';
export const initialSecurityState: SecurityState = {
authenticated: false,
user: undefined
};
@Store(initialSecurityState)
export class AuthStore {
@Action(LoginSuccess)
loginSuccess(state: SecurityState, action: LoginSuccess) {
return {
...state,
authenticated: true,
user: action.user
};
}
@Action(Logout)
logout(state: SecurityState, action: Logout) {
return {
...state,
authenticated: false,
user: undefined
};
}
}
export const securityReducer = function (state: SecurityState, action: StoreAction) {
return createReducer<SecurityState>(AuthStore)(state, action);
};
And I've got a SecurityModule which I import from the AppModule.
import { ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SecurityRoutingModule } from './security-routing.module';
import { LoginPageComponent } from './components/login-page/login-page.component';
import { StoreModule } from '@ngrx/store';
import { securityReducer } from './security.reducer';
import { EffectsModule } from '@ngrx/effects';
import { SecurityEffects } from './security.effects';
import { IsAuthenticatedGuard } from './is-authenticated.guard';
@NgModule({
imports: [
CommonModule,
],
declarations: [LoginPageComponent]
})
export class SecurityModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: RootSecurityModule,
providers: [
IsAuthenticatedGuard,
],
};
}
}
@NgModule({
imports: [
SecurityModule,
StoreModule.forFeature('security', securityReducer),
EffectsModule.forFeature([
SecurityEffects
]),
SecurityRoutingModule,
],
})
export class RootSecurityModule {}
It works without aot, but as soon as I add the --aot
flag I get
ERROR in app/security/security.module.ts(32,40): Error during template compile of 'RootSecurityModule'
Function expressions are not supported in decorators in 'securityReducer'
'securityReducer' contains the error at app/security/security.reducer.ts(34,32)
Consider changing the function expression into an exported function.
I've read the documentation and I'm wrapping the createReducer as suggested, but it still fails.
Angular 5.2.1
Typescript 2.6.2
ngrx-actions 2.1.1
Could it have anythinng to do with that I'm adding the reducer with StoreModule.forFeature?
Any suggestions would be greatly appreciated :)
How can I return the initial state of the reducer(@Store) to be used in the following test?
describe('UNKNOWN action', () => {
test('should return the initial state', () => {
const action = {} as any;
const state = reducer(InitialState, action);
expect(state).toBe(InitialState);
});
});
Because I had the idea to go on creating InitialState and passing it to the Store that way.
(With this I have still got the InitialState Constant to use in the tests)
export const InitialState: LoginState = {
error: null,
pending: false
};
@Store(InitialState)
export class LoginStore {
...
}
current implement:
symbols.ts
export type ActionType = { new (...args: any[]): Action };
We can add generic type for ActionType like this:
export type ActionType<T extends Action = Action> = { new (...args: any[]): T };
Then we can reuse it, i.e in of-action.ts
import { ActionType } from './symbols';
export function ofAction<T extends Action>(allowedType: ActionType<T>): OperatorFunction<Action, T>;
or in action.ts
export function Action(...actionsKlasses: ActionType[]) { ... }
The following code creates a new select on EVERY get. This is performance issue.
Object.defineProperty(target, name, {
get:() => store.select(fn),
enumerable: true,
configurable: true
});
I tried to only create it once like:
Object.defineProperty(target, name, {
get: function() {
if (!select) {
select = createSelect();
}
return select;
},
enumerable: true,
configurable: true
});
however in real world unit tests, it started failing. I'm not sure the EXACT cause of this but I'm thinking its because the select is created statically and if u are recreating a class over and over again the select is not pointing to the new instance.
If that is true, I dont know a good way to do this because we need a hook to know when the class was created again and while we could do ngOnInit
hook, it would get shaken out in AoT if the user didn't define this.
@albyrock87 - any thoughts?
Hi Austin,
great work trying to reduce boilerplate code!
One of the projects I'm working on is still AngularJS with typescript, redux, redux-observables, reselect...
Would you maybe consider making a version for plain redux? Actions should also be POJOs instead of classes I think.
It might be enough to educate me about how to do it in a blog post or so. :)
I think that once we migrate to angular we will stick with redux and not switch to ngrx to use all the awesome middleware and tools from the redux-community (redux-persist, redux-offline...). ng2-redux would be also be a good choice I think.
Cheers,
Michael
I have created a sample project that demonstrates an issue I have encountered in one work projects. I am using a meta-reducer in order to reset the store state. The expected result is to have the initial state set, but one of the boolean values in the state is unexpectedly flipped.
I provided a nearly identical second store with the traditional ngrx implementation of the reducer and this problem does not exist. I have been looking for a solution, but have not had luck.
Here is the project: https://github.com/DerekSchmid/ngrx-actions-bug
Hi there, very nice project, great job.
This is not an actual issue but more a question or a suggestion, depending on how you look at it. I am still very very green in this whole observable stuff, ngrx and etc. I have setup a project that is still relatively small and the other day i saw that you added this new @effect() decorator that is allowing me to consolidate also the effects under my store file, so to the question:
@Effect(DeliverPizza)
deliverPizzaToCustomer(state, { payload }: DeliverPizza) {
this.pizzaService.deliver(payload);
}
What is the reasoning in providing the state parameter as first argument to the decorated method?
I find it handy to have the state supplied to the method, but most of the time in my effects I do only care about the action payload. Its not a big deal it just makes the linter to underline it. besides that I need to also type it each an every time. Maybe if it comes as a second argument it will be better, as I can simply omit the state if I don't need it.
Apparently this is not valid for the @action decorator as the reducer method would definitely need both parameters. Mentioned this, because it maybe that you wanted to be consistent with the decorators signatures.
Great work again.
As described in the title, the current version is not compatible with angular v6.
Could we please have a working example with effects and selectors ?
How Use @Store with Entity Works
Update Action signature to be like:
@Action(MyAction, { update: boolean });
@Action([MyAction, MyAction2], { update: boolean });
@Action('MY_ACTION')
update
would be optional to spread the existing.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.