Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ $ npm install @ngxs/store@dev

- Fix: Do not re-use the global `Store` instance between different apps [#1740](https://github.com/ngxs/store/pull/1740)
- Fix: Do not run `Promise.then` within synchronous tests when decorating factory [#1753](https://github.com/ngxs/store/pull/1753)
- Fix: Devtools Plugin - Do not connect to devtools when the plugin is disabled [#1761](https://github.com/ngxs/store/pull/1761)
- Fix: Router Plugin - Cleanup subscriptions when the root view is destroyed [#1754](https://github.com/ngxs/store/pull/1754)
- Performance: Tree-shake errors and warnings [#1732](https://github.com/ngxs/store/pull/1732)
- Performance: Router Plugin - Tree-shake `isAngularInTestMode()` [#1738](https://github.com/ngxs/store/pull/1738)
Expand Down
4 changes: 2 additions & 2 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@
"path": "./@ngxs/devtools-plugin/fesm2015/ngxs-devtools-plugin.js",
"package": "@ngxs/devtools-plugin",
"target": "es2015",
"maxSize": "9.035KB",
"maxSize": "10.42KB",
"compression": "none"
},
{
"path": "./@ngxs/devtools-plugin/fesm5/ngxs-devtools-plugin.js",
"package": "@ngxs/devtools-plugin",
"target": "es5",
"maxSize": "10.525KB",
"maxSize": "12.05KB",
"compression": "none"
},
{
Expand Down
80 changes: 62 additions & 18 deletions packages/devtools-plugin/src/devtools.plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Inject, Injectable, Injector } from '@angular/core';
import { Inject, Injectable, Injector, NgZone, OnDestroy, ɵglobal } from '@angular/core';
import { getActionTypeFromInstance, NgxsNextPluginFn, NgxsPlugin, Store } from '@ngxs/store';
import { tap, catchError } from 'rxjs/operators';

Expand All @@ -9,24 +9,44 @@ import {
NgxsDevtoolsOptions
} from './symbols';

const enum ReduxDevtoolsActionType {
Dispatch = 'DISPATCH',
Action = 'ACTION'
}

const enum ReduxDevtoolsPayloadType {
JumpToAction = 'JUMP_TO_ACTION',
JumpToState = 'JUMP_TO_STATE',
ToggleAction = 'TOGGLE_ACTION',
ImportState = 'IMPORT_STATE'
}

/**
* Adds support for the Redux Devtools extension:
* http://extension.remotedev.io/
*/
@Injectable()
export class NgxsReduxDevtoolsPlugin implements NgxsPlugin {
private readonly devtoolsExtension: NgxsDevtoolsExtension | null = null;
private readonly windowObj: any = typeof window !== 'undefined' ? window : {};
export class NgxsReduxDevtoolsPlugin implements OnDestroy, NgxsPlugin {
private devtoolsExtension: NgxsDevtoolsExtension | null = null;
private readonly globalDevtools =
ɵglobal['__REDUX_DEVTOOLS_EXTENSION__'] || ɵglobal['devToolsExtension'];

private unsubscribe: VoidFunction | null = null;

constructor(
@Inject(NGXS_DEVTOOLS_OPTIONS) private _options: NgxsDevtoolsOptions,
private _injector: Injector
private _injector: Injector,
private _ngZone: NgZone
) {
const globalDevtools =
this.windowObj['__REDUX_DEVTOOLS_EXTENSION__'] || this.windowObj['devToolsExtension'];
if (globalDevtools) {
this.devtoolsExtension = globalDevtools.connect(_options) as NgxsDevtoolsExtension;
this.devtoolsExtension.subscribe(a => this.dispatched(a));
this.connect();
}

ngOnDestroy(): void {
if (this.unsubscribe !== null) {
this.unsubscribe();
}
if (this.globalDevtools) {
this.globalDevtools.disconnect();
}
}

Expand All @@ -41,8 +61,7 @@ export class NgxsReduxDevtoolsPlugin implements NgxsPlugin {
* Middleware handle function
*/
handle(state: any, action: any, next: NgxsNextPluginFn) {
const isDisabled = this._options && this._options.disabled;
if (!this.devtoolsExtension || isDisabled) {
if (!this.devtoolsExtension || this._options.disabled) {
return next(state, action);
}

Expand Down Expand Up @@ -73,16 +92,16 @@ export class NgxsReduxDevtoolsPlugin implements NgxsPlugin {
* Handle the action from the dev tools subscription
*/
dispatched(action: NgxsDevtoolsAction) {
if (action.type === 'DISPATCH') {
if (action.type === ReduxDevtoolsActionType.Dispatch) {
if (
action.payload.type === 'JUMP_TO_ACTION' ||
action.payload.type === 'JUMP_TO_STATE'
action.payload.type === ReduxDevtoolsPayloadType.JumpToAction ||
action.payload.type === ReduxDevtoolsPayloadType.JumpToState
) {
const prevState = JSON.parse(action.state);
this.store.reset(prevState);
} else if (action.payload.type === 'TOGGLE_ACTION') {
} else if (action.payload.type === ReduxDevtoolsPayloadType.ToggleAction) {
console.warn('Skip is not supported at this time.');
} else if (action.payload.type === 'IMPORT_STATE') {
} else if (action.payload.type === ReduxDevtoolsPayloadType.ImportState) {
const {
actionsById,
computedStates,
Expand All @@ -96,9 +115,34 @@ export class NgxsReduxDevtoolsPlugin implements NgxsPlugin {
);
this.store.reset(computedStates[currentStateIndex].state);
}
} else if (action.type === 'ACTION') {
} else if (action.type === ReduxDevtoolsActionType.Action) {
const actionPayload = JSON.parse(action.payload);
this.store.dispatch(actionPayload);
}
}

private connect(): void {
if (!this.globalDevtools || this._options.disabled) {
return;
}

// The `connect` method adds `message` event listener since it communicates
// with an extension through `window.postMessage` and message events.
// We handle only 2 events; thus, we don't want to run many change detections
// because the extension sends events that we don't have to handle.
this.devtoolsExtension = this._ngZone.runOutsideAngular(
() => <NgxsDevtoolsExtension>this.globalDevtools.connect(this._options)
);

this.unsubscribe = this.devtoolsExtension.subscribe(action => {
if (
action.type === ReduxDevtoolsActionType.Dispatch ||
action.type === ReduxDevtoolsActionType.Action
) {
this._ngZone.run(() => {
this.dispatched(action);
});
}
});
}
}
3 changes: 1 addition & 2 deletions packages/devtools-plugin/src/symbols.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { InjectionToken } from '@angular/core';
import { Subscription } from 'rxjs';

/**
* Interface for the redux-devtools-extension API.
*/
export interface NgxsDevtoolsExtension {
init(state: any): void;
send(action: any, state?: any): void;
subscribe(fn: (message: NgxsDevtoolsAction) => void): Subscription;
subscribe(fn: (message: NgxsDevtoolsAction) => void): VoidFunction;
}

export interface NgxsDevtoolsAction {
Expand Down
4 changes: 2 additions & 2 deletions packages/devtools-plugin/tests/devtools-custom.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('[TEST]: Devtools with custom settings', () => {
}
}

it('should be disable devtools', () => {
it('should disable devtools', () => {
TestBed.configureTestingModule({
imports: [
NgxsModule.forRoot([CountState]),
Expand All @@ -34,7 +34,7 @@ describe('[TEST]: Devtools with custom settings', () => {
expect(store.snapshot()).toEqual({ count: 1 });
});

it('should be check custom name', () => {
it('should check custom name', () => {
const devtools = new ReduxDevtoolsMockConnector();
createReduxDevtoolsExtension(devtools);

Expand Down
2 changes: 1 addition & 1 deletion packages/devtools-plugin/tests/devtools-errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('[TEST]: Devtools with errors', () => {
}
}

it('should be correct worked with errors', () => {
it('should correctly handle errors', () => {
const devtools = new ReduxDevtoolsMockConnector();
createReduxDevtoolsExtension(devtools);

Expand Down
4 changes: 2 additions & 2 deletions packages/devtools-plugin/tests/devtools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('[TEST]: Devtools', () => {
store = TestBed.inject(Store);
});

it('should be correct execution redux devtools with catching actions', () => {
it('should should catch actions correctly', () => {
expect(devtools.options).toEqual({ name: 'NGXS' });
expect(devtools.initialState).toEqual({ count: 0 });
expect(devtools.devtoolsStack).toEqual([
Expand Down Expand Up @@ -110,7 +110,7 @@ describe('[TEST]: Devtools', () => {
expect(store.snapshot()).toEqual({ count: 2 });
});

it('should be correct jump to state', () => {
it('should jump to state', () => {
expect(store.snapshot()).toEqual({ count: 0 });

store.dispatch({ type: 'increment' }); // id - 1, count - 1
Expand Down