├── .eslintignore
├── apps
├── demos
│ ├── src
│ │ ├── polyfills.ts
│ │ ├── test-setup.ts
│ │ ├── app
│ │ │ ├── app.component.scss
│ │ │ ├── store
│ │ │ │ ├── index.ts
│ │ │ │ └── progress
│ │ │ │ │ ├── progress.actions.ts
│ │ │ │ │ └── progress.state.ts
│ │ │ ├── progress
│ │ │ │ ├── progress.component.html
│ │ │ │ ├── progress.component.scss
│ │ │ │ └── progress.component.ts
│ │ │ ├── app.component.html
│ │ │ ├── app.server.module.ts
│ │ │ ├── app.module.ts
│ │ │ └── app.component.ts
│ │ ├── environments
│ │ │ ├── environment.ts
│ │ │ └── environment.prod.ts
│ │ ├── index.html
│ │ ├── main.server.ts
│ │ ├── main.ts
│ │ └── server.ts
│ ├── tsconfig.editor.json
│ ├── tsconfig.spec.json
│ ├── tsconfig.server.json
│ ├── tsconfig.app.json
│ ├── .browserslistrc
│ ├── jest.config.ts
│ ├── tsconfig.json
│ ├── .eslintrc.json
│ └── project.json
└── demos-e2e
│ ├── tsconfig.json
│ ├── src
│ ├── plugins
│ │ └── index.js
│ └── e2e
│ │ └── ssr.cy.ts
│ ├── tsconfig.e2e.json
│ ├── cypress.config.ts
│ ├── .eslintrc.json
│ └── project.json
├── .commitlintrc.json
├── libs
└── select-snapshot
│ ├── src
│ ├── test-setup.ts
│ ├── index.ts
│ └── lib
│ │ ├── core
│ │ ├── decorators
│ │ │ ├── select-snapshot.ts
│ │ │ └── view-select-snapshot.ts
│ │ └── internals
│ │ │ ├── internals.ts
│ │ │ ├── static-injector.ts
│ │ │ └── select-snapshot.ts
│ │ ├── select-snapshot.module.ts
│ │ └── select-snapshot.spec.ts
│ ├── ng-package.json
│ ├── tsconfig.lib.prod.json
│ ├── tsconfig.spec.json
│ ├── tsconfig.lib.json
│ ├── package.json
│ ├── jest.config.ts
│ ├── tsconfig.json
│ ├── .eslintrc.json
│ └── project.json
├── .husky
├── pre-commit
└── commit-msg
├── jest.config.ts
├── .gitignore
├── .yarnrc.yml
├── tsconfig.base.json
├── jest.preset.js
├── LICENSE
├── .eslintrc.json
├── .github
├── PULL_REQUEST_TEMPLATE.md
├── workflows
│ └── select-snapshot.yml
└── ISSUE_TEMPLATE.md
├── nx.json
├── .yarn
└── plugins
│ └── @yarnpkg
│ └── plugin-after-install.cjs
├── package.json
├── decorate-angular-cli.js
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/apps/demos/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 |
--------------------------------------------------------------------------------
/apps/demos/src/test-setup.ts:
--------------------------------------------------------------------------------
1 | import 'jest-preset-angular/setup-jest';
2 |
--------------------------------------------------------------------------------
/apps/demos/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | button {
2 | margin: 0 5px;
3 | }
4 |
--------------------------------------------------------------------------------
/.commitlintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@commitlint/config-conventional"]
3 | }
4 |
--------------------------------------------------------------------------------
/libs/select-snapshot/src/test-setup.ts:
--------------------------------------------------------------------------------
1 | import 'jest-preset-angular/setup-jest';
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn lint-staged
5 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn commitlint --edit $1
5 |
--------------------------------------------------------------------------------
/apps/demos/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: false,
3 | };
4 |
--------------------------------------------------------------------------------
/apps/demos/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | };
4 |
--------------------------------------------------------------------------------
/apps/demos/src/app/store/index.ts:
--------------------------------------------------------------------------------
1 | export * from './progress/progress.state';
2 | export * from './progress/progress.actions';
3 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | const { getJestProjects } = require('@nrwl/jest');
2 |
3 | export default {
4 | projects: getJestProjects()
5 | };
6 |
--------------------------------------------------------------------------------
/apps/demos/src/app/store/progress/progress.actions.ts:
--------------------------------------------------------------------------------
1 | export class IncrementProgress {
2 | static readonly type = '[Progress] Increment';
3 | }
4 |
--------------------------------------------------------------------------------
/apps/demos/src/app/progress/progress.component.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/apps/demos/tsconfig.editor.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": ["**/*.ts"],
4 | "compilerOptions": {
5 | "types": ["jest", "node"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/demos-e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "files": [],
4 | "include": [],
5 | "references": [
6 | {
7 | "path": "./tsconfig.e2e.json"
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/apps/demos-e2e/src/plugins/index.js:
--------------------------------------------------------------------------------
1 | const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor');
2 |
3 | module.exports = (on, config) => {
4 | on('file:preprocessor', preprocessTypescript(config));
5 | };
6 |
--------------------------------------------------------------------------------
/libs/select-snapshot/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/libs/select-snapshot",
4 | "lib": {
5 | "entryFile": "src/index.ts"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/libs/select-snapshot/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.lib.json",
3 | "compilerOptions": {
4 | "declarationMap": false
5 | },
6 | "angularCompilerOptions": {
7 | "compilationMode": "partial"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/libs/select-snapshot/src/index.ts:
--------------------------------------------------------------------------------
1 | export { SelectSnapshot } from './lib/core/decorators/select-snapshot';
2 | export { ViewSelectSnapshot } from './lib/core/decorators/view-select-snapshot';
3 | export { NgxsSelectSnapshotModule } from './lib/select-snapshot.module';
4 |
--------------------------------------------------------------------------------
/apps/demos-e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "sourceMap": false,
6 | "skipLibCheck": true,
7 | "types": ["cypress", "node"]
8 | },
9 | "include": ["src/**/*.ts", "src/**/*.js"]
10 | }
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.angular/cache
2 | dist
3 | node_modules
4 | .idea
5 | .vscode
6 | .cache
7 | yarn-error.log
8 | coverage
9 |
10 | migrations.json
11 |
12 | .pnp.*
13 | .yarn/*
14 | !.yarn/patches
15 | !.yarn/plugins
16 | !.yarn/releases
17 | !.yarn/sdks
18 | !.yarn/versions
19 |
20 | .angular
21 |
--------------------------------------------------------------------------------
/apps/demos/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "types": ["jest", "node"]
6 | },
7 | "files": ["src/test-setup.ts"],
8 | "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts", "jest.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/apps/demos/tsconfig.server.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.app.json",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "types": ["node"]
6 | },
7 | "files": ["src/main.server.ts", "src/server.ts"],
8 | "angularCompilerOptions": {
9 | "entryModule": "./src/app/app.server.module#AppServerModule"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/select-snapshot/src/lib/core/decorators/select-snapshot.ts:
--------------------------------------------------------------------------------
1 | import { defineSelectSnapshotProperties } from '../internals/select-snapshot';
2 |
3 | export function SelectSnapshot(selectorOrFeature?: any, ...paths: string[]) {
4 | return (type: any, name: string) => {
5 | defineSelectSnapshotProperties(selectorOrFeature, paths, type, name);
6 | };
7 | }
8 |
--------------------------------------------------------------------------------
/libs/select-snapshot/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "files": ["src/test-setup.ts"],
9 | "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/apps/demos-e2e/cypress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'cypress';
2 |
3 | export default defineConfig({
4 | video: false,
5 | chromeWebSecurity: false,
6 | fileServerFolder: '.',
7 | screenshotOnRunFailure: false,
8 | e2e: {
9 | supportFile: false,
10 | fixturesFolder: false,
11 | specPattern: './src/e2e/**/*.cy.ts'
12 | }
13 | });
14 |
--------------------------------------------------------------------------------
/apps/demos/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Progress is being updated using setInverval which shouldn't cause OnPush views gets updated, but
5 | our progress does
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/apps/demos-e2e/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"],
3 | "ignorePatterns": ["!**/*"],
4 | "rules": {},
5 | "overrides": [
6 | {
7 | "files": ["src/plugins/index.js"],
8 | "rules": {
9 | "@typescript-eslint/no-var-requires": "off",
10 | "no-undef": "off"
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/apps/demos/src/app/app.server.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { ServerModule } from '@angular/platform-server';
3 |
4 | import { AppModule } from './app.module';
5 | import { AppComponent } from './app.component';
6 |
7 | @NgModule({
8 | imports: [AppModule, ServerModule],
9 | bootstrap: [AppComponent]
10 | })
11 | export class AppServerModule {}
12 |
--------------------------------------------------------------------------------
/apps/demos/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["node"],
5 | "module": "esnext",
6 | "target": "es2022",
7 | "useDefineForClassFields": false
8 | },
9 | "files": ["src/main.ts", "src/polyfills.ts"],
10 | "include": ["src/**/*.d.ts"],
11 | "exclude": ["**/*.test.ts", "**/*.spec.ts", "jest.config.ts"]
12 | }
13 |
--------------------------------------------------------------------------------
/apps/demos/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | last 1 Chrome version
9 |
--------------------------------------------------------------------------------
/libs/select-snapshot/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "declaration": true,
6 | "declarationMap": true,
7 | "inlineSources": true,
8 | "types": []
9 | },
10 | "exclude": ["src/test-setup.ts", "src/**/*.spec.ts", "jest.config.ts", "src/**/*.test.ts"],
11 | "include": ["src/**/*.ts"]
12 | }
13 |
--------------------------------------------------------------------------------
/apps/demos/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Angular Universal @ngxs-labs/select-snapshot integration
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/demos/src/main.server.ts:
--------------------------------------------------------------------------------
1 | import '@angular/platform-server/init';
2 |
3 | import { enableProdMode } from '@angular/core';
4 |
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | export { AppServerModule } from './app/app.server.module';
12 | export { renderModule, renderModuleFactory } from '@angular/platform-server';
13 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | afterInstall: yarn ngcc && node ./decorate-angular-cli.js && yarn husky install
2 |
3 | nodeLinker: node-modules
4 |
5 | npmRegistryServer: "https://registry.npmjs.org"
6 |
7 | plugins:
8 | - path: .yarn/plugins/@yarnpkg/plugin-after-install.cjs
9 | spec: "https://raw.githubusercontent.com/mhassan1/yarn-plugin-after-install/v0.3.1/bundles/@yarnpkg/plugin-after-install.js"
10 |
11 | yarnPath: .yarn/releases/yarn-3.2.1.cjs
12 |
--------------------------------------------------------------------------------
/apps/demos/src/app/progress/progress.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 500px;
3 | display: flex;
4 |
5 | .progress-wrapper {
6 | width: 100%;
7 | background-color: #e0e0e0;
8 | padding: 3px;
9 | border-radius: 3px;
10 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
11 |
12 | .progress {
13 | display: block;
14 | height: 22px;
15 | background-color: #659cef;
16 | border-radius: 3px;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/apps/demos/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | document.addEventListener('DOMContentLoaded', async () => {
12 | try {
13 | await platformBrowserDynamic().bootstrapModule(AppModule);
14 | } catch (e) {
15 | console.error(e);
16 | }
17 | });
18 |
--------------------------------------------------------------------------------
/libs/select-snapshot/src/lib/select-snapshot.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, ModuleWithProviders, NgModuleRef } from '@angular/core';
2 |
3 | import { setInjector, clearInjector } from './core/internals/static-injector';
4 |
5 | @NgModule()
6 | export class NgxsSelectSnapshotModule {
7 | constructor(ngModuleRef: NgModuleRef) {
8 | setInjector(ngModuleRef.injector);
9 | ngModuleRef.onDestroy(clearInjector);
10 | }
11 |
12 | static forRoot(): ModuleWithProviders {
13 | return {
14 | ngModule: NgxsSelectSnapshotModule,
15 | };
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/libs/select-snapshot/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ngxs-labs/select-snapshot",
3 | "version": "5.0.0",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/ngxs-labs/select-snapshot.git"
7 | },
8 | "license": "MIT",
9 | "homepage": "https://github.com/ngxs-labs/select-snapshot#readme",
10 | "bugs": {
11 | "url": "https://github.com/ngxs-labs/select-snapshot/issues"
12 | },
13 | "keywords": [
14 | "ngxs",
15 | "redux",
16 | "store"
17 | ],
18 | "sideEffects": false,
19 | "peerDependencies": {
20 | "@angular/core": ">=15.0.0",
21 | "@ngxs/store": ">=3.7.3",
22 | "rxjs": ">=6.5.2"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/apps/demos/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { NgxsModule } from '@ngxs/store';
4 | import { NgxsSelectSnapshotModule } from '@ngxs-labs/select-snapshot';
5 |
6 | import { ProgressState } from './store';
7 |
8 | import { AppComponent } from './app.component';
9 |
10 | @NgModule({
11 | imports: [
12 | BrowserModule.withServerTransition({ appId: 'universal-select-snapshot' }),
13 | NgxsModule.forRoot([ProgressState]),
14 | NgxsSelectSnapshotModule.forRoot(),
15 | ],
16 | declarations: [AppComponent],
17 | bootstrap: [AppComponent],
18 | })
19 | export class AppModule {}
20 |
--------------------------------------------------------------------------------
/apps/demos-e2e/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demos-e2e",
3 | "$schema": "../../node_modules/nx/schemas/project-schema.json",
4 | "projectType": "application",
5 | "sourceRoot": "apps/demos-e2e/src",
6 | "targets": {
7 | "e2e": {
8 | "executor": "@nrwl/cypress:cypress",
9 | "options": {
10 | "cypressConfig": "apps/demos-e2e/cypress.config.ts",
11 | "tsConfig": "apps/demos-e2e/tsconfig.e2e.json",
12 | "devServerTarget": "demos:serve-ssr:production"
13 | }
14 | },
15 | "lint": {
16 | "executor": "@nrwl/linter:eslint",
17 | "options": {
18 | "lintFilePatterns": ["apps/demos-e2e/**/*.{js,ts}"]
19 | }
20 | }
21 | },
22 | "implicitDependencies": ["demos"]
23 | }
24 |
--------------------------------------------------------------------------------
/libs/select-snapshot/jest.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | export default {
3 | displayName: 'select-snapshot',
4 | preset: '../../jest.preset.js',
5 | setupFilesAfterEnv: ['/src/test-setup.ts'],
6 | transform: {
7 | '^.+\\.(ts|mjs|js|html)$': [
8 | 'jest-preset-angular',
9 | {
10 | tsconfig: '/tsconfig.spec.json',
11 | stringifyContentPathRegex: '\\.(html|svg)$',
12 | },
13 | ],
14 | },
15 | transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
16 | snapshotSerializers: [
17 | 'jest-preset-angular/build/serializers/no-ng-attributes',
18 | 'jest-preset-angular/build/serializers/ng-snapshot',
19 | 'jest-preset-angular/build/serializers/html-comment',
20 | ],
21 | };
22 |
--------------------------------------------------------------------------------
/apps/demos/jest.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | export default {
3 | displayName: 'demos',
4 | preset: '../../jest.preset.js',
5 | setupFilesAfterEnv: ['/src/test-setup.ts'],
6 | transform: {
7 | '^.+\\.(ts|mjs|js|html)$': [
8 | 'jest-preset-angular',
9 | {
10 | isolatedModules: true,
11 | tsconfig: '/tsconfig.spec.json',
12 | stringifyContentPathRegex: '\\.(html|svg)$'
13 | }
14 | ]
15 | },
16 | transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
17 | snapshotSerializers: [
18 | 'jest-preset-angular/build/serializers/no-ng-attributes',
19 | 'jest-preset-angular/build/serializers/ng-snapshot',
20 | 'jest-preset-angular/build/serializers/html-comment'
21 | ]
22 | };
23 |
--------------------------------------------------------------------------------
/apps/demos/src/app/progress/progress.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ChangeDetectionStrategy, HostBinding, OnDestroy } from '@angular/core';
2 | import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot';
3 |
4 | import { ProgressState } from '../store';
5 |
6 | @Component({
7 | selector: 'app-progress',
8 | templateUrl: './progress.component.html',
9 | styleUrls: ['./progress.component.scss'],
10 | changeDetection: ChangeDetectionStrategy.OnPush,
11 | standalone: true,
12 | })
13 | export class ProgressComponent implements OnDestroy {
14 | @HostBinding('class.ivy-enabled') ivyEnabled!: boolean;
15 |
16 | @ViewSelectSnapshot(ProgressState.getProgress) progress!: number;
17 |
18 | ngOnDestroy(): void {
19 | console.log('Just ensuring that this hook is still called.');
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "noImplicitOverride": true,
11 | "importHelpers": true,
12 | "target": "es2015",
13 | "module": "esnext",
14 | "typeRoots": ["node_modules/@types", "typings"],
15 | "lib": ["es2020", "dom", "dom.iterable"],
16 | "skipLibCheck": true,
17 | "skipDefaultLibCheck": true,
18 | "baseUrl": ".",
19 | "paths": {
20 | "@ngxs-labs/select-snapshot": ["libs/select-snapshot/src/index.ts"]
21 | }
22 | },
23 | "angularCompilerOptions": {
24 | "strictTemplates": true
25 | },
26 | "exclude": ["node_modules", "tmp"]
27 | }
28 |
--------------------------------------------------------------------------------
/libs/select-snapshot/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "useDefineForClassFields": false,
5 | "forceConsistentCasingInFileNames": true,
6 | "strict": true,
7 | "noImplicitOverride": true,
8 | "noPropertyAccessFromIndexSignature": true,
9 | "noImplicitReturns": true,
10 | "noFallthroughCasesInSwitch": true
11 | },
12 | "files": [],
13 | "include": [],
14 | "references": [
15 | {
16 | "path": "./tsconfig.lib.json"
17 | },
18 | {
19 | "path": "./tsconfig.spec.json"
20 | }
21 | ],
22 | "extends": "../../tsconfig.base.json",
23 | "angularCompilerOptions": {
24 | "enableI18nLegacyMessageIdFormat": false,
25 | "strictInjectionParameters": true,
26 | "strictInputAccessModifiers": true,
27 | "strictTemplates": true
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/apps/demos-e2e/src/e2e/ssr.cy.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | describe('Server side rendering', () => {
4 | const indexUrl = '/';
5 |
6 | it('should render `app-progress` component', () => {
7 | cy.visit(indexUrl).get('.progress-wrapper').should('be.visible');
8 | });
9 |
10 | it('should click the button and the progress should be updated', () => {
11 | let resolve: VoidFunction;
12 |
13 | const progressCompleted = new Promise(r => (resolve = r));
14 |
15 | cy.visit(indexUrl)
16 | .then(window => {
17 | window.addEventListener('progressCompleted', () => {
18 | resolve();
19 | });
20 | })
21 | .get('[data-cy=start-progress]')
22 | .click()
23 | .then(() => progressCompleted);
24 |
25 | cy.get('.progress').should('have.attr', 'style', 'width: 100%;');
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/apps/demos/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "files": [],
4 | "include": [],
5 | "references": [
6 | {
7 | "path": "./tsconfig.app.json"
8 | },
9 | {
10 | "path": "./tsconfig.spec.json"
11 | },
12 | {
13 | "path": "./tsconfig.editor.json"
14 | },
15 | {
16 | "path": "./tsconfig.server.json"
17 | }
18 | ],
19 | "compilerOptions": {
20 | "forceConsistentCasingInFileNames": true,
21 | "strict": true,
22 | "noImplicitOverride": true,
23 | "noPropertyAccessFromIndexSignature": false,
24 | "noImplicitReturns": true,
25 | "noFallthroughCasesInSwitch": true,
26 | "target": "es2020"
27 | },
28 | "angularCompilerOptions": {
29 | "strictInjectionParameters": true,
30 | "strictInputAccessModifiers": true,
31 | "strictTemplates": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/apps/demos/src/app/store/progress/progress.state.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { State, Action, StateContext, Selector } from '@ngxs/store';
3 |
4 | import { IncrementProgress } from './progress.actions';
5 |
6 | export interface ProgressStateModel {
7 | progress: number;
8 | }
9 |
10 | @Injectable()
11 | @State({
12 | name: 'progress',
13 | defaults: {
14 | progress: 0,
15 | },
16 | })
17 | export class ProgressState {
18 | @Selector()
19 | static getProgress(state: ProgressStateModel): number {
20 | return state.progress;
21 | }
22 |
23 | @Action(IncrementProgress)
24 | incrementProgress(ctx: StateContext) {
25 | const state = ctx.getState();
26 |
27 | if (state.progress !== 100) {
28 | state.progress += 1;
29 | ctx.setState({ ...state });
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/libs/select-snapshot/src/lib/core/internals/internals.ts:
--------------------------------------------------------------------------------
1 | const DOLLAR_CHAR_CODE = 36;
2 |
3 | export function removeDollarAtTheEnd(name: string | symbol): string {
4 | if (typeof name !== 'string') {
5 | name = name.toString();
6 | }
7 | const lastCharIndex = name.length - 1;
8 | const dollarAtTheEnd = name.charCodeAt(lastCharIndex) === DOLLAR_CHAR_CODE;
9 | return dollarAtTheEnd ? name.slice(0, lastCharIndex) : name;
10 | }
11 |
12 | export function getPropsArray(selectorOrFeature: string, paths: string[]): string[] {
13 | if (paths.length) {
14 | return [selectorOrFeature, ...paths];
15 | }
16 | return selectorOrFeature.split('.');
17 | }
18 |
19 | export function compliantPropGetter(paths: string[]): (x: any) => any {
20 | const copyOfPaths = [...paths];
21 | return obj => copyOfPaths.reduce((acc: any, part: string) => acc && acc[part], obj);
22 | }
23 |
24 | export const META_KEY = 'NGXS_META';
25 |
--------------------------------------------------------------------------------
/apps/demos/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../../.eslintrc.json"],
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "extends": [
8 | "plugin:@nrwl/nx/angular",
9 | "plugin:@angular-eslint/template/process-inline-templates"
10 | ],
11 | "rules": {
12 | "@angular-eslint/directive-selector": [
13 | "error",
14 | {
15 | "type": "attribute",
16 | "prefix": "app",
17 | "style": "camelCase"
18 | }
19 | ],
20 | "@angular-eslint/component-selector": [
21 | "error",
22 | {
23 | "type": "element",
24 | "prefix": "app",
25 | "style": "kebab-case"
26 | }
27 | ]
28 | }
29 | },
30 | {
31 | "files": ["*.html"],
32 | "extends": ["plugin:@nrwl/nx/angular-template"],
33 | "rules": {}
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/jest.preset.js:
--------------------------------------------------------------------------------
1 | const nxPreset = require('@nrwl/jest/preset').default;
2 |
3 | module.exports = {
4 | ...nxPreset,
5 | testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'],
6 | transform: {
7 | '^.+\\.(ts|js|html)$': 'ts-jest'
8 | },
9 | resolver: '@nrwl/jest/plugins/resolver',
10 | moduleFileExtensions: ['ts', 'js', 'html'],
11 | coverageReporters: ['html', 'lcov'],
12 | /* TODO: Update to latest Jest snapshotFormat
13 | * By default Nx has kept the older style of Jest Snapshot formats
14 | * to prevent breaking of any existing tests with snapshots.
15 | * It's recommend you update to the latest format.
16 | * You can do this by removing snapshotFormat property
17 | * and running tests with --update-snapshot flag.
18 | * Example: "nx affected --targets=test --update-snapshot"
19 | * More info: https://jestjs.io/docs/upgrading-to-jest29#snapshot-format
20 | */
21 | snapshotFormat: { escapeString: true, printBasicPrototype: true }
22 | };
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
2 |
3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
4 |
5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
6 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": ["**/*"],
4 | "plugins": ["@nrwl/nx"],
5 | "overrides": [
6 | {
7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
8 | "rules": {
9 | "@nrwl/nx/enforce-module-boundaries": [
10 | "error",
11 | {
12 | "enforceBuildableLibDependency": true,
13 | "allow": [],
14 | "depConstraints": [
15 | {
16 | "sourceTag": "*",
17 | "onlyDependOnLibsWithTags": ["*"]
18 | }
19 | ]
20 | }
21 | ]
22 | }
23 | },
24 | {
25 | "files": ["*.ts", "*.tsx"],
26 | "extends": ["plugin:@nrwl/nx/typescript"],
27 | "rules": {}
28 | },
29 | {
30 | "files": ["*.js", "*.jsx"],
31 | "extends": ["plugin:@nrwl/nx/javascript"],
32 | "rules": {}
33 | },
34 | {
35 | "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"],
36 | "env": {
37 | "jest": true
38 | },
39 | "rules": {}
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/libs/select-snapshot/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../../.eslintrc.json"],
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "rules": {
8 | "@angular-eslint/directive-selector": [
9 | "error",
10 | {
11 | "type": "attribute",
12 | "prefix": "selectSnapshot",
13 | "style": "camelCase"
14 | }
15 | ],
16 | "@angular-eslint/component-selector": [
17 | "error",
18 | {
19 | "type": "element",
20 | "prefix": "select-snapshot",
21 | "style": "kebab-case"
22 | }
23 | ],
24 | "@typescript-eslint/ban-types": "off",
25 | "@typescript-eslint/no-non-null-assertion": "off"
26 | },
27 | "extends": [
28 | "plugin:@nrwl/nx/angular",
29 | "plugin:@angular-eslint/template/process-inline-templates"
30 | ]
31 | },
32 | {
33 | "files": ["*.html"],
34 | "extends": ["plugin:@nrwl/nx/angular-template"],
35 | "rules": {}
36 | }
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/libs/select-snapshot/src/lib/core/internals/static-injector.ts:
--------------------------------------------------------------------------------
1 | import { Injector } from '@angular/core';
2 | import { Store } from '@ngxs/store';
3 |
4 | class NgxsSelectSnapshotModuleIsNotImported extends Error {
5 | constructor() {
6 | super(`You've forgotten to import "NgxsSelectSnapshotModule"!`);
7 | }
8 | }
9 |
10 | let injector: Injector | null = null;
11 | let store: Store | null = null;
12 |
13 | function assertDefined(actual: T | null | undefined): asserts actual is T {
14 | if (actual == null) {
15 | throw new NgxsSelectSnapshotModuleIsNotImported();
16 | }
17 | }
18 |
19 | export function setInjector(parentInjector: Injector): void {
20 | injector = parentInjector;
21 | }
22 |
23 | /**
24 | * Ensure that we don't keep any references in case of the bootstrapped
25 | * module is destroyed via `NgModuleRef.destroy()`.
26 | */
27 | export function clearInjector(): void {
28 | injector = null;
29 | store = null;
30 | }
31 |
32 | export function getStore(): never | Store {
33 | assertDefined(injector);
34 | store = store || injector!.get(Store);
35 | return store;
36 | }
37 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## PR Checklist
2 | Please check if your PR fulfills the following requirements:
3 |
4 | - [ ] The commit message follows our guidelines: https://github.com/ngxs/store/blob/master/CONTRIBUTING.md#commit
5 | - [ ] Tests for the changes have been added (for bug fixes / features)
6 | - [ ] Docs have been added / updated (for bug fixes / features)
7 |
8 | ## PR Type
9 | What kind of change does this PR introduce?
10 |
11 |
12 | ```
13 | [ ] Bugfix
14 | [ ] Feature
15 | [ ] Code style update (formatting, local variables)
16 | [ ] Refactoring (no functional changes, no api changes)
17 | [ ] Build related changes
18 | [ ] CI related changes
19 | [ ] Documentation content changes
20 | [ ] Other... Please describe:
21 | ```
22 |
23 | ## What is the current behavior?
24 |
25 |
26 | Issue Number: N/A
27 |
28 |
29 | ## What is the new behavior?
30 |
31 |
32 | ## Does this PR introduce a breaking change?
33 | ```
34 | [ ] Yes
35 | [ ] No
36 | ```
37 |
38 |
39 |
40 |
41 | ## Other information
42 |
--------------------------------------------------------------------------------
/.github/workflows/select-snapshot.yml:
--------------------------------------------------------------------------------
1 | name: '@ngxs-labs/select-snapshot'
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | fail-fast: true
14 |
15 | steps:
16 | - uses: actions/checkout@v3
17 | with:
18 | fetch-depth: 0
19 |
20 | - run: git fetch --no-tags --prune --depth 2 origin master
21 |
22 | - uses: actions/cache@v3
23 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
24 | with:
25 | path: ~/.cache # Default cache directory for both Yarn and Cypress
26 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
27 | restore-keys: |
28 | ${{ runner.os }}-yarn-
29 |
30 | - uses: actions/setup-node@v3
31 | with:
32 | node-version: 16.13.0
33 | registry-url: 'https://registry.npmjs.org'
34 |
35 | - name: Install dependencies
36 | run: yarn --immutable
37 |
38 | - run: yarn nx affected:lint --parallel --base=origin/master
39 | - run: yarn nx affected:test --parallel --base=origin/master --configuration ci
40 | - run: yarn nx affected:build --base=origin/master
41 | - run: yarn nx affected:e2e --base=origin/master
42 | env:
43 | ELECTRON_EXTRA_LAUNCH_ARGS: '--disable-gpu'
44 |
--------------------------------------------------------------------------------
/libs/select-snapshot/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "select-snapshot",
3 | "$schema": "../../node_modules/nx/schemas/project-schema.json",
4 | "projectType": "library",
5 | "sourceRoot": "libs/select-snapshot/src",
6 | "prefix": "select-snapshot",
7 | "targets": {
8 | "build": {
9 | "executor": "@nrwl/angular:package",
10 | "outputs": ["{workspaceRoot}/dist/{projectRoot}"],
11 | "options": {
12 | "project": "libs/select-snapshot/ng-package.json"
13 | },
14 | "configurations": {
15 | "production": {
16 | "tsConfig": "libs/select-snapshot/tsconfig.lib.prod.json"
17 | },
18 | "development": {
19 | "tsConfig": "libs/select-snapshot/tsconfig.lib.json"
20 | }
21 | },
22 | "defaultConfiguration": "production"
23 | },
24 | "test": {
25 | "executor": "@nrwl/jest:jest",
26 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
27 | "options": {
28 | "jestConfig": "libs/select-snapshot/jest.config.ts",
29 | "passWithNoTests": true
30 | },
31 | "configurations": {
32 | "ci": {
33 | "ci": true,
34 | "codeCoverage": true
35 | }
36 | }
37 | },
38 | "lint": {
39 | "executor": "@nrwl/linter:eslint",
40 | "outputs": ["{options.outputFile}"],
41 | "options": {
42 | "lintFilePatterns": ["libs/select-snapshot/**/*.ts", "libs/select-snapshot/**/*.html"]
43 | }
44 | }
45 | },
46 | "tags": ["lib"]
47 | }
48 |
--------------------------------------------------------------------------------
/nx.json:
--------------------------------------------------------------------------------
1 | {
2 | "affected": {
3 | "defaultBase": "master"
4 | },
5 | "npmScope": "select-snapshot",
6 | "tasksRunnerOptions": {
7 | "default": {
8 | "runner": "nx/tasks-runners/default",
9 | "options": {
10 | "cacheableOperations": ["build", "lint", "test", "e2e"],
11 | "parallel": 1
12 | }
13 | }
14 | },
15 | "defaultProject": "select-snapshot",
16 | "generators": {
17 | "@nrwl/angular:application": {
18 | "style": "css",
19 | "linter": "eslint",
20 | "unitTestRunner": "jest",
21 | "e2eTestRunner": "none"
22 | },
23 | "@nrwl/angular:library": {
24 | "linter": "eslint",
25 | "unitTestRunner": "jest"
26 | },
27 | "@nrwl/angular:component": {
28 | "style": "css"
29 | }
30 | },
31 | "$schema": "./node_modules/nx/schemas/nx-schema.json",
32 | "targetDefaults": {
33 | "build": {
34 | "dependsOn": ["^build"],
35 | "inputs": ["production", "^production"]
36 | },
37 | "e2e": {
38 | "inputs": ["default", "^production"]
39 | },
40 | "test": {
41 | "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"]
42 | },
43 | "lint": {
44 | "inputs": ["default", "{workspaceRoot}/.eslintrc.json"]
45 | }
46 | },
47 | "namedInputs": {
48 | "default": ["{projectRoot}/**/*", "sharedGlobals"],
49 | "sharedGlobals": ["{workspaceRoot}/tsconfig.base.json", "{workspaceRoot}/nx.json"],
50 | "production": [
51 | "default",
52 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
53 | "!{projectRoot}/tsconfig.spec.json",
54 | "!{projectRoot}/jest.config.[jt]s",
55 | "!{projectRoot}/.eslintrc.json"
56 | ]
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/apps/demos/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Component,
3 | ɵivyEnabled,
4 | ChangeDetectionStrategy,
5 | NgModuleRef,
6 | OnInit,
7 | ViewChild,
8 | ViewContainerRef,
9 | } from '@angular/core';
10 | import { Store } from '@ngxs/store';
11 | import { SelectSnapshot } from '@ngxs-labs/select-snapshot';
12 |
13 | import { ProgressState, IncrementProgress } from './store';
14 |
15 | @Component({
16 | selector: 'app-root',
17 | templateUrl: './app.component.html',
18 | styleUrls: ['./app.component.scss'],
19 | changeDetection: ChangeDetectionStrategy.OnPush,
20 | })
21 | export class AppComponent implements OnInit {
22 | @ViewChild('progressContainer', {
23 | static: true,
24 | read: ViewContainerRef,
25 | })
26 | progressContainer!: ViewContainerRef;
27 |
28 | @SelectSnapshot(ProgressState.getProgress) progress!: number;
29 |
30 | constructor(private ngModuleRef: NgModuleRef, private store: Store) {}
31 |
32 | ngOnInit(): void {
33 | import(/* webpackChunkName: 'progress' */ './progress/progress.component').then(m => {
34 | const ref = this.progressContainer.createComponent(m.ProgressComponent);
35 | ref.instance.ivyEnabled = ɵivyEnabled;
36 | ref.changeDetectorRef.detectChanges();
37 | });
38 | }
39 |
40 | startProgress(): void {
41 | const intervalId = setInterval(() => {
42 | if (this.progress === 100) {
43 | clearInterval(intervalId);
44 | // Just for testing purposes.
45 | window.dispatchEvent(new CustomEvent('progressCompleted'));
46 | } else {
47 | this.store.dispatch(new IncrementProgress());
48 | }
49 | }, 5);
50 | }
51 |
52 | destroy(): void {
53 | this.ngModuleRef.destroy();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/.yarn/plugins/@yarnpkg/plugin-after-install.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | //prettier-ignore
3 | module.exports = {
4 | name: "@yarnpkg/plugin-after-install",
5 | factory: function (require) {
6 | var plugin=(()=>{var g=Object.create,r=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var k=Object.getPrototypeOf,y=Object.prototype.hasOwnProperty;var I=t=>r(t,"__esModule",{value:!0});var i=t=>{if(typeof require!="undefined")return require(t);throw new Error('Dynamic require of "'+t+'" is not supported')};var h=(t,o)=>{for(var e in o)r(t,e,{get:o[e],enumerable:!0})},w=(t,o,e)=>{if(o&&typeof o=="object"||typeof o=="function")for(let n of C(o))!y.call(t,n)&&n!=="default"&&r(t,n,{get:()=>o[n],enumerable:!(e=x(o,n))||e.enumerable});return t},a=t=>w(I(r(t!=null?g(k(t)):{},"default",t&&t.__esModule&&"default"in t?{get:()=>t.default,enumerable:!0}:{value:t,enumerable:!0})),t);var j={};h(j,{default:()=>b});var c=a(i("@yarnpkg/core")),m={afterInstall:{description:"Hook that will always run after install",type:c.SettingsType.STRING,default:""}};var u=a(i("clipanion")),d=a(i("@yarnpkg/core"));var p=a(i("@yarnpkg/shell")),l=async(t,o)=>{var f;let e=t.get("afterInstall"),n=!!((f=t.projectCwd)==null?void 0:f.endsWith(`dlx-${process.pid}`));return e&&!n?(o&&console.log("Running `afterInstall` hook..."),(0,p.execute)(e,[],{cwd:t.projectCwd||void 0})):0};var s=class extends u.Command{async execute(){let o=await d.Configuration.find(this.context.cwd,this.context.plugins);return l(o,!1)}};s.paths=[["after-install"]];var P={configuration:m,commands:[s],hooks:{afterAllInstalled:async t=>{if(await l(t.configuration,!0))throw new Error("The `afterInstall` hook failed, see output above.")}}},b=P;return j;})();
7 | return plugin;
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/apps/demos/src/server.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js/node';
2 |
3 | import { join } from 'path';
4 | import * as express from 'express';
5 |
6 | import { APP_BASE_HREF } from '@angular/common';
7 | import { ngExpressEngine } from '@nguniversal/express-engine';
8 |
9 | import { AppServerModule } from './main.server';
10 |
11 | export function app(): express.Express {
12 | const server = express();
13 | const distFolder = join(process.cwd(), 'dist/apps/demos/browser');
14 |
15 | server.engine(
16 | 'html',
17 | ngExpressEngine({
18 | bootstrap: AppServerModule
19 | })
20 | );
21 |
22 | server.set('view engine', 'html');
23 | server.set('views', distFolder);
24 |
25 | server.get(
26 | '*.*',
27 | express.static(distFolder, {
28 | maxAge: '1y'
29 | })
30 | );
31 |
32 | server.get('*', (req, res) => {
33 | res.render('index', { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
34 | });
35 |
36 | return server;
37 | }
38 |
39 | function run(): void {
40 | const port = process.env.PORT || 4200;
41 |
42 | const server = app();
43 | server.listen(port, () => {
44 | console.log(`Node Express server listening on http://localhost:${port}`);
45 | });
46 | }
47 |
48 | // Webpack will replace 'require' with '__webpack_require__'
49 | // '__non_webpack_require__' is a proxy to Node 'require'
50 | // The below code is to ensure that the server is run only when not requiring the bundle.
51 | declare const __non_webpack_require__: NodeRequire;
52 | const mainModule = __non_webpack_require__.main;
53 | const moduleFilename = (mainModule && mainModule.filename) || '';
54 | if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
55 | run();
56 | }
57 |
58 | export * from './main.server';
59 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | ## I'm submitting a...
8 |
9 |
10 | [ ] Regression (a behavior that used to work and stopped working in a new release)
11 | [ ] Bug report
12 | [ ] Performance issue
13 | [ ] Feature request
14 | [ ] Documentation issue or request
15 | [ ] Support request => https://github.com/ngxs/store/blob/master/CONTRIBUTING.md
16 | [ ] Other... Please describe:
17 |
18 |
19 | ## Current behavior
20 |
21 |
22 |
23 | ## Expected behavior
24 |
25 |
26 |
27 | ## Minimal reproduction of the problem with instructions
28 |
29 |
38 |
39 | ## What is the motivation / use case for changing the behavior?
40 |
41 |
42 |
43 | ## Environment
44 |
45 |
46 | Libs:
47 | - @angular/core version: X.Y.Z
48 | - @ngxs/store version: X.Y.Z
49 |
50 |
51 | Browser:
52 | - [ ] Chrome (desktop) version XX
53 | - [ ] Chrome (Android) version XX
54 | - [ ] Chrome (iOS) version XX
55 | - [ ] Firefox version XX
56 | - [ ] Safari (desktop) version XX
57 | - [ ] Safari (iOS) version XX
58 | - [ ] IE version XX
59 | - [ ] Edge version XX
60 |
61 | For Tooling issues:
62 | - Node version: XX
63 | - Platform:
64 |
65 | Others:
66 |
67 |
68 |
--------------------------------------------------------------------------------
/libs/select-snapshot/src/lib/core/internals/select-snapshot.ts:
--------------------------------------------------------------------------------
1 | import { Store } from '@ngxs/store';
2 |
3 | import { getStore } from './static-injector';
4 | import { removeDollarAtTheEnd, getPropsArray, compliantPropGetter, META_KEY } from './internals';
5 |
6 | type CreateSelectorFactory = (selectorOrFeature: any) => any;
7 |
8 | export function createSelectorFactory(paths: string[]): CreateSelectorFactory {
9 | return (selectorOrFeature: any) => {
10 | if (typeof selectorOrFeature === 'string') {
11 | const propsArray = getPropsArray(selectorOrFeature, paths);
12 | return compliantPropGetter(propsArray);
13 | } else if (selectorOrFeature[META_KEY] && selectorOrFeature[META_KEY].path) {
14 | return compliantPropGetter(selectorOrFeature[META_KEY].path.split('.'));
15 | }
16 |
17 | return selectorOrFeature;
18 | };
19 | }
20 |
21 | export function getSelectorFromInstance(
22 | instance: any,
23 | selectorFnName: string,
24 | createSelector: CreateSelectorFactory,
25 | selectorOrFeature: any,
26 | ) {
27 | return instance[selectorFnName] || (instance[selectorFnName] = createSelector(selectorOrFeature));
28 | }
29 |
30 | export function defineSelectSnapshotProperties(
31 | selectorOrFeature: any,
32 | paths: string[],
33 | target: Object,
34 | name: string | symbol,
35 | store?: Store,
36 | ) {
37 | const selectorFnName = `__${name.toString()}__selector`;
38 | const createSelector = createSelectorFactory(paths);
39 |
40 | Object.defineProperties(target, {
41 | [selectorFnName]: {
42 | writable: true,
43 | enumerable: false,
44 | configurable: true,
45 | },
46 | [name]: {
47 | get() {
48 | const selector = getSelectorFromInstance(
49 | this,
50 | selectorFnName,
51 | createSelector,
52 | selectorOrFeature,
53 | );
54 | // Don't use the `directiveInject` here as it works ONLY
55 | // during view creation.
56 | store = getStore();
57 | return store.selectSnapshot(selector);
58 | },
59 | enumerable: true,
60 | configurable: true,
61 | },
62 | });
63 |
64 | return {
65 | selectorFnName,
66 | createSelector,
67 | selectorOrFeature: selectorOrFeature || removeDollarAtTheEnd(name),
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "select-snapshot",
3 | "version": "0.0.0",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/ngxs-labs/select-snapshot.git"
7 | },
8 | "license": "MIT",
9 | "homepage": "https://github.com/ngxs-labs/select-snapshot#readme",
10 | "bugs": {
11 | "url": "https://github.com/ngxs-labs/select-snapshot/issues"
12 | },
13 | "keywords": [
14 | "ngxs",
15 | "redux",
16 | "store"
17 | ],
18 | "scripts": {
19 | "prebuild": "nx build select-snapshot --skip-nx-cache",
20 | "build": "yarn prebuild && yarn postbuild",
21 | "postbuild": "cpx README.md dist/libs/select-snapshot",
22 | "test": "nx test select-snapshot",
23 | "e2e": "nx e2e demos-e2e"
24 | },
25 | "private": true,
26 | "devDependencies": {
27 | "@angular-devkit/build-angular": "15.1.1",
28 | "@angular-devkit/core": "15.1.1",
29 | "@angular-devkit/schematics": "15.1.1",
30 | "@angular-eslint/eslint-plugin": "15.0.0",
31 | "@angular-eslint/eslint-plugin-template": "15.0.0",
32 | "@angular-eslint/template-parser": "15.0.0",
33 | "@angular/animations": "15.1.0",
34 | "@angular/cli": "15.1.1",
35 | "@angular/common": "15.1.0",
36 | "@angular/compiler": "15.1.0",
37 | "@angular/compiler-cli": "15.1.0",
38 | "@angular/core": "15.1.0",
39 | "@angular/forms": "15.1.0",
40 | "@angular/language-service": "15.1.0",
41 | "@angular/platform-browser": "15.1.0",
42 | "@angular/platform-browser-dynamic": "15.1.0",
43 | "@angular/platform-server": "15.1.0",
44 | "@angular/router": "15.1.0",
45 | "@commitlint/cli": "^17.5.0",
46 | "@commitlint/config-conventional": "^17.4.4",
47 | "@nguniversal/builders": "15.1.0",
48 | "@nguniversal/express-engine": "15.1.0",
49 | "@ngxs/store": "3.7.6",
50 | "@nrwl/angular": "15.8.9",
51 | "@nrwl/cli": "15.8.9",
52 | "@nrwl/cypress": "15.8.9",
53 | "@nrwl/eslint-plugin-nx": "15.8.9",
54 | "@nrwl/jest": "15.8.9",
55 | "@nrwl/js": "15.8.9",
56 | "@nrwl/linter": "15.8.9",
57 | "@nrwl/workspace": "15.8.9",
58 | "@schematics/angular": "15.1.1",
59 | "@types/express": "^4.17.2",
60 | "@types/jest": "29.4.4",
61 | "@types/node": "^16.11.7",
62 | "@typescript-eslint/eslint-plugin": "5.43.0",
63 | "@typescript-eslint/parser": "5.43.0",
64 | "cpx": "^1.5.0",
65 | "cypress": "^10.0.0",
66 | "eslint": "8.15.0",
67 | "eslint-config-prettier": "^8.3.0",
68 | "eslint-plugin-cypress": "^2.12.1",
69 | "express": "^4.17.1",
70 | "husky": "^8.0.0",
71 | "jest": "29.4.3",
72 | "jest-environment-jsdom": "29.4.3",
73 | "jest-preset-angular": "13.0.0",
74 | "lint-staged": "^13.0.0",
75 | "ng-packagr": "~15.2.2",
76 | "nx": "15.8.9",
77 | "postcss": "8.4.16",
78 | "postcss-import": "14.1.0",
79 | "postcss-preset-env": "7.5.0",
80 | "postcss-url": "10.1.3",
81 | "prettier": "2.7.1",
82 | "rxjs": "^7.5.4",
83 | "ts-jest": "29.0.5",
84 | "ts-node": "10.9.1",
85 | "tslib": "^2.3.0",
86 | "typescript": "~4.9.5",
87 | "zone.js": "0.12.0"
88 | },
89 | "lint-staged": {
90 | "*.{js,html,scss,ts,md}": [
91 | "prettier --write"
92 | ]
93 | },
94 | "prettier": {
95 | "semi": true,
96 | "endOfLine": "lf",
97 | "tabWidth": 2,
98 | "printWidth": 100,
99 | "trailingComma": "all",
100 | "bracketSpacing": true,
101 | "arrowParens": "avoid",
102 | "singleQuote": true
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/decorate-angular-cli.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching
3 | * and faster execution of tasks.
4 | *
5 | * It does this by:
6 | *
7 | * - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command.
8 | * - Symlinking the ng to nx command, so all commands run through the Nx CLI
9 | * - Updating the package.json postinstall script to give you control over this script
10 | *
11 | * The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it.
12 | * Every command you run should work the same when using the Nx CLI, except faster.
13 | *
14 | * Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case,
15 | * will point to nx, which will perform optimizations before invoking ng. So the Angular CLI is always invoked.
16 | * The Nx CLI simply does some optimizations before invoking the Angular CLI.
17 | *
18 | * To opt out of this patch:
19 | * - Replace occurrences of nx with ng in your package.json
20 | * - Remove the script from your postinstall script in your package.json
21 | * - Delete and reinstall your node_modules
22 | */
23 |
24 | const fs = require('fs');
25 | const os = require('os');
26 | const cp = require('child_process');
27 | const isWindows = os.platform() === 'win32';
28 | let output;
29 | try {
30 | output = require('@nrwl/workspace').output;
31 | } catch (e) {
32 | console.warn(
33 | 'Angular CLI could not be decorated to enable computation caching. Please ensure @nrwl/workspace is installed.'
34 | );
35 | process.exit(0);
36 | }
37 |
38 | /**
39 | * Paths to files being patched
40 | */
41 | const angularCLIInitPath = 'node_modules/@angular/cli/lib/cli/index.js';
42 |
43 | /**
44 | * Patch index.js to warn you if you invoke the undecorated Angular CLI.
45 | */
46 | function patchAngularCLI(initPath) {
47 | const angularCLIInit = fs.readFileSync(initPath, 'utf-8').toString();
48 |
49 | if (!angularCLIInit.includes('NX_CLI_SET')) {
50 | fs.writeFileSync(
51 | initPath,
52 | `
53 | if (!process.env['NX_CLI_SET']) {
54 | const { output } = require('@nrwl/workspace');
55 | output.warn({ title: 'The Angular CLI was invoked instead of the Nx CLI. Use "npx ng [command]" or "nx [command]" instead.' });
56 | }
57 | ${angularCLIInit}
58 | `
59 | );
60 | }
61 | }
62 |
63 | /**
64 | * Symlink of ng to nx, so you can keep using `ng build/test/lint` and still
65 | * invoke the Nx CLI and get the benefits of computation caching.
66 | */
67 | function symlinkNgCLItoNxCLI() {
68 | try {
69 | const ngPath = './node_modules/.bin/ng';
70 | const nxPath = './node_modules/.bin/nx';
71 | if (isWindows) {
72 | /**
73 | * This is the most reliable way to create symlink-like behavior on Windows.
74 | * Such that it works in all shells and works with npx.
75 | */
76 | ['', '.cmd', '.ps1'].forEach(ext => {
77 | if (fs.existsSync(nxPath + ext))
78 | fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext));
79 | });
80 | } else {
81 | // If unix-based, symlink
82 | cp.execSync(`ln -sf ./nx ${ngPath}`);
83 | }
84 | } catch (e) {
85 | output.error({
86 | title: 'Unable to create a symlink from the Angular CLI to the Nx CLI:' + e.message
87 | });
88 | throw e;
89 | }
90 | }
91 |
92 | try {
93 | symlinkNgCLItoNxCLI();
94 | patchAngularCLI(angularCLIInitPath);
95 | output.log({
96 | title: 'Angular CLI has been decorated to enable computation caching.'
97 | });
98 | } catch (e) {
99 | output.error({
100 | title: 'Decoration of the Angular CLI did not complete successfully'
101 | });
102 | }
103 |
--------------------------------------------------------------------------------
/apps/demos/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demos",
3 | "$schema": "../../node_modules/nx/schemas/project-schema.json",
4 | "projectType": "application",
5 | "sourceRoot": "apps/demos/src",
6 | "prefix": "app",
7 | "targets": {
8 | "build": {
9 | "executor": "@angular-devkit/build-angular:browser",
10 | "options": {
11 | "outputPath": "dist/apps/demos/browser",
12 | "index": "apps/demos/src/index.html",
13 | "main": "apps/demos/src/main.ts",
14 | "polyfills": "apps/demos/src/polyfills.ts",
15 | "tsConfig": "apps/demos/tsconfig.app.json",
16 | "styles": [],
17 | "scripts": [],
18 | "vendorChunk": true,
19 | "extractLicenses": false,
20 | "buildOptimizer": false,
21 | "sourceMap": true,
22 | "optimization": false,
23 | "namedChunks": true,
24 | "assets": ["apps/demos/src/favicon.ico", "apps/demos/src/assets"]
25 | },
26 | "configurations": {
27 | "development": {
28 | "buildOptimizer": false,
29 | "optimization": false,
30 | "vendorChunk": true,
31 | "extractLicenses": false,
32 | "sourceMap": true,
33 | "namedChunks": true
34 | },
35 | "production": {
36 | "fileReplacements": [
37 | {
38 | "replace": "apps/demos/src/environments/environment.ts",
39 | "with": "apps/demos/src/environments/environment.prod.ts"
40 | }
41 | ],
42 | "optimization": true,
43 | "outputHashing": "all",
44 | "sourceMap": false,
45 | "namedChunks": false,
46 | "extractLicenses": true,
47 | "vendorChunk": false,
48 | "buildOptimizer": true,
49 | "budgets": [
50 | {
51 | "type": "initial",
52 | "maximumWarning": "2mb",
53 | "maximumError": "5mb"
54 | },
55 | {
56 | "type": "anyComponentStyle",
57 | "maximumWarning": "6kb",
58 | "maximumError": "10kb"
59 | }
60 | ]
61 | }
62 | },
63 | "defaultConfiguration": "production"
64 | },
65 | "serve": {
66 | "executor": "@angular-devkit/build-angular:dev-server",
67 | "options": {
68 | "browserTarget": "demos:build"
69 | },
70 | "configurations": {
71 | "development": {
72 | "browserTarget": "demos:build:development"
73 | },
74 | "production": {
75 | "browserTarget": "demos:build:production"
76 | }
77 | },
78 | "defaultConfiguration": "development"
79 | },
80 | "lint": {
81 | "executor": "@nrwl/linter:eslint",
82 | "options": {
83 | "lintFilePatterns": ["apps/demos/src/**/*.ts", "apps/demos/src/**/*.html"]
84 | },
85 | "outputs": ["{options.outputFile}"]
86 | },
87 | "test": {
88 | "executor": "@nrwl/jest:jest",
89 | "outputs": ["{workspaceRoot}/coverage/apps/demos"],
90 | "options": {
91 | "jestConfig": "apps/demos/jest.config.ts",
92 | "passWithNoTests": true
93 | }
94 | },
95 | "server": {
96 | "executor": "@angular-devkit/build-angular:server",
97 | "options": {
98 | "outputPath": "dist/apps/demos/server",
99 | "main": "apps/demos/src/server.ts",
100 | "tsConfig": "apps/demos/tsconfig.server.json",
101 | "sourceMap": true,
102 | "optimization": false
103 | },
104 | "configurations": {
105 | "development": {
106 | "optimization": false,
107 | "sourceMap": true,
108 | "extractLicenses": false
109 | },
110 | "production": {
111 | "sourceMap": false,
112 | "optimization": true,
113 | "outputHashing": "media",
114 | "fileReplacements": [
115 | {
116 | "replace": "apps/demos/src/environments/environment.ts",
117 | "with": "apps/demos/src/environments/environment.prod.ts"
118 | }
119 | ]
120 | }
121 | },
122 | "defaultConfiguration": "production"
123 | },
124 | "serve-ssr": {
125 | "executor": "@nguniversal/builders:ssr-dev-server",
126 | "options": {
127 | "browserTarget": "demos:build",
128 | "serverTarget": "demos:server"
129 | },
130 | "configurations": {
131 | "development": {
132 | "browserTarget": "demos:build:development",
133 | "serverTarget": "demos:server:development"
134 | },
135 | "production": {
136 | "browserTarget": "demos:build:production",
137 | "serverTarget": "demos:server:production"
138 | }
139 | },
140 | "defaultConfiguration": "development"
141 | }
142 | },
143 | "type": ["app"]
144 | }
145 |
--------------------------------------------------------------------------------
/libs/select-snapshot/src/lib/core/decorators/view-select-snapshot.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ɵComponentType,
3 | ɵDirectiveType,
4 | ɵComponentDef,
5 | ɵDirectiveDef,
6 | ɵɵdirectiveInject,
7 | ChangeDetectorRef,
8 | } from '@angular/core';
9 | import { Store } from '@ngxs/store';
10 | import { Subscription } from 'rxjs';
11 |
12 | import {
13 | defineSelectSnapshotProperties,
14 | getSelectorFromInstance,
15 | } from '../internals/select-snapshot';
16 |
17 | interface CreateSelectorParams {
18 | name: string | symbol;
19 | paths: string[];
20 | selectorOrFeature: any;
21 | }
22 |
23 | const targetToScheduledMicrotaskMap = new Map