ngxs-labs / select-snapshot Goto Github PK
View Code? Open in Web Editor NEW🌊 Flexibile decorator that allows to get a snapshot of the state
License: MIT License
🌊 Flexibile decorator that allows to get a snapshot of the state
License: MIT License
learning about this project from https://medium.com/better-programming/how-i-got-rid-of-state-observables-in-angular-2c69cbbbbd0e
thanks @alessiaAmitrano
I was little confused seeing at other project https://github.com/ngxs-labs/select-snapshot-ivy
Which one show I use for Angular 9 project?
This looks like very useful for NGXS users and reduce unnecessary boilerplate code. when will be this integrated with NGXS?
A console log is thrown during test run.
While running, no error is thrown, and the ViewSelectSnapshot
works fine.
During tests, it does not fail the test, but this error appears:
console.error
Unhandled Promise rejection: Cannot set property 'factory' of undefined ; Zone: <root> ; Task: Promise.then ; Value: TypeError: Cannot set property 'factory' of undefined
at decorateView (/_MY_PROJECT_/node_modules/@ngxs-labs/select-snapshot/bundles/ng:/@ngxs-labs/select-snapshot/lib/core/decorators/view-select-snapshot.ts:48:41)
at /_MY_PROJECT_/node_modules/@ngxs-labs/select-snapshot/bundles/ng:/@ngxs-labs/select-snapshot/lib/core/decorators/view-select-snapshot.ts:37:36
at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (/_MY_PROJECT_/node_modules/zone.js/dist/zone.js:386:30)
at Zone.Object.<anonymous>.Zone.run (/_MY_PROJECT_/node_modules/zone.js/dist/zone.js:143:47)
at /_MY_PROJECT_/node_modules/zone.js/dist/zone.js:891:38
at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invokeTask (/_MY_PROJECT_/node_modules/zone.js/dist/zone.js:421:35)
at Zone.Object.<anonymous>.Zone.runTask (/_MY_PROJECT_/node_modules/zone.js/dist/zone.js:188:51)
at drainMicroTaskQueue (/_MY_PROJECT_/node_modules/zone.js/dist/zone.js:601:39)
at processTicksAndRejections (internal/process/task_queues.js:93:5) TypeError: Cannot set property 'factory' of undefined
at decorateView (/_MY_PROJECT_/node_modules/@ngxs-labs/select-snapshot/bundles/ng:/@ngxs-labs/select-snapshot/lib/core/decorators/view-select-snapshot.ts:48:41)
at /_MY_PROJECT_/node_modules/@ngxs-labs/select-snapshot/bundles/ng:/@ngxs-labs/select-snapshot/lib/core/decorators/view-select-snapshot.ts:37:36
at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (/_MY_PROJECT_/node_modules/zone.js/dist/zone.js:386:30)
at Zone.Object.<anonymous>.Zone.run (/_MY_PROJECT_/node_modules/zone.js/dist/zone.js:143:47)
at /_MY_PROJECT_/node_modules/zone.js/dist/zone.js:891:38
at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invokeTask (/_MY_PROJECT_/node_modules/zone.js/dist/zone.js:421:35)
at Zone.Object.<anonymous>.Zone.runTask (/_MY_PROJECT_/node_modules/zone.js/dist/zone.js:188:51)
at drainMicroTaskQueue (/_MY_PROJECT_/node_modules/zone.js/dist/zone.js:601:39)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at Object.api.onUnhandledError (../../../node_modules/zone.js/dist/zone.js:690:29)
at handleUnhandledRejection (../../../node_modules/zone.js/dist/zone.js:713:17)
at _loop_1 (../../../node_modules/zone.js/dist/zone.js:704:21)
at Object.api.microtaskDrainDone (../../../node_modules/zone.js/dist/zone.js:708:17)
at drainMicroTaskQueue (../../../node_modules/zone.js/dist/zone.js:608:22)
[x] Support request => https://github.com/ngxs/store/blob/master/CONTRIBUTING.md
An error is thrown on console.
No error thrown on console.
I've a sidebar component, that works very well with ViewSelectSnapshot
.
state
export interface SidebarStateInfo {
display?: boolean;
position?: string;
}
export interface SidebarStateModel {
info: SidebarStateInfo;
}
export const defaultSidebarInfo: SidebarStateInfo = {
position: 'left'
};
export const initialSidebarState: SidebarStateModel = {
info: defaultSidebarInfo
};
@State<SidebarStateModel>({
name: 'sidebar',
defaults: initialSidebarState
})
@Injectable()
export class SidebarState {
@Selector()
@ImmutableSelector()
static getSidebarInfo(state: SidebarStateModel): SidebarStateInfo {
return state.info;
}
...
module
const imports = [CommonModule, NgxsModule.forFeature([SidebarState]), PrimeSidebarModule];
const components = [SidebarComponent];
@NgModule({
declarations: [...components],
exports: [...components],
imports
})
export class SidebarModule {}
component
@Component({
...
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SidebarComponent {
@ViewSelectSnapshot(SidebarState.getSidebarInfo) info: SidebarStateInfo;
...
test
describe('SidebarComponent', () => {
let component: SidebarComponent;
let fixture: ComponentFixture<SidebarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
NgxsModule.forRoot([]),
NgxsDispatchPluginModule.forRoot(),
NgxsSelectSnapshotModule.forRoot(),
SidebarModule
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SidebarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Libs:
- @angular/core version: ^9.1.12
- @ngxs/store version: ^3.7.0
For Tooling issues:
- Node version: v14.14.0
- Platform: MacOs Catalina
Thanks.
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[ ] Performance issue
[X] Feature request
[ ] Documentation issue or request
[ ] Support request => https://github.com/ngxs/store/blob/master/CONTRIBUTING.md
[ ] Other... Please describe:
Currently we can't use SelectSnapshot with ChangeDetectionStrategy.OnPush
, or the values won't update as they should.
It would be great if we could use it with OnPush
too. Any plans on adding support for it?
Please sorry for not using the template, close the issue if you feel is not descriptive enough.
npm install @ngxs-labs/select-snapshot --save
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/tslib
npm ERR! tslib@"^2.0.0" from the root project
npm ERR! tslib@"^2.0.0" from @angular/[email protected]
npm ERR! node_modules/@angular/core
npm ERR! @angular/core@"~11.0.1" from the root project
npm ERR! peer @angular/core@">=9" from @ngxs-labs/[email protected]
npm ERR! node_modules/@ngxs-labs/select-snapshot
npm ERR! @ngxs-labs/select-snapshot@"*" from the root project
npm ERR! 1 more (@ngxs/store)
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer tslib@"^1.10.0" from @ngxs-labs/[email protected]
npm ERR! node_modules/@ngxs-labs/select-snapshot
npm ERR! @ngxs-labs/select-snapshot@"*" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
[ ] Regression (a behavior that used to work and stopped working in a new release)
[X] Bug report
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => https://github.com/ngxs/store/blob/master/CONTRIBUTING.md
[ ] Other... Please describe:
If SelectSnapshot
is used with a state without defaults it returns an object, which can result in issues as shown in the attached example.
I'd expect SelectSnapshot
to return undefined
when no default value is configured.
In this example you can see that the *ngIf="animals"
check is passed even though the state isn't defined yet, which results in an error when the *ngFor
tries to iterate over it.
https://stackblitz.com/edit/select-snapshot-initial-value
In my app I have a piece of state which is undefined by default, and I initialize it after a certain server response. Since I don't need defaults for it, I don't expect SelectSnapshot
to return an object.
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => https://github.com/ngxs/store/blob/master/CONTRIBUTING.md
[x] Other... Please describe: Question
Is that is really reliable?
file: static-injector.ts
line: 2
import { ɵm as NgxsConfig } from '@ngxs/store';
ɵm
is just a keyword, right? Maybe it will change. It is not a reliable solution.
Is this project still under active development and if so is there project roadmap/plan publicly available with what features are planned?
[ ] Regression (a behaviour that used to work and stopped working in a new release)
[x] Bug report
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => https://github.com/ngxs/store/blob/master/CONTRIBUTING.md
[ ] Other... Please describe:
I get this error Uncaught ReferenceError: Cannot access 'AuthState' before initialization
when I use @SelectSnapshot
in a service but it works fine in a component or an interceptor. Replacing @SelectSnapshot
with store.selectSnapshot
works fine.
Does anyone know why this is happening?
Do not throw a reference error and work as expected in a service
Sample code that triggers the error:
import { Injectable } from '@angular/core';
import { EmitterService } from '@ngxs-labs/emitter';
import { SelectSnapshot } from '@ngxs-labs/select-snapshot';
import { Store } from '@ngxs/store';
import { ApiService } from 'src/app/services/api/api.service';
import { AuthState } from '../../state/auth.state';
@Injectable({
providedIn: 'root'
})
export class AuthService {
@SelectSnapshot(AuthState.token) token: string | null; // <- Uncaught ReferenceError: Cannot access 'AuthState' before initialization
constructor(private readonly apiService: ApiService, private emitter: EmitterService) { }
logout() {
const token = ''; // this.store.selectSnapshot(AuthState.token); // Works!
const subscription = this.apiService.post<any>('user/logout', {token});
subscription.subscribe(() => {
this.emitter.action(AuthState.logout).emit();
//TODO: Handle elsewhere (at state level maybe)
// this.router.navigate(['/login']);
});
return subscription;
}
}
Libs:
- @angular/core version: 13.2.6
- @ngxs/store version: 3.7.3
Browser:
- [x] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
For Tooling issues:
- Node version: v16.15.0
- Platform: Windows
Others:
[x] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => https://github.com/ngxs/store/blob/master/CONTRIBUTING.md
[ ] Other... Please describe:
I updated @ngxs-labs/select-snapshot from 1.2.0 to 3.0.0. I noticed a change in unit tests:
Let's take as an example this very simple component that uses @SelectSnapshot()
:
// sample.component.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { AccessSelectors } from '@app/shared/access/access.selectors';
import { Access } from '@app/shared/clients/audience-experience-api.client';
import { SelectSnapshot } from '@ngxs-labs/select-snapshot';
@Component({
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SampleComponent {
@SelectSnapshot(AccessSelectors.access) readonly access!: Set<Access>;
targetMethod(): void {
if (this.access.has(Access.ViewBudget)) {
console.log('User has access');
} else {
console.log('User has NOT access');
}
}
}
And test it:
// sample.component.spec.ts
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { ExceptionService } from '@app/shared/exception/exception.service';
import { ExceptionServiceMock } from '@app/shared/exception/exception.service.mock';
import { NgxsSelectSnapshotModule } from '@ngxs-labs/select-snapshot';
import { NgxsModule, Store } from '@ngxs/store';
import { ngxsConfig } from '@testing/external-dependencies/ngxs-config';
import { noop } from 'lodash';
import { SampleComponent } from './sample.component';
import { AccessState } from './shared/access/access.state';
import { Access } from './shared/clients/audience-experience-api.client';
describe('SampleComponent', () => {
let component: SampleComponent;
let store: Store;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SampleComponent],
imports: [HttpClientTestingModule, NgxsSelectSnapshotModule, NgxsModule.forRoot([AccessState], ngxsConfig)],
providers: [{ provide: ExceptionService, useClass: ExceptionServiceMock }],
});
component = TestBed.createComponent(SampleComponent).componentInstance;
store = TestBed.inject(Store);
});
afterEach(() => jest.clearAllMocks());
describe('targetMethod', () => {
it('should log if access', () => {
// Arrange
store.reset({ access: { allAccess: new Set([Access.ViewBudget]) } });
jest.spyOn(console, 'log').mockImplementation(noop);
// Act
component.targetMethod();
// Assert
expect(console.log).toHaveBeenCalledTimes(1);
expect(console.log).toHaveBeenCalledWith('User has access');
});
it('should log if NO access', () => {
// Arrange
store.reset({ access: { allAccess: new Set() } });
jest.spyOn(console, 'log').mockImplementation(noop);
// Act
component.targetMethod();
// Assert
expect(console.log).toHaveBeenCalledTimes(1);
expect(console.log).toHaveBeenCalledWith('User has NOT access');
});
});
});
In the test, I use store.reset()
to change the content of the state.
With @ngxs-labs/select-snapshot v1.2.0, the tests pass.
With v3.0.0, they fail:
FAIL src/app/sample.component.spec.ts
SampleComponent
targetMethod
√ should log if access (109 ms)
× should log if NO access (30 ms)
● SampleComponent › targetMethod › should log if NO access
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: "User has NOT access"
Received: "User has access"
Number of calls: 1
52 | // Assert
53 | expect(console.log).toHaveBeenCalledTimes(1);
> 54 | expect(console.log).toHaveBeenCalledWith('User has NOT access');
| ^
55 | });
56 | });
57 | });
at it (src/app/sample.component.spec.ts:54:27)
I tried to call TestBed.createComponent()
after store.reset()
and to call detectChanges()
on the component fixture, without success.
Is it a bug or I am missing something?
Libs:
- @angular/core version: 11.2.14
- @ngxs/store version: 3.7.3
If I use @ViewSelectSnapshot in one Component multiple times like:
@component({
selector: 'app-progress',
template: <div> <div> <span>progress1</span> </div> <div> <span>progress2</span> </div> </div>
,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProgressComponent {
@ViewSelectSnapshot(ProgressState.getProgress1) progress1: number;
@ViewSelectSnapshot(ProgressState.getProgress2) progress2: number;
}
MarkForCheck is only executed if progress2 is changed in the state. If progress1 is changed no markforcheck is executed.
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ x] Bug report
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => https://github.com/ngxs/store/blob/master/CONTRIBUTING.md
[ ] Other... Please describe:
Libs:
Browser:
We are running angular 8 at our company and although we plan to upgrade to 9 sometime soon, that hasn't happened yet.
I was wondering how much effort do you think it is to also support angular 8?
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[ ] Performance issue
[x] Feature request
[ ] Documentation issue or request
[ ] Support request => https://github.com/ngxs/store/blob/master/CONTRIBUTING.md
[ ] Other... Please describe:
It doesn't support Angular 8
It could support Angular 8
Running npm install on node version 12.16.3 installs the package, but I get these runtime errors:
ERROR in ./node_modules/@ngxs-labs/select-snapshot/fesm2015/ngxs-labs-select-snapshot.js 136:16-28
"export 'ɵNG_COMP_DEF' was not found in '@angular/core'
ERROR in ./node_modules/@ngxs-labs/select-snapshot/fesm2015/ngxs-labs-select-snapshot.js 136:38-49
"export 'ɵNG_DIR_DEF' was not found in '@angular/core'
Looking at @angular/core, it looks like they just renamed those variables from NG_COMPONENT_DEF to NG_COMP_DEF at some point in the past.
Be able to support 1 angular major version older than it currently supports
Libs:
Browser:
For Tooling issues:
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[ ] Performance issue
[ ] Feature request
[x] Documentation issue or request
[ ] Support request => https://github.com/ngxs/store/blob/master/CONTRIBUTING.md
[ ] Other... Please describe:
Hello!
I would like to propose a small improvement to the documentation.
In my humble opinion, I find that that the behavior of the @SelectSnapshot
decorator is currently not immediately clear when reading the documentation.
At first, I wrongly thought that using it to decorate a component's property was doing a one-off Store.selectSnapshot(...)
, and thus that the property would get the selector's value at the moment the component is instantiated.
While in fact, it creates a property getter that will call Store.selectSnapshot(...)
each time the getter is called (source code).
@SelectSnapshot(MyState.mySelector) myProperty;
// roughly equates to:
get myProperty() {
return store.selectSnapshot(MyState.mySelector);
}
I suggest updating the documentation to make it more obvious that getting the property selects a snapshot each and every time, thus giving the latest value from the state.
Here is my proposed PR: #156
Thanks!
We see in our server logs many of these 2 FATAL Errors on production and on non-production environments:
We clearly have imported both ngxs modules in AppModule and in the Feature Modules.
In App module:
NgxsModule.forRoot(
[
xxx....
],
{
developmentMode: !environment.production,
},
),
NgxsRouterPluginModule.forRoot(),
NgxsFormPluginModule.forRoot(),
NgxsSelectSnapshotModule.forRoot(),
NgxsStoragePluginModule.forRoot({
...xxx
},
}),
NgxsReduxDevtoolsPluginModule.forRoot({
name: 'xxxx',
disabled: environment.production,
}),
in Feature Module:
NgxsModule.forFeature([xxx, yyy]),
NgxsSelectSnapshotModule,
It is a very huge project with many modules and components... And the errors are only happening via SSR, and there it happens rarely on our dev machines... When it does, though, we see a LOT of Error statements successively while no other component or module is complaing with an error. It is just ngxs.
It seems, that SSR still deliveres the html, and the Angular app loads still well on the client (including all ngxs stuff)
Ok. We found out how to reproduce:
Guess: This might be caused by angular destroying all stuff and both ngxs modules will clear the Injector onDestroy.
As the errors happens ONLY when we reload quickly after aborting (just aborting never creates the errors), it must be sth about angular universal still (re-?) using some components while ngxs already got rid of its injector reference.
FATAL [main.js:2897] NgxsSelectSnapshotModuleIsNotImported [Error]: You've forgotten to import "NgxsSelectSnapshotModule"!
at assertDefined (/opt/app/dist/server/main.js:2897:xx)
at getStore (/opt/app/dist/server/main.js:2897:xx)
at XXXComponent.get [as isXXX] (/opt/app/dist/server/main.js:2897:xx)
at XXXComponent_Template (/opt/app/dist/server/main.js:2897:xx)
at executeTemplate (/opt/app/dist/server/main.js:2897:xx)
at refreshView (/opt/app/dist/server/main.js:2897:xx)
at refreshComponent (/opt/app/dist/server/main.js:2897:xx)
at refreshChildComponents (/opt/app/dist/server/main.js:2897:xx)
at refreshView (/opt/app/dist/server/main.js:2897:xx)
at refreshComponent (/opt/app/dist/server/main.js:2897:xx)
or....
FATAL [main.js:2897] Error: You have forgotten to import the NGXS module!
at createSelectObservable (/opt/app/dist/server/main.js:1:xx)
at AppComponent.get [as progress] (/opt/app/dist/server/main.js:1:xx)
at AppComponent_Template (/opt/app/dist/server/main.js:2897:1625353)
at executeTemplate (/opt/app/dist/server/main.js:2897:xx)
at refreshView (/opt/app/dist/server/main.js:2897:xx)
at refreshComponent (/opt/app/dist/server/main.js:2897:xx)
at refreshChildComponents (/opt/app/dist/server/main.js:2897:xx)
at refreshView (/opt/app/dist/server/main.js:2897:xx)
at renderComponentOrTemplate (/opt/app/dist/server/main.js:2897:xx)
at tickRootContext (/opt/app/dist/server/main.js:2897:xx)
Libs:
"@angular/core": "^11.0.6",
"@ngxs/store": "^3.7.1",
"@ngxs-labs/select-snapshot": "^2.0.1",
Browser: only on SSR
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.