├── .angular-cli.json ├── .circleci ├── bazel.rc └── config.yml ├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .prettierignore ├── BUILD.bazel ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MIGRATION.md ├── README.md ├── WORKSPACE ├── build ├── builder.ts ├── config.ts ├── deploy-build.ts ├── index.ts ├── tasks.ts └── util.ts ├── docs ├── effects │ ├── README.md │ ├── api.md │ └── testing.md ├── entity │ ├── README.md │ ├── adapter.md │ └── interfaces.md ├── router-store │ ├── README.md │ └── api.md ├── schematics │ ├── README.md │ ├── action.md │ ├── container.md │ ├── effect.md │ ├── entity.md │ ├── feature.md │ ├── reducer.md │ └── store.md ├── store-devtools │ └── README.md └── store │ ├── README.md │ ├── actions.md │ ├── api.md │ ├── selectors.md │ ├── setup.md │ └── testing.md ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.e2e.json ├── example-app ├── README.md ├── app │ ├── app.module.ts │ ├── auth │ │ ├── actions │ │ │ └── auth.ts │ │ ├── auth.module.ts │ │ ├── components │ │ │ ├── __snapshots__ │ │ │ │ └── login-form.component.spec.ts.snap │ │ │ ├── login-form.component.spec.ts │ │ │ └── login-form.component.ts │ │ ├── containers │ │ │ ├── __snapshots__ │ │ │ │ └── login-page.component.spec.ts.snap │ │ │ ├── login-page.component.spec.ts │ │ │ └── login-page.component.ts │ │ ├── effects │ │ │ ├── auth.effects.spec.ts │ │ │ └── auth.effects.ts │ │ ├── models │ │ │ └── user.ts │ │ ├── reducers │ │ │ ├── __snapshots__ │ │ │ │ ├── auth.spec.ts.snap │ │ │ │ └── login-page.spec.ts.snap │ │ │ ├── auth.spec.ts │ │ │ ├── auth.ts │ │ │ ├── index.ts │ │ │ ├── login-page.spec.ts │ │ │ └── login-page.ts │ │ └── services │ │ │ ├── auth-guard.service.spec.ts │ │ │ ├── auth-guard.service.ts │ │ │ └── auth.service.ts │ ├── books │ │ ├── actions │ │ │ ├── book.ts │ │ │ └── collection.ts │ │ ├── books.module.ts │ │ ├── components │ │ │ ├── book-authors.ts │ │ │ ├── book-detail.ts │ │ │ ├── book-preview-list.ts │ │ │ ├── book-preview.ts │ │ │ ├── book-search.ts │ │ │ └── index.ts │ │ ├── containers │ │ │ ├── __snapshots__ │ │ │ │ ├── collection-page.spec.ts.snap │ │ │ │ ├── find-book-page.spec.ts.snap │ │ │ │ ├── selected-book-page.spec.ts.snap │ │ │ │ └── view-book-page.spec.ts.snap │ │ │ ├── collection-page.spec.ts │ │ │ ├── collection-page.ts │ │ │ ├── find-book-page.spec.ts │ │ │ ├── find-book-page.ts │ │ │ ├── selected-book-page.spec.ts │ │ │ ├── selected-book-page.ts │ │ │ ├── view-book-page.spec.ts │ │ │ └── view-book-page.ts │ │ ├── effects │ │ │ ├── book.spec.ts │ │ │ ├── book.ts │ │ │ ├── collection.spec.ts │ │ │ └── collection.ts │ │ ├── guards │ │ │ └── book-exists.ts │ │ ├── models │ │ │ └── book.ts │ │ └── reducers │ │ │ ├── __snapshots__ │ │ │ └── books.spec.ts.snap │ │ │ ├── books.spec.ts │ │ │ ├── books.ts │ │ │ ├── collection.ts │ │ │ ├── index.ts │ │ │ └── search.ts │ ├── core │ │ ├── actions │ │ │ └── layout.ts │ │ ├── components │ │ │ ├── layout.ts │ │ │ ├── nav-item.ts │ │ │ ├── sidenav.ts │ │ │ └── toolbar.ts │ │ ├── containers │ │ │ ├── app.ts │ │ │ └── not-found-page.ts │ │ ├── core.module.ts │ │ ├── reducers │ │ │ └── layout.ts │ │ └── services │ │ │ ├── google-books.spec.ts │ │ │ └── google-books.ts │ ├── db.ts │ ├── index.ts │ ├── material │ │ ├── index.ts │ │ └── material.module.ts │ ├── reducers │ │ └── index.ts │ ├── routes.ts │ └── shared │ │ ├── pipes │ │ ├── add-commas.spec.ts │ │ ├── add-commas.ts │ │ ├── ellipsis.spec.ts │ │ ├── ellipsis.ts │ │ └── index.ts │ │ └── utils.ts ├── assets │ ├── .gitkeep │ └── .npmignore ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css ├── test.ts ├── tsconfig.app.json └── tsconfig.spec.json ├── karma.conf.js ├── lerna.json ├── modules ├── effects │ ├── BUILD │ ├── CHANGELOG.md │ ├── README.md │ ├── index.ts │ ├── package.json │ ├── public_api.ts │ ├── rollup.config.js │ ├── spec │ │ ├── BUILD │ │ ├── actions.spec.ts │ │ ├── effect_sources.spec.ts │ │ ├── effects_feature_module.spec.ts │ │ ├── effects_metadata.spec.ts │ │ ├── effects_resolver.spec.ts │ │ ├── effects_root_module.spec.ts │ │ └── ngc │ │ │ ├── ngc.spec.ts │ │ │ └── tsconfig.ngc.json │ ├── src │ │ ├── actions.ts │ │ ├── effect_notification.ts │ │ ├── effect_sources.ts │ │ ├── effects_feature_module.ts │ │ ├── effects_metadata.ts │ │ ├── effects_module.ts │ │ ├── effects_resolver.ts │ │ ├── effects_root_module.ts │ │ ├── effects_runner.ts │ │ ├── index.ts │ │ ├── on_run_effects.ts │ │ └── tokens.ts │ ├── testing │ │ ├── BUILD │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src │ │ │ └── testing.ts │ │ └── tsconfig-build.json │ └── tsconfig-build.json ├── entity │ ├── BUILD │ ├── CHANGELOG.md │ ├── README.md │ ├── index.ts │ ├── package.json │ ├── public_api.ts │ ├── rollup.config.js │ ├── spec │ │ ├── BUILD │ │ ├── entity_state.spec.ts │ │ ├── fixtures │ │ │ └── book.ts │ │ ├── sorted_state_adapter.spec.ts │ │ ├── state_selectors.spec.ts │ │ └── unsorted_state_adapter.spec.ts │ ├── src │ │ ├── create_adapter.ts │ │ ├── entity_state.ts │ │ ├── index.ts │ │ ├── models.ts │ │ ├── sorted_state_adapter.ts │ │ ├── state_adapter.ts │ │ ├── state_selectors.ts │ │ └── unsorted_state_adapter.ts │ └── tsconfig-build.json ├── router-store │ ├── BUILD │ ├── CHANGELOG.md │ ├── README.md │ ├── e2e │ │ ├── app.ts │ │ └── main.ts │ ├── index.ts │ ├── package.json │ ├── public_api.ts │ ├── rollup.config.js │ ├── spec │ │ ├── BUILD │ │ └── integration.spec.ts │ ├── src │ │ ├── index.ts │ │ ├── router_store_module.ts │ │ └── serializer.ts │ ├── tsconfig-build.json │ └── webpack.e2e.config.js ├── schematics │ ├── CHANGELOG.md │ ├── README.md │ ├── collection.json │ ├── package.json │ ├── src │ │ ├── action │ │ │ ├── files │ │ │ │ └── __path__ │ │ │ │ │ └── __name@dasherize@if-flat__ │ │ │ │ │ ├── __name@dasherize__.actions.ts │ │ │ │ │ └── __name@dasherize__.actions__dot__spec.ts │ │ │ ├── index.spec.ts │ │ │ ├── index.ts │ │ │ ├── schema.d.ts │ │ │ └── schema.json │ │ ├── container │ │ │ ├── files │ │ │ │ └── __path__ │ │ │ │ │ └── __name@dasherize@if-flat__ │ │ │ │ │ └── __name@dasherize__.component__dot__spec.ts │ │ │ ├── index.spec.ts │ │ │ ├── index.ts │ │ │ ├── schema.d.ts │ │ │ └── schema.json │ │ ├── effect │ │ │ ├── files │ │ │ │ └── __path__ │ │ │ │ │ └── __name@dasherize@if-flat__ │ │ │ │ │ ├── __name@dasherize__.effects.ts │ │ │ │ │ └── __name@dasherize__.effects__dot__spec.ts │ │ │ ├── index.spec.ts │ │ │ ├── index.ts │ │ │ ├── schema.d.ts │ │ │ └── schema.json │ │ ├── entity │ │ │ ├── files │ │ │ │ └── __path__ │ │ │ │ │ └── __name@dasherize@if-flat__ │ │ │ │ │ ├── __name@dasherize@group-actions__.actions.ts │ │ │ │ │ ├── __name@dasherize@group-models__.model.ts │ │ │ │ │ ├── __name@dasherize@group-reducers__.reducer.ts │ │ │ │ │ └── __name@dasherize@group-reducers__.reducer__dot__spec.ts │ │ │ ├── index.spec.ts │ │ │ ├── index.ts │ │ │ ├── schema.d.ts │ │ │ └── schema.json │ │ ├── feature │ │ │ ├── index.spec.ts │ │ │ ├── index.ts │ │ │ ├── schema.d.ts │ │ │ └── schema.json │ │ ├── reducer │ │ │ ├── files │ │ │ │ └── __path__ │ │ │ │ │ └── __name@dasherize@if-flat__ │ │ │ │ │ ├── __name@dasherize__.reducer.ts │ │ │ │ │ └── __name@dasherize__.reducer__dot__spec.ts │ │ │ ├── index.spec.ts │ │ │ ├── index.ts │ │ │ ├── schema.d.ts │ │ │ └── schema.json │ │ ├── store │ │ │ ├── files │ │ │ │ └── __path__ │ │ │ │ │ └── __statePath__ │ │ │ │ │ └── index.ts │ │ │ ├── index.spec.ts │ │ │ ├── index.ts │ │ │ ├── schema.d.ts │ │ │ └── schema.json │ │ ├── strings.ts │ │ └── utility │ │ │ ├── ast-utils.ts │ │ │ ├── ast-utils_spec.ts │ │ │ ├── change.ts │ │ │ ├── find-module.ts │ │ │ ├── find-module_spec.ts │ │ │ ├── ngrx-utils.ts │ │ │ ├── route-utils.ts │ │ │ └── test │ │ │ ├── create-app-module.ts │ │ │ ├── create-reducers.ts │ │ │ ├── get-file-content.ts │ │ │ └── index.ts │ └── tsconfig-build.json ├── store-devtools │ ├── BUILD │ ├── CHANGELOG.md │ ├── README.md │ ├── index.ts │ ├── package.json │ ├── public_api.ts │ ├── rollup.config.js │ ├── spec │ │ ├── BUILD │ │ ├── config.spec.ts │ │ ├── extension.spec.ts │ │ ├── integration.spec.ts │ │ └── store.spec.ts │ ├── src │ │ ├── actions.ts │ │ ├── config.ts │ │ ├── devtools.ts │ │ ├── extension.ts │ │ ├── index.ts │ │ ├── instrument.ts │ │ ├── reducer.ts │ │ └── utils.ts │ ├── tests.js │ └── tsconfig-build.json └── store │ ├── BUILD │ ├── CHANGELOG.md │ ├── README.md │ ├── index.ts │ ├── package.json │ ├── public_api.ts │ ├── rollup.config.js │ ├── spec │ ├── BUILD │ ├── edge.spec.ts │ ├── fixtures │ │ ├── counter.ts │ │ ├── edge_todos.ts │ │ └── todos.ts │ ├── integration.spec.ts │ ├── modules.spec.ts │ ├── ngc │ │ ├── main.ts │ │ └── tsconfig.ngc.json │ ├── selector.spec.ts │ ├── state.spec.ts │ ├── store.spec.ts │ └── utils.spec.ts │ ├── src │ ├── actions_subject.ts │ ├── index.ts │ ├── models.ts │ ├── private_export.ts │ ├── reducer_manager.ts │ ├── scanned_actions_subject.ts │ ├── selector.ts │ ├── state.ts │ ├── store.ts │ ├── store_module.ts │ ├── tokens.ts │ └── utils.ts │ └── tsconfig-build.json ├── package-lock.json ├── package.json ├── protractor.conf.js ├── setup-jest.ts ├── tests.js ├── tools ├── BUILD ├── bazel.rc ├── defaults.bzl ├── package.json ├── rxjs-patch-pr3322.js ├── testing │ ├── BUILD │ └── bootstrap_node_tests.ts └── yarn.lock ├── tsconfig.json ├── tslint.json └── yarn.lock /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "example-app" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "example-app", 9 | "outDir": "example-dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.app.json", 19 | "testTsconfig": "tsconfig.spec.json", 20 | "prefix": "bc", 21 | "styles": [ 22 | "styles.css" 23 | ], 24 | "scripts": [], 25 | "environmentSource": "environments/environment.ts", 26 | "environments": { 27 | "dev": "environments/environment.ts", 28 | "prod": "environments/environment.prod.ts" 29 | } 30 | } 31 | ], 32 | "e2e": { 33 | "protractor": { 34 | "config": "./protractor.conf.js" 35 | } 36 | }, 37 | "lint": [ 38 | { 39 | "project": "example-app/tsconfig.app.json" 40 | }, 41 | { 42 | "project": "example-app/tsconfig.spec.json" 43 | }, 44 | { 45 | "project": "e2e/tsconfig.e2e.json" 46 | } 47 | ], 48 | "test": { 49 | "karma": { 50 | "config": "./karma.conf.js" 51 | } 52 | }, 53 | "defaults": { 54 | "styleExt": "css", 55 | "component": { 56 | "inlineStyle": true, 57 | "inlineTemplate": true, 58 | "flat": true, 59 | "spec": false 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.circleci/bazel.rc: -------------------------------------------------------------------------------- 1 | # These options are enabled when running on CI 2 | # We do this by copying this file to /etc/bazel.bazelrc at the start of the build. 3 | 4 | # Don't be spammy in the logs 5 | build --noshow_progress 6 | 7 | # Don't run manual tests 8 | test --test_tag_filters=-manual 9 | 10 | # Prevent unstable environment variables from tainting cache keys 11 | build --experimental_strict_action_env 12 | 13 | # Workaround https://github.com/bazelbuild/bazel/issues/3645 14 | # Bazel doesn't calculate the memory ceiling correctly when running under Docker. 15 | # Limit Bazel to consuming resources that fit in CircleCI "medium" class which is the default: 16 | # https://circleci.com/docs/2.0/configuration-reference/#resource_class 17 | build --local_resources=3072,2.0,1.0 18 | 19 | # Retry in the event of flakes, eg. https://circleci.com/gh/angular/angular/31309 20 | test --flaky_test_attempts=2 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## I'm submitting a... 4 | 5 |

 6 | [ ] Regression (a behavior that used to work and stopped working in a new release)
 7 | [ ] Bug report  
 8 | [ ] Feature request
 9 | [ ] Documentation issue or request
10 | 
11 | 12 | ## What is the current behavior? 13 | 14 | 15 | ## Expected behavior: 16 | 17 | 18 | 19 | ## Minimal reproduction of the problem with instructions: 20 | 24 | 25 | ## Version of affected browser(s),operating system(s), npm, node and ngrx: 26 | 27 | ## Other information: 28 | 29 | 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | .nyc 5 | .nyc_output 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # Compiled binary addons (http://nodejs.org/api/addons.html) 22 | build/Release 23 | 24 | # Users Environment Variables 25 | .lock-wscript 26 | 27 | # OS generated files # 28 | .DS_Store 29 | ehthumbs.db 30 | Icon? 31 | Thumbs.db 32 | 33 | # Node Files # 34 | node_modules 35 | /bower_components 36 | 37 | # Typing TSD # 38 | /src/typings/tsd/ 39 | /typings/ 40 | /tsd_typings/ 41 | 42 | # Dist # 43 | /dist 44 | /public/__build__/ 45 | /src/*/__build__/ 46 | __build__/** 47 | .webpack.json 48 | 49 | #doc 50 | /doc 51 | 52 | # IDE # 53 | .idea/ 54 | *.iml 55 | *.swp 56 | !/typings/custom.d.ts 57 | .vscode/ 58 | 59 | # Build Artifacts # 60 | release 61 | dist 62 | bazel-out 63 | /node_modules/ 64 | lerna-debug.log 65 | /lib/ 66 | ngfactory 67 | output 68 | *.ngsummary.json 69 | *.ngfactory.ts 70 | tmp 71 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /modules/schematics/src/*/files/* -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | # Needed so that tsconfig.json can be referenced from BUILD rules. 4 | exports_files(["tsconfig.json"]) 5 | 6 | filegroup( 7 | name = "ngrx_test_dependencies", 8 | # NB: rxjs is not in this list, because we build it from sources using the 9 | # label @rxjs//:rxjs 10 | srcs = glob(["/".join([ 11 | "node_modules", 12 | pkg, 13 | "**", 14 | ext, 15 | ]) for pkg in [ 16 | "@angular", 17 | "jasmine", 18 | "jasmine-marbles", 19 | "typescript", 20 | "@types", 21 | ] for ext in [ 22 | "*.js", 23 | "*.json", 24 | "*.d.ts", 25 | ]]), 26 | ) 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelogs 2 | 3 | - [@ngrx/store](./modules/store/CHANGELOG.md) 4 | - [@ngrx/effects](./modules/effects/CHANGELOG.md) 5 | - [@ngrx/router-store](./modules/router-store/CHANGELOG.md) 6 | - [@ngrx/store-devtools](./modules/store-devtools/CHANGELOG.md) 7 | - [@ngrx/entity](./modules/entity/CHANGELOG.md) 8 | - [@ngrx/schematics](./modules/schematics/CHANGELOG.md) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Brandon Roberts, Mike Ryan, Victor Savkin, Rob Wormald 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build/builder.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from './tasks'; 2 | import { createBuilder } from './util'; 3 | 4 | export default createBuilder([ 5 | ['Removing "./dist" Folder', tasks.removeDistFolder], 6 | ['Compiling packages with NGC', tasks.compilePackagesWithNgc], 7 | ['Bundling FESMs', tasks.bundleFesms], 8 | ['Down-leveling FESMs to ES5', tasks.downLevelFesmsToES5], 9 | ['Creating UMD Bundles', tasks.createUmdBundles], 10 | ['Renaming package entry files', tasks.renamePackageEntryFiles], 11 | ['Cleaning TypeScript files', tasks.cleanTypeScriptFiles], 12 | ['Cleaning JavaScript files', tasks.cleanJavaScriptFiles], 13 | ['Removing remaining sourcemap files', tasks.removeRemainingSourceMapFiles], 14 | ['Copying type definition files', tasks.copyTypeDefinitionFiles], 15 | ['Copying schematic files', tasks.copySchematicFiles], 16 | ['Minifying UMD bundles', tasks.minifyUmdBundles], 17 | ['Copying documents', tasks.copyDocs], 18 | ['Copying package.json files', tasks.copyPackageJsonFiles], 19 | ['Removing "./dist/packages" Folder', tasks.removePackagesFolder], 20 | ['Removing summary files', tasks.removeSummaryFiles], 21 | ]); 22 | -------------------------------------------------------------------------------- /build/config.ts: -------------------------------------------------------------------------------- 1 | export interface PackageDescription { 2 | name: string; 3 | hasTestingModule: boolean; 4 | bundle: boolean; 5 | } 6 | 7 | export interface Config { 8 | packages: PackageDescription[]; 9 | scope: string; 10 | } 11 | 12 | export const packages: PackageDescription[] = [ 13 | { 14 | name: 'store', 15 | hasTestingModule: false, 16 | bundle: true, 17 | }, 18 | { 19 | name: 'effects', 20 | hasTestingModule: true, 21 | bundle: true, 22 | }, 23 | { 24 | name: 'router-store', 25 | hasTestingModule: false, 26 | bundle: true, 27 | }, 28 | { 29 | name: 'store-devtools', 30 | hasTestingModule: false, 31 | bundle: true, 32 | }, 33 | { 34 | name: 'entity', 35 | hasTestingModule: false, 36 | bundle: true, 37 | }, 38 | { 39 | name: 'schematics', 40 | hasTestingModule: false, 41 | bundle: false, 42 | }, 43 | ]; 44 | -------------------------------------------------------------------------------- /build/deploy-build.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from './tasks'; 2 | import { createBuilder } from './util'; 3 | import { packages } from './config'; 4 | 5 | const deploy = createBuilder([['Deploy builds', tasks.publishToRepo]]); 6 | 7 | deploy({ 8 | scope: '@ngrx', 9 | packages, 10 | }).catch(err => { 11 | console.error(err); 12 | process.exit(1); 13 | }); 14 | -------------------------------------------------------------------------------- /build/index.ts: -------------------------------------------------------------------------------- 1 | import build from './builder'; 2 | import { packages } from './config'; 3 | 4 | build({ 5 | scope: '@ngrx', 6 | packages, 7 | }).catch(err => { 8 | console.error(err); 9 | process.exit(1); 10 | }); 11 | -------------------------------------------------------------------------------- /docs/entity/README.md: -------------------------------------------------------------------------------- 1 | # @ngrx/entity 2 | 3 | Entity State adapter for managing record collections. 4 | 5 | @ngrx/entity provides an API to manipulate and query entity collections. 6 | 7 | - Reduces boilerplate for creating reducers that manage a collection of models. 8 | - Provides performant CRUD operations for managing entity collections. 9 | - Extensible type-safe adapters for selecting entity information. 10 | 11 | ### Installation 12 | Install @ngrx/entity from npm: 13 | 14 | `npm install @ngrx/entity --save` OR `yarn add @ngrx/entity` 15 | 16 | ### Nightly builds 17 | 18 | `npm install github:ngrx/entity-builds` OR `yarn add github:ngrx/entity-builds` 19 | 20 | ## API Documentation 21 | - [Interfaces](./interfaces.md) 22 | - [Entity Adapter](./adapter.md) 23 | - [Selectors](./adapter.md#entity-selectors) 24 | -------------------------------------------------------------------------------- /docs/entity/interfaces.md: -------------------------------------------------------------------------------- 1 | # Entity Interfaces 2 | 3 | ## EntityState 4 | 5 | The Entity State is a predefined generic interface for a given entity collection with the following interface: 6 | 7 | ```ts 8 | interface EntityState { 9 | ids: string[] | number[]; 10 | entities: { [id: string | id: number]: V }; 11 | } 12 | ``` 13 | 14 | * `ids`: An array of all the primary ids in the collection 15 | * `entities`: A dictionary of entities in the collection indexed by the primary id 16 | 17 | Extend this interface to provided any additional properties for the entity state. 18 | 19 | Usage: 20 | 21 | ```ts 22 | export interface User { 23 | id: string; 24 | name: string; 25 | } 26 | 27 | export interface State extends EntityState { 28 | // additional entity state properties 29 | selectedUserId: number | null; 30 | } 31 | ``` 32 | 33 | ## EntityAdapter 34 | 35 | Provides a generic type interface for the provided [entity adapter](./adapter.md#createentityadapter). The entity adapter provides many [collection methods](./adapter.md#adapter-collection-methods) for managing the entity state. 36 | 37 | Usage: 38 | 39 | ```ts 40 | export const adapter: EntityAdapter = createEntityAdapter(); 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/router-store/README.md: -------------------------------------------------------------------------------- 1 | # @ngrx/router-store 2 | 3 | 用 `@ngrx/store` 绑定 ng 的 路由 4 | 5 | ### 安装 6 | 从 npm 安装 `Install @ngrx/router-store` : 7 | `npm install @ngrx/router-store --save` 或 `yarn add @ngrx/router-store` 8 | 9 | ### 每日构建版 10 | `npm install github:ngrx/router-store-builds` 或 `yarn add github:ngrx/router-store-builds` 11 | 12 | ## 用法 13 | 在路由导航跳转期间, 在任何的路由守卫和路由解析器执行之前,路由会触发一个有 `RouterNavigationAction` 签名的 `ROUTER_NAVIGATION` action: 14 | 15 | 16 | ```ts 17 | /** 18 | * ROUTER_NAVIGATION 的有效载荷 (携带的信息) 19 | */ 20 | export declare type RouterNavigationPayload = { 21 | routerState: T; 22 | event: RoutesRecognized; 23 | }; 24 | 25 | /** 26 | * 路由导航开始时,action 将被派发出去 27 | */ 28 | export declare type RouterNavigationAction = { 29 | type: typeof ROUTER_NAVIGATION; 30 | payload: RouterNavigationPayload; 31 | }; 32 | ``` 33 | 34 | - `Reducers` 会收到这个 `action`。 如果取消导航的话会抛出一个错误。 35 | - `Effects` 能监听这个 `action`。 36 | - `ROUTER_CANCEL` action 表示路由守卫取消了当前的导航。 37 | - `ROUTER_ERROR` action 表示一个导航错误。 38 | - `ROUTER_CANCEL` 和 `ROUTER_ERROR` 包含了开始导航前的 `store` 状态。 使用上一个状态来恢复 `store` 的前后一致性。 39 | 40 | ## 步骤 41 | 42 | ```ts 43 | import { StoreRouterConnectingModule, routerReducer } from '@ngrx/router-store'; 44 | import { AppComponent } from './app.component'; 45 | 46 | @NgModule({ 47 | imports: [ 48 | BrowserModule, 49 | StoreModule.forRoot({ 50 | router: routerReducer 51 | }), 52 | RouterModule.forRoot([ 53 | // routes 54 | ]), 55 | StoreRouterConnectingModule.forRoot({ 56 | stateKey: 'router' // name of reducer key 57 | }) 58 | ], 59 | bootstrap: [AppComponent] 60 | }) 61 | export class AppModule { } 62 | ``` 63 | 64 | ## API 文档 65 | 66 | - [Navigation actions](./api.md#navigation-actions) 67 | - [Effects](./api.md#effects) 68 | - [定制你的 state 序列化部分](./api.md#custom-router-state-serializer) 69 | -------------------------------------------------------------------------------- /docs/schematics/README.md: -------------------------------------------------------------------------------- 1 | # @ngrx/schematics 2 | 3 | Scaffolding library for Angular applications using NgRx libraries. 4 | 5 | @ngrx/schematics provides blueprints for generating files when building out feature areas using NgRx. Built on top of `Schematics`, it integrates with the `Angular CLI` to make setting up and expanding NgRx in Angular applications easier. 6 | 7 | ### Installation 8 | Install @ngrx/schematics from npm: 9 | 10 | `npm install @ngrx/schematics --save-dev` 11 | 12 | ##### OR 13 | 14 | `yarn add @ngrx/schematics --dev` 15 | 16 | ### Nightly builds 17 | 18 | `npm install github:ngrx/schematics-builds --save-dev` 19 | 20 | ##### OR 21 | 22 | `yarn add github:ngrx/schematics-builds --dev` 23 | 24 | ## Dependencies 25 | 26 | After installing `@ngrx/schematics`, install the NgRx dependencies. 27 | 28 | `npm install @ngrx/{store,effects,entity,store-devtools} --save` 29 | 30 | ##### OR 31 | 32 | `yarn add @ngrx/{store,effects,entity,store-devtools}` 33 | 34 | 35 | ## Default Schematics Collection 36 | 37 | To use `@ngrx/schematics` as the default collection in your Angular CLI project, 38 | add it to your `.angular-cli.json`: 39 | 40 | ```sh 41 | ng set defaults.schematics.collection=@ngrx/schematics 42 | ``` 43 | 44 | The [collection schema](../../modules/schematics/collection.json) also has aliases to the most common blueprints used to generate files. 45 | 46 | ## Initial State Setup 47 | 48 | Generate the initial state management and register it within the `app.module.ts` 49 | 50 | ```sh 51 | ng generate store State --root --module app.module.ts --collection @ngrx/schematics 52 | ``` 53 | 54 | ## Initial Effects Setup 55 | 56 | Generate the root effects and register it within the `app.module.ts` 57 | 58 | ```sh 59 | ng generate effect App --root --module app.module.ts --collection @ngrx/schematics 60 | ``` 61 | 62 | ## Blueprints 63 | 64 | - [Action](action.md) 65 | - [Container](container.md) 66 | - [Effect](effect.md) 67 | - [Entity](entity.md) 68 | - [Feature](feature.md) 69 | - [Reducer](reducer.md) 70 | - [Store](store.md) 71 | -------------------------------------------------------------------------------- /docs/schematics/action.md: -------------------------------------------------------------------------------- 1 | # Action 2 | -------- 3 | 4 | ## Overview 5 | 6 | Generates an action file that contains an enum of action types, 7 | an example action class and an exported type union of action classes. 8 | 9 | ## Command 10 | 11 | ```sh 12 | ng generate action ActionName [options] 13 | ``` 14 | 15 | ##### OR 16 | 17 | ```sh 18 | ng generate a ActionName [options] 19 | ``` 20 | 21 | ### Options 22 | 23 | Nest the actions file within a folder based on the action `name`. 24 | 25 | - `--flat` 26 | - Type: `boolean` 27 | - Default: `true` 28 | 29 | Group the action file within an `actions` folder. 30 | 31 | - `--group` 32 | - Alias: `-g` 33 | - Type: `boolean` 34 | - Default: `false` 35 | 36 | Generate a spec file alongside the action file. 37 | 38 | - `--spec` 39 | - Type: `boolean` 40 | - Default: `false` 41 | 42 | 43 | #### Examples 44 | 45 | Generate a `User` actions file with an associated spec file. 46 | 47 | ```sh 48 | ng generate action User --spec 49 | ``` 50 | 51 | Generate a `User` actions file within a nested folder 52 | 53 | ```sh 54 | ng generate action User --flat false 55 | ``` 56 | 57 | Generate a `User` actions file within a nested `actions` folder 58 | 59 | ```sh 60 | ng generate action User --group 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/schematics/container.md: -------------------------------------------------------------------------------- 1 | # Container 2 | -------- 3 | 4 | ## Overview 5 | 6 | Generates a component with `Store` injected into its constructor. You can optionally provide the path to your reducers and your state interface. 7 | 8 | ## Command 9 | 10 | ```sh 11 | ng generate container ComponentName [options] 12 | ``` 13 | 14 | ##### OR 15 | 16 | ```sh 17 | ng generate co ComponentName [options] 18 | ``` 19 | 20 | ### General Options 21 | 22 | `Angular CLI` [component options](https://github.com/angular/angular-cli/wiki/generate-component#options). 23 | 24 | ### Container Options 25 | 26 | Provide the path to your file with an exported state interface 27 | 28 | - `--state` 29 | - Type: `string` 30 | 31 | Provide the name of the interface exported for your state interface 32 | 33 | - `--stateInterface` 34 | - Type: `string` 35 | - Default: `State` 36 | 37 | #### Examples 38 | 39 | Generate a `UsersPage` container component with your reducers imported and the `Store` typed a custom interface named `MyState`. 40 | 41 | ```sh 42 | ng generate container UsersPage --state reducers/index.ts --stateInterface MyState 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/schematics/effect.md: -------------------------------------------------------------------------------- 1 | # Effect 2 | -------- 3 | 4 | ## Overview 5 | 6 | Generates an effect file for `@ngrx/effects`. 7 | 8 | ## Command 9 | 10 | ```sh 11 | ng generate effect EffectName [options] 12 | ``` 13 | 14 | ##### OR 15 | 16 | ```sh 17 | ng generate ef EffectName [options] 18 | ``` 19 | 20 | ### Options 21 | 22 | Nest the effects file within a folder based by the effect `name`. 23 | 24 | - `--flat` 25 | - Type: `boolean` 26 | - Default: `true` 27 | 28 | Group the effect file within an `effects` folder. 29 | 30 | - `--group` 31 | - Alias: `-g` 32 | - Type: `boolean` 33 | - Default: `false` 34 | 35 | Provide the path to a file containing an `Angular Module` and the effect will be added to its `imports` array. If the `--root` option is not included, the effect will be registered using `EffectsModule.forFeature`. 36 | 37 | - `--module` 38 | - Alias: `-m` 39 | - Type: `string` 40 | 41 | When used with the `--module` option, it registers an effect within the `Angular Module` using `EffectsModule.forRoot`. 42 | 43 | - `--root` 44 | - Type: `boolean` 45 | - Default: `false` 46 | 47 | Generate a spec file alongside the action file. 48 | 49 | - `--spec` 50 | - Type: `boolean` 51 | - Default: `true` 52 | 53 | 54 | #### Examples 55 | 56 | Generate a `UserEffects` file and register it within the root Angular module in the root-level effects. 57 | 58 | ```sh 59 | ng generate effect User --root -m app.module.ts 60 | ``` 61 | 62 | Generate a `UserEffects` file within a `user` folder and register it with the `user.module.ts` file in the same folder. 63 | 64 | ```sh 65 | ng generate module User --flat false 66 | ng generate effect user/User -m user.module.ts 67 | ``` 68 | 69 | Generate a `UserEffects` file within a nested `effects` folder 70 | 71 | ```sh 72 | ng generate effect User --group 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/schematics/entity.md: -------------------------------------------------------------------------------- 1 | # Entity 2 | -------- 3 | 4 | ## Overview 5 | 6 | Generates an set of entity files for managing a collection using `@ngrx/entity` including a set of predefined `actions`, a collection `model` and a `reducer` with state selectors. 7 | 8 | ## Command 9 | 10 | ```sh 11 | ng generate entity EntityName [options] 12 | ``` 13 | 14 | ##### OR 15 | 16 | ```sh 17 | ng generate en EntityName [options] 18 | ``` 19 | 20 | ### Options 21 | 22 | Nest the effects file within a folder based on the entity `name`. 23 | 24 | - `--flat` 25 | - Type: `boolean` 26 | - Default: `true` 27 | 28 | Provide the path to a file containing an `Angular Module` and the entity reducer will be added to its `imports` array using `StoreModule.forFeature`. 29 | 30 | - `--module` 31 | - Alias: `-m` 32 | - Type: `string` 33 | 34 | Provide the path to a `reducers` file containing a state interface and a object map of action reducers. The generated entity interface will be imported added to the first defined interface within the file. The entity reducer will be imported and added to the first defined object with an `ActionReducerMap` type. 35 | 36 | - `--reducers` 37 | - Alias: `-r` 38 | - Type: `string` 39 | 40 | Generate spec files associated with the entity files. 41 | 42 | - `--spec` 43 | - Type: `boolean` 44 | - Default: `true` 45 | 46 | 47 | #### Examples 48 | 49 | Generate a set of `User` entity files and add it to a defined map of reducers generated from a [feature state](./store.md#examples). 50 | 51 | ```sh 52 | ng generate entity User --reducers reducers/index.ts 53 | ``` 54 | 55 | Generate a set of `User` entity files within a nested folder 56 | 57 | ```sh 58 | ng generate entity User --flat false 59 | ``` 60 | 61 | Generate a set of `User` entity files and register it within the `Angular Module` in `app.module.ts` as a feature state. 62 | 63 | ```sh 64 | ng generate entity User -m app.module.ts 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/schematics/reducer.md: -------------------------------------------------------------------------------- 1 | # Reducer 2 | -------- 3 | 4 | ## Overview 5 | 6 | Generates a reducer file that contains a state interface, 7 | and initial state object for the reducer, and a reducer function. 8 | 9 | ## Command 10 | 11 | ```sh 12 | ng generate reducer ReducerName [options] 13 | ``` 14 | 15 | ##### OR 16 | 17 | ```sh 18 | ng generate r ReducerName [options] 19 | ``` 20 | 21 | ### Options 22 | 23 | Nest the reducer file within a folder based on the reducer `name`. 24 | 25 | - `--flat` 26 | - Type: `boolean` 27 | - Default: `true` 28 | 29 | Group the reducer file within an `reducers` folder. 30 | 31 | - `--group` 32 | - Alias: `-g` 33 | - Type: `boolean` 34 | - Default: `false` 35 | 36 | Provide the path to a file containing an `Angular Module` and the entity reducer will be added to its `imports` array using `StoreModule.forFeature`. 37 | 38 | - `--module` 39 | - Alias: `-m` 40 | - Type: `string` 41 | 42 | Provide the path to a `reducers` file containing a state interface and a object map of action reducers. The generated reducer interface will be imported added to the first defined interface within the file. The reducer will be imported and added to the first defined object with an `ActionReducerMap` type. 43 | 44 | - `--reducers` 45 | - Alias: `-r` 46 | - Type: `string` 47 | 48 | Generate a spec file alongside the reducer file. 49 | 50 | - `--spec` 51 | - Type: `boolean` 52 | - Default: `true` 53 | 54 | 55 | #### Examples 56 | 57 | Generate a `User` reducer file add it to a defined map of reducers generated from a [feature state](./store.md#examples). 58 | 59 | ```sh 60 | ng generate reducer User --reducers reducers/index.ts 61 | ``` 62 | 63 | Generate `User` a reducer file within a nested folder based on the reducer name. 64 | 65 | ```sh 66 | ng generate reducer User --flat false 67 | ``` 68 | 69 | Generate a `User` reducer and register it within the `Angular Module` in `app.module.ts`. 70 | 71 | ```sh 72 | ng generate reducer User --module app.module.ts 73 | ``` 74 | 75 | Generate a `User` reducer file within a nested `reducers` folder 76 | 77 | ```sh 78 | ng generate reducer User --group 79 | ``` -------------------------------------------------------------------------------- /docs/schematics/store.md: -------------------------------------------------------------------------------- 1 | # Store 2 | -------- 3 | 4 | ## Overview 5 | 6 | Generates the initial setup for state management and registering new feature states. It registers the `@ngrx/store-devtools` integration and generates a state management file containing the state interface, the object map of action reducers and any associated meta-reducers. 7 | 8 | ## Command 9 | 10 | ```sh 11 | ng generate store State [options] 12 | ``` 13 | 14 | ##### OR 15 | 16 | ```sh 17 | ng generate st State [options] 18 | ``` 19 | 20 | ### Options 21 | 22 | Provide the path to a file containing an `Angular Module` and the feature state will be added to its `imports` array using `StoreModule.forFeature` or `StoreModule.forRoot`. 23 | 24 | - `--module` 25 | - Alias: `-m` 26 | - Type: `string` 27 | 28 | When used with the `--module` option, it registers the state within the `Angular Module` using `StoreModule.forRoot`. The `--root` option should only be used to setup the global `@ngrx/store` providers. 29 | 30 | - `--root` 31 | - Type: `boolean` 32 | - Default: `false` 33 | 34 | Provide the folder where the state files will be created. 35 | 36 | - `--statePath` 37 | - Type: `string` 38 | - Default: `reducers` 39 | 40 | #### Examples 41 | 42 | Generate the initial state management files and register it within the `app.module.ts` 43 | 44 | ```sh 45 | ng generate store State --root --module app.module.ts 46 | ``` 47 | 48 | Generate an `Admin` feature state within the `admin` folder and register it with the `admin.module.ts` in the same folder. 49 | 50 | ```sh 51 | ng generate module admin --flat false 52 | ng generate store Admin -m admin/admin.module.ts 53 | ``` 54 | 55 | Generate the initial state management files within a `store` folder and register it within the `app.module.ts` 56 | 57 | ```sh 58 | ng generate store State --root --statePath store --module app.module.ts 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/store-devtools/README.md: -------------------------------------------------------------------------------- 1 | # @ngrx/store-devtools 2 | 3 | [@ngrx/store](../store/README.md) 的开发者工具 4 | 5 | ### 安装 6 | 从 npm 中安装 @ngrx/store-devtools-builds 7 | 8 | `npm install @ngrx/store-devtools --save` 或 `yarn add @ngrx/store-devtools` 9 | 10 | 11 | ### 每日构建版本 12 | `npm install github:ngrx/store-devtools-builds` 或 `yarn add github:ngrx/store-devtools-builds` 13 | 14 | ## 仪表盘 15 | 16 | 17 | ### 使用火狐、谷歌浏览器的扩展查看路由的变更状态 18 | 1. 下载 [Redux Devtools Extension](http://zalmoxisus.github.io/redux-devtools-extension/) 19 | 2. 在你的 `AppModule` 中通过在元数据 metaData 中导入 `StoreDevtoolsModule.instrument` 添加仪表盘 20 | 21 | 22 | ```ts 23 | import { StoreDevtoolsModule } from '@ngrx/store-devtools'; 24 | import { environment } from '../environments/environment'; // Angular CLI environemnt 25 | 26 | @NgModule({ 27 | imports: [ 28 | StoreModule.forRoot(reducers), 29 | // 注意: 必须马上在引入 `StoreModule` 紧接着引入你的 `StoreDevtoolsModule` 30 | StoreDevtoolsModule.instrument({ 31 | maxAge: 25 // 保留最新的 25 个 state 32 | logOnly: environment.production // 根据应用运行环境限制扩展的行为 33 | 34 | }) 35 | ] 36 | }) 37 | export class AppModule { } 38 | ``` 39 | 40 | 41 | ### 可选的设置项 42 | 当你调用仪表盘时, 可以给它配置一些可选的参数: 43 | 44 | #### `maxAge` 45 | `number` 型 ( 必须>1 ) 或者 false - 配置允许存储在记录中的 `actions` 的最大值。达到最大值时,最早的 `action` 会被清除。 这个参数很影响性能。 默认值是 `false` ( 表示无限大 )。 46 | 47 | #### `logOnly` 48 | boolean - connect to the Devtools Extension in log-only mode. Default is `false` which enables all extension [features](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#features). 49 | 50 | #### `name` 51 | `string` 型 展示在仪表 监控面板上的实例名字。 默认是 _NgRx Store DevTools_ 。 52 | 53 | #### `monitor`: 54 | `function` 型 - 配置你想劫持的监控函数 55 | 56 | #### `actionSanitizer` 57 | `function` 型 入参 `action` 和 `Id` , 返回 指定的 `action` 对象。 58 | 59 | #### `stateSanitizer` 60 | `function` 型 传入 `state` 对象 和 索引,返回 `state` 对象。 61 | 62 | #### `serialize` 63 | false | configuration 对象 - 控制你序列化 state 的方案,[这里查看更多细节](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#serialize)。 64 | 65 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { ExampleAppPage } from './app.po'; 2 | 3 | describe('example-app App', function() { 4 | let page: ExampleAppPage; 5 | 6 | beforeEach(() => { 7 | page = new ExampleAppPage(); 8 | }); 9 | 10 | it('should display the app title in the menu', () => { 11 | page.navigateTo(); 12 | expect(page.getAppDescription()).toContain('Book Collection'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class ExampleAppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getAppDescription() { 9 | return element(by.css('mat-toolbar-row')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "declaration": false, 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "lib": [ 9 | "es2016" 10 | ], 11 | "outDir": "../dist/out-tsc-e2e", 12 | "module": "commonjs", 13 | "target": "es6", 14 | "types": [ 15 | "jasmine", 16 | "node" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example-app/app/auth/actions/auth.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { User, Authenticate } from '../models/user'; 3 | 4 | export enum AuthActionTypes { 5 | Login = '[Auth] Login', 6 | Logout = '[Auth] Logout', 7 | LoginSuccess = '[Auth] Login Success', 8 | LoginFailure = '[Auth] Login Failure', 9 | LoginRedirect = '[Auth] Login Redirect', 10 | } 11 | 12 | export class Login implements Action { 13 | readonly type = AuthActionTypes.Login; 14 | 15 | constructor(public payload: Authenticate) {} 16 | } 17 | 18 | export class LoginSuccess implements Action { 19 | readonly type = AuthActionTypes.LoginSuccess; 20 | 21 | constructor(public payload: { user: User }) {} 22 | } 23 | 24 | export class LoginFailure implements Action { 25 | readonly type = AuthActionTypes.LoginFailure; 26 | 27 | constructor(public payload: any) {} 28 | } 29 | 30 | export class LoginRedirect implements Action { 31 | readonly type = AuthActionTypes.LoginRedirect; 32 | } 33 | 34 | export class Logout implements Action { 35 | readonly type = AuthActionTypes.Logout; 36 | } 37 | 38 | export type AuthActions = 39 | | Login 40 | | LoginSuccess 41 | | LoginFailure 42 | | LoginRedirect 43 | | Logout; 44 | -------------------------------------------------------------------------------- /example-app/app/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { ReactiveFormsModule } from '@angular/forms'; 5 | import { StoreModule } from '@ngrx/store'; 6 | import { EffectsModule } from '@ngrx/effects'; 7 | import { LoginPageComponent } from './containers/login-page.component'; 8 | import { LoginFormComponent } from './components/login-form.component'; 9 | 10 | import { AuthService } from './services/auth.service'; 11 | import { AuthGuard } from './services/auth-guard.service'; 12 | import { AuthEffects } from './effects/auth.effects'; 13 | import { reducers } from './reducers'; 14 | import { MaterialModule } from '../material'; 15 | 16 | export const COMPONENTS = [LoginPageComponent, LoginFormComponent]; 17 | 18 | @NgModule({ 19 | imports: [CommonModule, ReactiveFormsModule, MaterialModule], 20 | declarations: COMPONENTS, 21 | exports: COMPONENTS, 22 | }) 23 | export class AuthModule { 24 | static forRoot(): ModuleWithProviders { 25 | return { 26 | ngModule: RootAuthModule, 27 | providers: [AuthService, AuthGuard], 28 | }; 29 | } 30 | } 31 | 32 | @NgModule({ 33 | imports: [ 34 | AuthModule, 35 | RouterModule.forChild([{ path: 'login', component: LoginPageComponent }]), 36 | StoreModule.forFeature('auth', reducers), 37 | EffectsModule.forFeature([AuthEffects]), 38 | ], 39 | }) 40 | export class RootAuthModule {} 41 | -------------------------------------------------------------------------------- /example-app/app/auth/containers/login-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Store, select } from '@ngrx/store'; 3 | import { Authenticate } from '../models/user'; 4 | import * as fromAuth from '../reducers'; 5 | import * as Auth from '../actions/auth'; 6 | 7 | @Component({ 8 | selector: 'bc-login-page', 9 | template: ` 10 | 14 | 15 | `, 16 | styles: [], 17 | }) 18 | export class LoginPageComponent implements OnInit { 19 | pending$ = this.store.pipe(select(fromAuth.getLoginPagePending)); 20 | error$ = this.store.pipe(select(fromAuth.getLoginPageError)); 21 | 22 | constructor(private store: Store) {} 23 | 24 | ngOnInit() {} 25 | 26 | onSubmit($event: Authenticate) { 27 | this.store.dispatch(new Auth.Login($event)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example-app/app/auth/effects/auth.effects.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Effect, Actions, ofType } from '@ngrx/effects'; 4 | import { of } from 'rxjs/observable/of'; 5 | import { tap, map, exhaustMap, catchError } from 'rxjs/operators'; 6 | 7 | import { AuthService } from '../services/auth.service'; 8 | import { 9 | Login, 10 | LoginSuccess, 11 | LoginFailure, 12 | AuthActionTypes, 13 | } from '../actions/auth'; 14 | import { User, Authenticate } from '../models/user'; 15 | 16 | @Injectable() 17 | export class AuthEffects { 18 | @Effect() 19 | login$ = this.actions$.pipe( 20 | ofType(AuthActionTypes.Login), 21 | map((action: Login) => action.payload), 22 | exhaustMap((auth: Authenticate) => 23 | this.authService 24 | .login(auth) 25 | .pipe( 26 | map(user => new LoginSuccess({ user })), 27 | catchError(error => of(new LoginFailure(error))) 28 | ) 29 | ) 30 | ); 31 | 32 | @Effect({ dispatch: false }) 33 | loginSuccess$ = this.actions$.pipe( 34 | ofType(AuthActionTypes.LoginSuccess), 35 | tap(() => this.router.navigate(['/'])) 36 | ); 37 | 38 | @Effect({ dispatch: false }) 39 | loginRedirect$ = this.actions$.pipe( 40 | ofType(AuthActionTypes.LoginRedirect, AuthActionTypes.Logout), 41 | tap(authed => { 42 | this.router.navigate(['/login']); 43 | }) 44 | ); 45 | 46 | constructor( 47 | private actions$: Actions, 48 | private authService: AuthService, 49 | private router: Router 50 | ) {} 51 | } 52 | -------------------------------------------------------------------------------- /example-app/app/auth/models/user.ts: -------------------------------------------------------------------------------- 1 | export interface Authenticate { 2 | username: string; 3 | password: string; 4 | } 5 | 6 | export interface User { 7 | name: string; 8 | } 9 | -------------------------------------------------------------------------------- /example-app/app/auth/reducers/__snapshots__/auth.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`AuthReducer LOGIN_SUCCESS should add a user set loggedIn to true in auth state 1`] = ` 4 | Object { 5 | "loggedIn": true, 6 | "user": Object { 7 | "name": "test", 8 | }, 9 | } 10 | `; 11 | 12 | exports[`AuthReducer LOGOUT should logout a user 1`] = ` 13 | Object { 14 | "loggedIn": false, 15 | "user": null, 16 | } 17 | `; 18 | 19 | exports[`AuthReducer undefined action should return the default state 1`] = ` 20 | Object { 21 | "loggedIn": false, 22 | "user": null, 23 | } 24 | `; 25 | 26 | exports[`AuthReducer wrong login payload should NOT authenticate a user 1`] = ` 27 | Object { 28 | "loggedIn": false, 29 | "user": null, 30 | } 31 | `; 32 | -------------------------------------------------------------------------------- /example-app/app/auth/reducers/__snapshots__/login-page.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`LoginPageReducer LOGIN should make pending to true 1`] = ` 4 | Object { 5 | "error": null, 6 | "pending": true, 7 | } 8 | `; 9 | 10 | exports[`LoginPageReducer LOGIN_FAILURE should have an error and no pending state 1`] = ` 11 | Object { 12 | "error": "login failed", 13 | "pending": false, 14 | } 15 | `; 16 | 17 | exports[`LoginPageReducer LOGIN_SUCCESS should have no error and no pending state 1`] = ` 18 | Object { 19 | "error": null, 20 | "pending": false, 21 | } 22 | `; 23 | 24 | exports[`LoginPageReducer undefined action should return the default state 1`] = ` 25 | Object { 26 | "error": null, 27 | "pending": false, 28 | } 29 | `; 30 | -------------------------------------------------------------------------------- /example-app/app/auth/reducers/auth.spec.ts: -------------------------------------------------------------------------------- 1 | import { reducer } from './auth'; 2 | import * as fromAuth from './auth'; 3 | import { Login, LoginSuccess, Logout } from '../actions/auth'; 4 | import { Authenticate, User } from '../models/user'; 5 | 6 | describe('AuthReducer', () => { 7 | describe('undefined action', () => { 8 | it('should return the default state', () => { 9 | const action = {} as any; 10 | 11 | const result = reducer(undefined, action); 12 | 13 | /** 14 | * Snapshot tests are a quick way to validate 15 | * the state produced by a reducer since 16 | * its plain JavaScript object. These snapshots 17 | * are used to validate against the current state 18 | * if the functionality of the reducer ever changes. 19 | */ 20 | expect(result).toMatchSnapshot(); 21 | }); 22 | }); 23 | 24 | describe('wrong login payload', () => { 25 | it('should NOT authenticate a user', () => { 26 | const user = { username: 'someUserName' } as Authenticate; 27 | const createAction = new Login(user); 28 | 29 | const expectedResult = fromAuth.initialState; 30 | 31 | const result = reducer(fromAuth.initialState, createAction); 32 | 33 | expect(result).toMatchSnapshot(); 34 | }); 35 | }); 36 | 37 | describe('LOGIN_SUCCESS', () => { 38 | it('should add a user set loggedIn to true in auth state', () => { 39 | const user = { name: 'test' } as User; 40 | const createAction = new LoginSuccess({ user }); 41 | 42 | const expectedResult = { 43 | loggedIn: true, 44 | user: { name: 'test' }, 45 | }; 46 | 47 | const result = reducer(fromAuth.initialState, createAction); 48 | 49 | expect(result).toMatchSnapshot(); 50 | }); 51 | }); 52 | 53 | describe('LOGOUT', () => { 54 | it('should logout a user', () => { 55 | const initialState = { 56 | loggedIn: true, 57 | user: { name: 'test' }, 58 | } as fromAuth.State; 59 | const createAction = new Logout(); 60 | 61 | const expectedResult = fromAuth.initialState; 62 | 63 | const result = reducer(initialState, createAction); 64 | 65 | expect(result).toMatchSnapshot(); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /example-app/app/auth/reducers/auth.ts: -------------------------------------------------------------------------------- 1 | import { AuthActions, AuthActionTypes } from './../actions/auth'; 2 | import { User } from '../models/user'; 3 | 4 | export interface State { 5 | loggedIn: boolean; 6 | user: User | null; 7 | } 8 | 9 | export const initialState: State = { 10 | loggedIn: false, 11 | user: null, 12 | }; 13 | 14 | export function reducer(state = initialState, action: AuthActions): State { 15 | switch (action.type) { 16 | case AuthActionTypes.LoginSuccess: { 17 | return { 18 | ...state, 19 | loggedIn: true, 20 | user: action.payload.user, 21 | }; 22 | } 23 | 24 | case AuthActionTypes.Logout: { 25 | return initialState; 26 | } 27 | 28 | default: { 29 | return state; 30 | } 31 | } 32 | } 33 | 34 | export const getLoggedIn = (state: State) => state.loggedIn; 35 | export const getUser = (state: State) => state.user; 36 | -------------------------------------------------------------------------------- /example-app/app/auth/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createSelector, 3 | createFeatureSelector, 4 | ActionReducerMap, 5 | } from '@ngrx/store'; 6 | import * as fromRoot from '../../reducers'; 7 | import * as fromAuth from './auth'; 8 | import * as fromLoginPage from './login-page'; 9 | 10 | export interface AuthState { 11 | status: fromAuth.State; 12 | loginPage: fromLoginPage.State; 13 | } 14 | 15 | export interface State extends fromRoot.State { 16 | auth: AuthState; 17 | } 18 | 19 | export const reducers: ActionReducerMap = { 20 | status: fromAuth.reducer, 21 | loginPage: fromLoginPage.reducer, 22 | }; 23 | 24 | export const selectAuthState = createFeatureSelector('auth'); 25 | 26 | export const selectAuthStatusState = createSelector( 27 | selectAuthState, 28 | (state: AuthState) => state.status 29 | ); 30 | export const getLoggedIn = createSelector( 31 | selectAuthStatusState, 32 | fromAuth.getLoggedIn 33 | ); 34 | export const getUser = createSelector(selectAuthStatusState, fromAuth.getUser); 35 | 36 | export const selectLoginPageState = createSelector( 37 | selectAuthState, 38 | (state: AuthState) => state.loginPage 39 | ); 40 | export const getLoginPageError = createSelector( 41 | selectLoginPageState, 42 | fromLoginPage.getError 43 | ); 44 | export const getLoginPagePending = createSelector( 45 | selectLoginPageState, 46 | fromLoginPage.getPending 47 | ); 48 | -------------------------------------------------------------------------------- /example-app/app/auth/reducers/login-page.spec.ts: -------------------------------------------------------------------------------- 1 | import { reducer } from './login-page'; 2 | import * as fromLoginPage from './login-page'; 3 | import { Login, LoginSuccess, LoginFailure, Logout } from '../actions/auth'; 4 | import { Authenticate, User } from '../models/user'; 5 | 6 | describe('LoginPageReducer', () => { 7 | describe('undefined action', () => { 8 | it('should return the default state', () => { 9 | const action = {} as any; 10 | 11 | const result = reducer(undefined, action); 12 | 13 | expect(result).toMatchSnapshot(); 14 | }); 15 | }); 16 | 17 | describe('LOGIN', () => { 18 | it('should make pending to true', () => { 19 | const user = { username: 'test' } as Authenticate; 20 | const createAction = new Login(user); 21 | 22 | const expectedResult = { 23 | error: null, 24 | pending: true, 25 | }; 26 | 27 | const result = reducer(fromLoginPage.initialState, createAction); 28 | 29 | expect(result).toMatchSnapshot(); 30 | }); 31 | }); 32 | 33 | describe('LOGIN_SUCCESS', () => { 34 | it('should have no error and no pending state', () => { 35 | const user = { name: 'test' } as User; 36 | const createAction = new LoginSuccess({ user }); 37 | 38 | const expectedResult = { 39 | error: null, 40 | pending: false, 41 | }; 42 | 43 | const result = reducer(fromLoginPage.initialState, createAction); 44 | 45 | expect(result).toMatchSnapshot(); 46 | }); 47 | }); 48 | 49 | describe('LOGIN_FAILURE', () => { 50 | it('should have an error and no pending state', () => { 51 | const error = 'login failed'; 52 | const createAction = new LoginFailure(error); 53 | 54 | const expectedResult = { 55 | error: error, 56 | pending: false, 57 | }; 58 | 59 | const result = reducer(fromLoginPage.initialState, createAction); 60 | 61 | expect(result).toMatchSnapshot(); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /example-app/app/auth/reducers/login-page.ts: -------------------------------------------------------------------------------- 1 | import { AuthActionTypes, AuthActions } from './../actions/auth'; 2 | 3 | export interface State { 4 | error: string | null; 5 | pending: boolean; 6 | } 7 | 8 | export const initialState: State = { 9 | error: null, 10 | pending: false, 11 | }; 12 | 13 | export function reducer(state = initialState, action: AuthActions): State { 14 | switch (action.type) { 15 | case AuthActionTypes.Login: { 16 | return { 17 | ...state, 18 | error: null, 19 | pending: true, 20 | }; 21 | } 22 | 23 | case AuthActionTypes.LoginSuccess: { 24 | return { 25 | ...state, 26 | error: null, 27 | pending: false, 28 | }; 29 | } 30 | 31 | case AuthActionTypes.LoginFailure: { 32 | return { 33 | ...state, 34 | error: action.payload, 35 | pending: false, 36 | }; 37 | } 38 | 39 | default: { 40 | return state; 41 | } 42 | } 43 | } 44 | 45 | export const getError = (state: State) => state.error; 46 | export const getPending = (state: State) => state.pending; 47 | -------------------------------------------------------------------------------- /example-app/app/auth/services/auth-guard.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { StoreModule, Store, combineReducers } from '@ngrx/store'; 3 | import { cold } from 'jasmine-marbles'; 4 | import { AuthGuard } from './auth-guard.service'; 5 | import * as Auth from '../actions/auth'; 6 | import * as fromRoot from '../../reducers'; 7 | import * as fromAuth from '../reducers'; 8 | 9 | describe('Auth Guard', () => { 10 | let guard: AuthGuard; 11 | let store: Store; 12 | 13 | beforeEach(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | StoreModule.forRoot({ 17 | ...fromRoot.reducers, 18 | auth: combineReducers(fromAuth.reducers), 19 | }), 20 | ], 21 | providers: [AuthGuard], 22 | }); 23 | 24 | store = TestBed.get(Store); 25 | spyOn(store, 'dispatch').and.callThrough(); 26 | guard = TestBed.get(AuthGuard); 27 | }); 28 | 29 | it('should return false if the user state is not logged in', () => { 30 | const expected = cold('(a|)', { a: false }); 31 | 32 | expect(guard.canActivate()).toBeObservable(expected); 33 | }); 34 | 35 | it('should return true if the user state is logged in', () => { 36 | const user: any = {}; 37 | const action = new Auth.LoginSuccess({ user }); 38 | store.dispatch(action); 39 | 40 | const expected = cold('(a|)', { a: true }); 41 | 42 | expect(guard.canActivate()).toBeObservable(expected); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /example-app/app/auth/services/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate } from '@angular/router'; 3 | import { Store, select } from '@ngrx/store'; 4 | import { Observable } from 'rxjs/Observable'; 5 | import { map, take } from 'rxjs/operators'; 6 | import * as Auth from '../actions/auth'; 7 | import * as fromAuth from '../reducers'; 8 | 9 | @Injectable() 10 | export class AuthGuard implements CanActivate { 11 | constructor(private store: Store) {} 12 | 13 | canActivate(): Observable { 14 | return this.store.pipe( 15 | select(fromAuth.getLoggedIn), 16 | map(authed => { 17 | if (!authed) { 18 | this.store.dispatch(new Auth.LoginRedirect()); 19 | return false; 20 | } 21 | 22 | return true; 23 | }), 24 | take(1) 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example-app/app/auth/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { of } from 'rxjs/observable/of'; 3 | import { _throw } from 'rxjs/observable/throw'; 4 | import { User, Authenticate } from '../models/user'; 5 | import { Observable } from 'rxjs/Observable'; 6 | 7 | @Injectable() 8 | export class AuthService { 9 | constructor() {} 10 | 11 | login({ username, password }: Authenticate): Observable { 12 | /** 13 | * Simulate a failed login to display the error 14 | * message for the login form. 15 | */ 16 | if (username !== 'test') { 17 | return _throw('Invalid username or password'); 18 | } 19 | 20 | return of({ name: 'User' }); 21 | } 22 | 23 | logout() { 24 | return of(true); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example-app/app/books/actions/book.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { Book } from '../models/book'; 3 | 4 | export enum BookActionTypes { 5 | Search = '[Book] Search', 6 | SearchComplete = '[Book] Search Complete', 7 | SearchError = '[Book] Search Error', 8 | Load = '[Book] Load', 9 | Select = '[Book] Select', 10 | } 11 | 12 | /** 13 | * Every action is comprised of at least a type and an optional 14 | * payload. Expressing actions as classes enables powerful 15 | * type checking in reducer functions. 16 | * 17 | * See Discriminated Unions: https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions 18 | */ 19 | export class Search implements Action { 20 | readonly type = BookActionTypes.Search; 21 | 22 | constructor(public payload: string) {} 23 | } 24 | 25 | export class SearchComplete implements Action { 26 | readonly type = BookActionTypes.SearchComplete; 27 | 28 | constructor(public payload: Book[]) {} 29 | } 30 | 31 | export class SearchError implements Action { 32 | readonly type = BookActionTypes.SearchError; 33 | 34 | constructor(public payload: string) {} 35 | } 36 | 37 | export class Load implements Action { 38 | readonly type = BookActionTypes.Load; 39 | 40 | constructor(public payload: Book) {} 41 | } 42 | 43 | export class Select implements Action { 44 | readonly type = BookActionTypes.Select; 45 | 46 | constructor(public payload: string) {} 47 | } 48 | 49 | /** 50 | * Export a type alias of all actions in this action group 51 | * so that reducers can easily compose action types 52 | */ 53 | export type BookActions = Search | SearchComplete | SearchError | Load | Select; 54 | -------------------------------------------------------------------------------- /example-app/app/books/components/book-authors.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | import { Book } from '../models/book'; 4 | 5 | @Component({ 6 | selector: 'bc-book-authors', 7 | template: ` 8 |
Written By:
9 | 10 | {{ authors | bcAddCommas }} 11 | 12 | `, 13 | styles: [ 14 | ` 15 | h5 { 16 | margin-bottom: 5px; 17 | } 18 | `, 19 | ], 20 | }) 21 | export class BookAuthorsComponent { 22 | @Input() book: Book; 23 | 24 | get authors() { 25 | return this.book.volumeInfo.authors; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example-app/app/books/components/book-preview-list.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { Book } from '../models/book'; 3 | 4 | @Component({ 5 | selector: 'bc-book-preview-list', 6 | template: ` 7 | 8 | `, 9 | styles: [ 10 | ` 11 | :host { 12 | display: flex; 13 | flex-wrap: wrap; 14 | justify-content: center; 15 | } 16 | `, 17 | ], 18 | }) 19 | export class BookPreviewListComponent { 20 | @Input() books: Book[]; 21 | } 22 | -------------------------------------------------------------------------------- /example-app/app/books/components/book-search.ts: -------------------------------------------------------------------------------- 1 | import { Component, Output, Input, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'bc-book-search', 5 | template: ` 6 | 7 | Find a Book 8 | 9 | 10 | 11 | 12 | 13 | 14 | {{error}} 15 | 16 | `, 17 | styles: [ 18 | ` 19 | mat-card-title, 20 | mat-card-content, 21 | mat-card-footer { 22 | display: flex; 23 | justify-content: center; 24 | } 25 | 26 | mat-card-footer { 27 | color: #FF0000; 28 | padding: 5px 0; 29 | } 30 | 31 | .mat-form-field { 32 | min-width: 300px; 33 | } 34 | 35 | .mat-spinner { 36 | position: relative; 37 | top: 10px; 38 | left: 10px; 39 | opacity: 0.0; 40 | padding-left: 60px; // Make room for the spinner 41 | } 42 | 43 | .mat-spinner.show { 44 | opacity: 1.0; 45 | } 46 | `, 47 | ], 48 | }) 49 | export class BookSearchComponent { 50 | @Input() query = ''; 51 | @Input() searching = false; 52 | @Input() error = ''; 53 | @Output() search = new EventEmitter(); 54 | } 55 | -------------------------------------------------------------------------------- /example-app/app/books/components/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | import { RouterModule } from '@angular/router'; 5 | 6 | import { BookAuthorsComponent } from './book-authors'; 7 | import { BookDetailComponent } from './book-detail'; 8 | import { BookPreviewComponent } from './book-preview'; 9 | import { BookPreviewListComponent } from './book-preview-list'; 10 | import { BookSearchComponent } from './book-search'; 11 | 12 | import { PipesModule } from '../../shared/pipes'; 13 | import { MaterialModule } from '../../material'; 14 | 15 | export const COMPONENTS = [ 16 | BookAuthorsComponent, 17 | BookDetailComponent, 18 | BookPreviewComponent, 19 | BookPreviewListComponent, 20 | BookSearchComponent, 21 | ]; 22 | 23 | @NgModule({ 24 | imports: [ 25 | CommonModule, 26 | ReactiveFormsModule, 27 | MaterialModule, 28 | RouterModule, 29 | PipesModule, 30 | ], 31 | declarations: COMPONENTS, 32 | exports: COMPONENTS, 33 | }) 34 | export class ComponentsModule {} 35 | -------------------------------------------------------------------------------- /example-app/app/books/containers/__snapshots__/collection-page.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Collection Page should compile 1`] = ` 4 | 8 | 9 | 13 | 14 | 15 | 19 | My Collection 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | `; 39 | -------------------------------------------------------------------------------- /example-app/app/books/containers/__snapshots__/selected-book-page.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Selected Book Page should compile 1`] = ` 4 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | `; 24 | -------------------------------------------------------------------------------- /example-app/app/books/containers/__snapshots__/view-book-page.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`View Book Page should compile 1`] = ` 4 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | `; 27 | -------------------------------------------------------------------------------- /example-app/app/books/containers/collection-page.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; 2 | import { Store, select } from '@ngrx/store'; 3 | import { Observable } from 'rxjs/Observable'; 4 | 5 | import * as fromBooks from '../reducers'; 6 | import * as collection from '../actions/collection'; 7 | import { Book } from '../models/book'; 8 | 9 | @Component({ 10 | selector: 'bc-collection-page', 11 | changeDetection: ChangeDetectionStrategy.OnPush, 12 | template: ` 13 | 14 | My Collection 15 | 16 | 17 | 18 | `, 19 | /** 20 | * Container components are permitted to have just enough styles 21 | * to bring the view together. If the number of styles grow, 22 | * consider breaking them out into presentational 23 | * components. 24 | */ 25 | styles: [ 26 | ` 27 | mat-card-title { 28 | display: flex; 29 | justify-content: center; 30 | } 31 | `, 32 | ], 33 | }) 34 | export class CollectionPageComponent implements OnInit { 35 | books$: Observable; 36 | 37 | constructor(private store: Store) { 38 | this.books$ = store.pipe(select(fromBooks.getBookCollection)); 39 | } 40 | 41 | ngOnInit() { 42 | this.store.dispatch(new collection.Load()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example-app/app/books/containers/find-book-page.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Store, select } from '@ngrx/store'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { take } from 'rxjs/operators'; 5 | 6 | import * as fromBooks from '../reducers'; 7 | import * as book from '../actions/book'; 8 | import { Book } from '../models/book'; 9 | 10 | @Component({ 11 | selector: 'bc-find-book-page', 12 | changeDetection: ChangeDetectionStrategy.OnPush, 13 | template: ` 14 | 15 | 16 | `, 17 | }) 18 | export class FindBookPageComponent { 19 | searchQuery$: Observable; 20 | books$: Observable; 21 | loading$: Observable; 22 | error$: Observable; 23 | 24 | constructor(private store: Store) { 25 | this.searchQuery$ = store.pipe(select(fromBooks.getSearchQuery), take(1)); 26 | this.books$ = store.pipe(select(fromBooks.getSearchResults)); 27 | this.loading$ = store.pipe(select(fromBooks.getSearchLoading)); 28 | this.error$ = store.pipe(select(fromBooks.getSearchError)); 29 | } 30 | 31 | search(query: string) { 32 | this.store.dispatch(new book.Search(query)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example-app/app/books/containers/selected-book-page.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Store, select } from '@ngrx/store'; 3 | import { Observable } from 'rxjs/Observable'; 4 | 5 | import * as fromBooks from '../reducers'; 6 | import * as collection from '../actions/collection'; 7 | import { Book } from '../models/book'; 8 | 9 | @Component({ 10 | selector: 'bc-selected-book-page', 11 | changeDetection: ChangeDetectionStrategy.OnPush, 12 | template: ` 13 | 18 | 19 | `, 20 | }) 21 | export class SelectedBookPageComponent { 22 | book$: Observable; 23 | isSelectedBookInCollection$: Observable; 24 | 25 | constructor(private store: Store) { 26 | this.book$ = store.pipe(select(fromBooks.getSelectedBook)); 27 | this.isSelectedBookInCollection$ = store.pipe( 28 | select(fromBooks.isSelectedBookInCollection) 29 | ); 30 | } 31 | 32 | addToCollection(book: Book) { 33 | this.store.dispatch(new collection.AddBook(book)); 34 | } 35 | 36 | removeFromCollection(book: Book) { 37 | this.store.dispatch(new collection.RemoveBook(book)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example-app/app/books/containers/view-book-page.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { Store } from '@ngrx/store'; 3 | import { ActivatedRoute } from '@angular/router'; 4 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 5 | import { MatCardModule } from '@angular/material'; 6 | 7 | import { ViewBookPageComponent } from './view-book-page'; 8 | import * as book from '../actions/book'; 9 | import * as fromBooks from '../reducers'; 10 | import { SelectedBookPageComponent } from './selected-book-page'; 11 | import { BookDetailComponent } from '../components/book-detail'; 12 | import { BookAuthorsComponent } from '../components/book-authors'; 13 | import { AddCommasPipe } from '../../shared/pipes/add-commas'; 14 | 15 | describe('View Book Page', () => { 16 | let params = new BehaviorSubject({}); 17 | let fixture: ComponentFixture; 18 | let store: Store; 19 | let instance: ViewBookPageComponent; 20 | 21 | beforeEach(() => { 22 | TestBed.configureTestingModule({ 23 | imports: [MatCardModule], 24 | providers: [ 25 | { 26 | provide: ActivatedRoute, 27 | useValue: { params }, 28 | }, 29 | { 30 | provide: Store, 31 | useValue: { 32 | select: jest.fn(), 33 | next: jest.fn(), 34 | pipe: jest.fn(), 35 | }, 36 | }, 37 | ], 38 | declarations: [ 39 | ViewBookPageComponent, 40 | SelectedBookPageComponent, 41 | BookDetailComponent, 42 | BookAuthorsComponent, 43 | AddCommasPipe, 44 | ], 45 | }); 46 | 47 | fixture = TestBed.createComponent(ViewBookPageComponent); 48 | instance = fixture.componentInstance; 49 | store = TestBed.get(Store); 50 | }); 51 | 52 | it('should compile', () => { 53 | fixture.detectChanges(); 54 | 55 | expect(fixture).toMatchSnapshot(); 56 | }); 57 | 58 | it('should dispatch a book.Select action on init', () => { 59 | const action = new book.Select('2'); 60 | params.next({ id: '2' }); 61 | 62 | fixture.detectChanges(); 63 | 64 | expect(store.next).toHaveBeenLastCalledWith(action); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /example-app/app/books/containers/view-book-page.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { Store } from '@ngrx/store'; 4 | import { Subscription } from 'rxjs/Subscription'; 5 | import { map } from 'rxjs/operators'; 6 | 7 | import * as fromBooks from '../reducers'; 8 | import * as book from '../actions/book'; 9 | 10 | /** 11 | * Note: Container components are also reusable. Whether or not 12 | * a component is a presentation component or a container 13 | * component is an implementation detail. 14 | * 15 | * The View Book Page's responsibility is to map router params 16 | * to a 'Select' book action. Actually showing the selected 17 | * book remains a responsibility of the 18 | * SelectedBookPageComponent 19 | */ 20 | @Component({ 21 | selector: 'bc-view-book-page', 22 | changeDetection: ChangeDetectionStrategy.OnPush, 23 | template: ` 24 | 25 | `, 26 | }) 27 | export class ViewBookPageComponent implements OnDestroy { 28 | actionsSubscription: Subscription; 29 | 30 | constructor(store: Store, route: ActivatedRoute) { 31 | this.actionsSubscription = route.params 32 | .pipe(map(params => new book.Select(params.id))) 33 | .subscribe(store); 34 | } 35 | 36 | ngOnDestroy() { 37 | this.actionsSubscription.unsubscribe(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example-app/app/books/models/book.ts: -------------------------------------------------------------------------------- 1 | export interface Book { 2 | id: string; 3 | volumeInfo: { 4 | title: string; 5 | subtitle: string; 6 | authors: string[]; 7 | publisher: string; 8 | publishDate: string; 9 | description: string; 10 | averageRating: number; 11 | ratingsCount: number; 12 | imageLinks: { 13 | thumbnail: string; 14 | smallThumbnail: string; 15 | }; 16 | }; 17 | } 18 | 19 | export function generateMockBook(): Book { 20 | return { 21 | id: '1', 22 | volumeInfo: { 23 | title: 'title', 24 | subtitle: 'subtitle', 25 | authors: ['author'], 26 | publisher: 'publisher', 27 | publishDate: '', 28 | description: 'description', 29 | averageRating: 3, 30 | ratingsCount: 5, 31 | imageLinks: { 32 | thumbnail: 'string', 33 | smallThumbnail: 'string', 34 | }, 35 | }, 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /example-app/app/books/reducers/collection.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CollectionActionTypes, 3 | CollectionActions, 4 | } from './../actions/collection'; 5 | 6 | export interface State { 7 | loaded: boolean; 8 | loading: boolean; 9 | ids: string[]; 10 | } 11 | 12 | const initialState: State = { 13 | loaded: false, 14 | loading: false, 15 | ids: [], 16 | }; 17 | 18 | export function reducer( 19 | state = initialState, 20 | action: CollectionActions 21 | ): State { 22 | switch (action.type) { 23 | case CollectionActionTypes.Load: { 24 | return { 25 | ...state, 26 | loading: true, 27 | }; 28 | } 29 | 30 | case CollectionActionTypes.LoadSuccess: { 31 | return { 32 | loaded: true, 33 | loading: false, 34 | ids: action.payload.map(book => book.id), 35 | }; 36 | } 37 | 38 | case CollectionActionTypes.AddBookSuccess: 39 | case CollectionActionTypes.RemoveBookFail: { 40 | if (state.ids.indexOf(action.payload.id) > -1) { 41 | return state; 42 | } 43 | 44 | return { 45 | ...state, 46 | ids: [...state.ids, action.payload.id], 47 | }; 48 | } 49 | 50 | case CollectionActionTypes.RemoveBookSuccess: 51 | case CollectionActionTypes.AddBookFail: { 52 | return { 53 | ...state, 54 | ids: state.ids.filter(id => id !== action.payload.id), 55 | }; 56 | } 57 | 58 | default: { 59 | return state; 60 | } 61 | } 62 | } 63 | 64 | export const getLoaded = (state: State) => state.loaded; 65 | 66 | export const getLoading = (state: State) => state.loading; 67 | 68 | export const getIds = (state: State) => state.ids; 69 | -------------------------------------------------------------------------------- /example-app/app/books/reducers/search.ts: -------------------------------------------------------------------------------- 1 | import { BookActionTypes, BookActions } from '../actions/book'; 2 | 3 | export interface State { 4 | ids: string[]; 5 | loading: boolean; 6 | error: string; 7 | query: string; 8 | } 9 | 10 | const initialState: State = { 11 | ids: [], 12 | loading: false, 13 | error: '', 14 | query: '', 15 | }; 16 | 17 | export function reducer(state = initialState, action: BookActions): State { 18 | switch (action.type) { 19 | case BookActionTypes.Search: { 20 | const query = action.payload; 21 | 22 | if (query === '') { 23 | return { 24 | ids: [], 25 | loading: false, 26 | error: '', 27 | query, 28 | }; 29 | } 30 | 31 | return { 32 | ...state, 33 | loading: true, 34 | error: '', 35 | query, 36 | }; 37 | } 38 | 39 | case BookActionTypes.SearchComplete: { 40 | return { 41 | ids: action.payload.map(book => book.id), 42 | loading: false, 43 | error: '', 44 | query: state.query, 45 | }; 46 | } 47 | 48 | case BookActionTypes.SearchError: { 49 | return { 50 | ...state, 51 | loading: false, 52 | error: action.payload, 53 | }; 54 | } 55 | 56 | default: { 57 | return state; 58 | } 59 | } 60 | } 61 | 62 | export const getIds = (state: State) => state.ids; 63 | 64 | export const getQuery = (state: State) => state.query; 65 | 66 | export const getLoading = (state: State) => state.loading; 67 | 68 | export const getError = (state: State) => state.error; 69 | -------------------------------------------------------------------------------- /example-app/app/core/actions/layout.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | 3 | export enum LayoutActionTypes { 4 | OpenSidenav = '[Layout] Open Sidenav', 5 | CloseSidenav = '[Layout] Close Sidenav', 6 | } 7 | 8 | export class OpenSidenav implements Action { 9 | readonly type = LayoutActionTypes.OpenSidenav; 10 | } 11 | 12 | export class CloseSidenav implements Action { 13 | readonly type = LayoutActionTypes.CloseSidenav; 14 | } 15 | 16 | export type LayoutActions = OpenSidenav | CloseSidenav; 17 | -------------------------------------------------------------------------------- /example-app/app/core/components/layout.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'bc-layout', 5 | template: ` 6 | 7 | 8 | 9 | 10 | 11 | `, 12 | styles: [ 13 | ` 14 | mat-sidenav-container { 15 | background: rgba(0, 0, 0, 0.03); 16 | } 17 | 18 | *, /deep/ * { 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | `, 23 | ], 24 | }) 25 | export class LayoutComponent {} 26 | -------------------------------------------------------------------------------- /example-app/app/core/components/nav-item.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'bc-nav-item', 5 | template: ` 6 | 7 | {{ icon }} 8 | 9 | {{ hint }} 10 | 11 | `, 12 | styles: [ 13 | ` 14 | .secondary { 15 | color: rgba(0, 0, 0, 0.54); 16 | } 17 | `, 18 | ], 19 | }) 20 | export class NavItemComponent { 21 | @Input() icon = ''; 22 | @Input() hint = ''; 23 | @Input() routerLink: string | any[] = '/'; 24 | @Output() navigate = new EventEmitter(); 25 | } 26 | -------------------------------------------------------------------------------- /example-app/app/core/components/sidenav.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'bc-sidenav', 5 | template: ` 6 | 7 | 8 | 9 | 10 | 11 | `, 12 | styles: [ 13 | ` 14 | mat-sidenav { 15 | width: 300px; 16 | } 17 | `, 18 | ], 19 | }) 20 | export class SidenavComponent { 21 | @Input() open = false; 22 | } 23 | -------------------------------------------------------------------------------- /example-app/app/core/components/toolbar.ts: -------------------------------------------------------------------------------- 1 | import { Component, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'bc-toolbar', 5 | template: ` 6 | 7 | 10 | 11 | 12 | `, 13 | }) 14 | export class ToolbarComponent { 15 | @Output() openMenu = new EventEmitter(); 16 | } 17 | -------------------------------------------------------------------------------- /example-app/app/core/containers/not-found-page.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'bc-not-found-page', 5 | changeDetection: ChangeDetectionStrategy.OnPush, 6 | template: ` 7 | 8 | 404: Not Found 9 | 10 |

Hey! It looks like this page doesn't exist yet.

11 |
12 | 13 | 14 | 15 |
16 | `, 17 | styles: [ 18 | ` 19 | :host { 20 | text-align: center; 21 | } 22 | `, 23 | ], 24 | }) 25 | export class NotFoundPageComponent {} 26 | -------------------------------------------------------------------------------- /example-app/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { AppComponent } from './containers/app'; 6 | import { NotFoundPageComponent } from './containers/not-found-page'; 7 | import { LayoutComponent } from './components/layout'; 8 | import { NavItemComponent } from './components/nav-item'; 9 | import { SidenavComponent } from './components/sidenav'; 10 | import { ToolbarComponent } from './components/toolbar'; 11 | import { MaterialModule } from '../material'; 12 | 13 | import { GoogleBooksService } from './services/google-books'; 14 | 15 | export const COMPONENTS = [ 16 | AppComponent, 17 | NotFoundPageComponent, 18 | LayoutComponent, 19 | NavItemComponent, 20 | SidenavComponent, 21 | ToolbarComponent, 22 | ]; 23 | 24 | @NgModule({ 25 | imports: [CommonModule, RouterModule, MaterialModule], 26 | declarations: COMPONENTS, 27 | exports: COMPONENTS, 28 | }) 29 | export class CoreModule { 30 | static forRoot() { 31 | return { 32 | ngModule: CoreModule, 33 | providers: [GoogleBooksService], 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example-app/app/core/reducers/layout.ts: -------------------------------------------------------------------------------- 1 | import { LayoutActionTypes, LayoutActions } from '../actions/layout'; 2 | 3 | export interface State { 4 | showSidenav: boolean; 5 | } 6 | 7 | const initialState: State = { 8 | showSidenav: false, 9 | }; 10 | 11 | export function reducer( 12 | state: State = initialState, 13 | action: LayoutActions 14 | ): State { 15 | switch (action.type) { 16 | case LayoutActionTypes.CloseSidenav: 17 | return { 18 | showSidenav: false, 19 | }; 20 | 21 | case LayoutActionTypes.OpenSidenav: 22 | return { 23 | showSidenav: true, 24 | }; 25 | 26 | default: 27 | return state; 28 | } 29 | } 30 | 31 | export const getShowSidenav = (state: State) => state.showSidenav; 32 | -------------------------------------------------------------------------------- /example-app/app/core/services/google-books.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { cold } from 'jasmine-marbles'; 4 | import { GoogleBooksService } from './google-books'; 5 | 6 | describe('Service: GoogleBooks', () => { 7 | let service: GoogleBooksService; 8 | let http: HttpClient; 9 | 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | providers: [ 13 | { provide: HttpClient, useValue: { get: jest.fn() } }, 14 | GoogleBooksService, 15 | ], 16 | }); 17 | 18 | service = TestBed.get(GoogleBooksService); 19 | http = TestBed.get(HttpClient); 20 | }); 21 | 22 | const data = { 23 | title: 'Book Title', 24 | author: 'John Smith', 25 | volumeId: '12345', 26 | }; 27 | 28 | const books = { 29 | items: [ 30 | { id: '12345', volumeInfo: { title: 'Title' } }, 31 | { id: '67890', volumeInfo: { title: 'Another Title' } }, 32 | ], 33 | }; 34 | 35 | const queryTitle = 'Book Title'; 36 | 37 | it('should call the search api and return the search results', () => { 38 | const response = cold('-a|', { a: books }); 39 | const expected = cold('-b|', { b: books.items }); 40 | http.get = jest.fn(() => response); 41 | 42 | expect(service.searchBooks(queryTitle)).toBeObservable(expected); 43 | expect(http.get).toHaveBeenCalledWith( 44 | `https://www.googleapis.com/books/v1/volumes?q=${queryTitle}` 45 | ); 46 | }); 47 | 48 | it('should retrieve the book from the volumeId', () => { 49 | const response = cold('-a|', { a: data }); 50 | const expected = cold('-b|', { b: data }); 51 | http.get = jest.fn(() => response); 52 | 53 | expect(service.retrieveBook(data.volumeId)).toBeObservable(expected); 54 | expect(http.get).toHaveBeenCalledWith( 55 | `https://www.googleapis.com/books/v1/volumes/${data.volumeId}` 56 | ); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /example-app/app/core/services/google-books.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { map } from 'rxjs/operators'; 5 | import { Book } from '../../books/models/book'; 6 | 7 | @Injectable() 8 | export class GoogleBooksService { 9 | private API_PATH = 'https://www.googleapis.com/books/v1/volumes'; 10 | 11 | constructor(private http: HttpClient) {} 12 | 13 | searchBooks(queryTitle: string): Observable { 14 | return this.http 15 | .get<{ items: Book[] }>(`${this.API_PATH}?q=${queryTitle}`) 16 | .pipe(map(books => books.items || [])); 17 | } 18 | 19 | retrieveBook(volumeId: string): Observable { 20 | return this.http.get(`${this.API_PATH}/${volumeId}`); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example-app/app/db.ts: -------------------------------------------------------------------------------- 1 | import { DBSchema } from '@ngrx/db'; 2 | 3 | /** 4 | * ngrx/db uses a simple schema config object to initialize stores in IndexedDB. 5 | */ 6 | export const schema: DBSchema = { 7 | version: 1, 8 | name: 'books_app', 9 | stores: { 10 | books: { 11 | autoIncrement: true, 12 | primaryKey: 'id', 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /example-app/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app.module'; 2 | -------------------------------------------------------------------------------- /example-app/app/material/index.ts: -------------------------------------------------------------------------------- 1 | export * from './material.module'; 2 | -------------------------------------------------------------------------------- /example-app/app/material/material.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { 4 | MatInputModule, 5 | MatCardModule, 6 | MatButtonModule, 7 | MatSidenavModule, 8 | MatListModule, 9 | MatIconModule, 10 | MatToolbarModule, 11 | MatProgressSpinnerModule, 12 | } from '@angular/material'; 13 | 14 | @NgModule({ 15 | imports: [ 16 | MatInputModule, 17 | MatCardModule, 18 | MatButtonModule, 19 | MatSidenavModule, 20 | MatListModule, 21 | MatIconModule, 22 | MatToolbarModule, 23 | MatProgressSpinnerModule, 24 | ], 25 | exports: [ 26 | MatInputModule, 27 | MatCardModule, 28 | MatButtonModule, 29 | MatSidenavModule, 30 | MatListModule, 31 | MatIconModule, 32 | MatToolbarModule, 33 | MatProgressSpinnerModule, 34 | ], 35 | }) 36 | export class MaterialModule {} 37 | -------------------------------------------------------------------------------- /example-app/app/routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { AuthGuard } from './auth/services/auth-guard.service'; 3 | import { NotFoundPageComponent } from './core/containers/not-found-page'; 4 | 5 | export const routes: Routes = [ 6 | { path: '', redirectTo: '/books', pathMatch: 'full' }, 7 | { 8 | path: 'books', 9 | loadChildren: './books/books.module#BooksModule', 10 | canActivate: [AuthGuard], 11 | }, 12 | { path: '**', component: NotFoundPageComponent }, 13 | ]; 14 | -------------------------------------------------------------------------------- /example-app/app/shared/pipes/add-commas.spec.ts: -------------------------------------------------------------------------------- 1 | import { AddCommasPipe } from './add-commas'; 2 | 3 | describe('Pipe: Add Commas', () => { 4 | let pipe: AddCommasPipe; 5 | 6 | beforeEach(() => { 7 | pipe = new AddCommasPipe(); 8 | }); 9 | 10 | it('should transform ["Rick"] to "Rick"', () => { 11 | expect(pipe.transform(['Rick'])).toEqual('Rick'); 12 | }); 13 | 14 | it('should transform ["Jeremy", "Andrew"] to "Jeremy and Andrew"', () => { 15 | expect(pipe.transform(['Jeremy', 'Andrew'])).toEqual('Jeremy and Andrew'); 16 | }); 17 | 18 | it('should transform ["Kim", "Ryan", "Amanda"] to "Kim, Ryan, and Amanda"', () => { 19 | expect(pipe.transform(['Kim', 'Ryan', 'Amanda'])).toEqual( 20 | 'Kim, Ryan, and Amanda' 21 | ); 22 | }); 23 | 24 | it('transforms undefined to "Author Unknown"', () => { 25 | expect(pipe.transform(undefined)).toEqual('Author Unknown'); 26 | }); 27 | 28 | it('transforms [] to "Author Unknown"', () => { 29 | expect(pipe.transform([])).toEqual('Author Unknown'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /example-app/app/shared/pipes/add-commas.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ name: 'bcAddCommas' }) 4 | export class AddCommasPipe implements PipeTransform { 5 | transform(authors: null | string[]) { 6 | if (!authors) { 7 | return 'Author Unknown'; 8 | } 9 | 10 | switch (authors.length) { 11 | case 0: 12 | return 'Author Unknown'; 13 | case 1: 14 | return authors[0]; 15 | case 2: 16 | return authors.join(' and '); 17 | default: 18 | const last = authors[authors.length - 1]; 19 | const remaining = authors.slice(0, -1); 20 | return `${remaining.join(', ')}, and ${last}`; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example-app/app/shared/pipes/ellipsis.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ name: 'bcEllipsis' }) 4 | export class EllipsisPipe implements PipeTransform { 5 | transform(str: string, strLength: number = 250) { 6 | const withoutHtml = str.replace(/(<([^>]+)>)/gi, ''); 7 | 8 | if (str.length >= strLength) { 9 | return `${withoutHtml.slice(0, strLength)}...`; 10 | } 11 | 12 | return withoutHtml; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example-app/app/shared/pipes/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { AddCommasPipe } from './add-commas'; 4 | import { EllipsisPipe } from './ellipsis'; 5 | 6 | export const PIPES = [AddCommasPipe, EllipsisPipe]; 7 | 8 | @NgModule({ 9 | declarations: PIPES, 10 | exports: PIPES, 11 | }) 12 | export class PipesModule {} 13 | -------------------------------------------------------------------------------- /example-app/app/shared/utils.ts: -------------------------------------------------------------------------------- 1 | import { RouterStateSerializer } from '@ngrx/router-store'; 2 | import { RouterStateSnapshot, Params } from '@angular/router'; 3 | 4 | /** 5 | * The RouterStateSerializer takes the current RouterStateSnapshot 6 | * and returns any pertinent information needed. The snapshot contains 7 | * all information about the state of the router at the given point in time. 8 | * The entire snapshot is complex and not always needed. In this case, you only 9 | * need the URL and query parameters from the snapshot in the store. Other items could be 10 | * returned such as route parameters and static route data. 11 | */ 12 | 13 | export interface RouterStateUrl { 14 | url: string; 15 | params: Params; 16 | queryParams: Params; 17 | } 18 | 19 | export class CustomRouterStateSerializer 20 | implements RouterStateSerializer { 21 | serialize(routerState: RouterStateSnapshot): RouterStateUrl { 22 | let route = routerState.root; 23 | 24 | while (route.firstChild) { 25 | route = route.firstChild; 26 | } 27 | 28 | const { url, root: { queryParams } } = routerState; 29 | const { params } = route; 30 | 31 | // Only return an object including the URL, params and query params 32 | // instead of the entire snapshot 33 | return { url, params, queryParams }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example-app/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxJS-CN/ngrx-docs-cn/2b93e8260b4f1e82fb41811795086dcf40cb13a2/example-app/assets/.gitkeep -------------------------------------------------------------------------------- /example-app/assets/.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxJS-CN/ngrx-docs-cn/2b93e8260b4f1e82fb41811795086dcf40cb13a2/example-app/assets/.npmignore -------------------------------------------------------------------------------- /example-app/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /example-app/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false, 8 | }; 9 | -------------------------------------------------------------------------------- /example-app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxJS-CN/ngrx-docs-cn/2b93e8260b4f1e82fb41811795086dcf40cb13a2/example-app/favicon.ico -------------------------------------------------------------------------------- /example-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Book Collection 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Loading... 14 | 15 | 16 | -------------------------------------------------------------------------------- /example-app/main.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { enableProdMode } from '@angular/core'; 5 | import { environment } from './environments/environment'; 6 | import { AppModule } from './app/app.module'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule); 13 | -------------------------------------------------------------------------------- /example-app/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular and is loaded before 2 | // the app. You can add your own extra polyfills to this file. 3 | import 'core-js/es6/symbol'; 4 | import 'core-js/es6/object'; 5 | import 'core-js/es6/function'; 6 | import 'core-js/es6/parse-int'; 7 | import 'core-js/es6/parse-float'; 8 | import 'core-js/es6/number'; 9 | import 'core-js/es6/math'; 10 | import 'core-js/es6/string'; 11 | import 'core-js/es6/date'; 12 | import 'core-js/es6/array'; 13 | import 'core-js/es6/regexp'; 14 | import 'core-js/es6/map'; 15 | import 'core-js/es6/set'; 16 | import 'core-js/es6/reflect'; 17 | 18 | import 'core-js/es7/reflect'; 19 | import 'zone.js/dist/zone'; 20 | 21 | import 'hammerjs'; 22 | -------------------------------------------------------------------------------- /example-app/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import "~@angular/material/prebuilt-themes/deeppurple-amber.css"; 3 | 4 | * { 5 | box-sizing: border-box; 6 | } 7 | 8 | html { 9 | -webkit-font-smoothing: antialiased; 10 | -ms-overflow-style: none; 11 | overflow: auto; 12 | } 13 | 14 | .mat-progress-spinner svg { 15 | width: 30px !important; 16 | height: 30px !important; 17 | } 18 | -------------------------------------------------------------------------------- /example-app/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting, 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // fixes typing errors in Atom editor 16 | import {} from 'jasmine'; 17 | 18 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 19 | declare var __karma__: any; 20 | declare var require: any; 21 | 22 | // Prevent Karma from running prematurely. 23 | __karma__.loaded = function() {}; 24 | 25 | // First, initialize the Angular testing environment. 26 | getTestBed().initTestEnvironment( 27 | BrowserDynamicTestingModule, 28 | platformBrowserDynamicTesting() 29 | ); 30 | // Then we find all the tests. 31 | const context = require.context('./', true, /\.spec\.ts$/); 32 | // And load the modules. 33 | context.keys().map(context); 34 | // Finally, start Karma to run the tests. 35 | __karma__.start(); 36 | -------------------------------------------------------------------------------- /example-app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "declaration": false, 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "lib": [ 9 | "es2017", 10 | "dom" 11 | ], 12 | "outDir": "../out-tsc/app", 13 | "target": "es5", 14 | "module": "es2015", 15 | "types": [], 16 | "baseUrl": ".", 17 | "rootDir": "../", 18 | "paths": { 19 | "@ngrx/effects": [ 20 | "../modules/effects" 21 | ], 22 | "@ngrx/store": [ 23 | "../modules/store" 24 | ], 25 | "@ngrx/store-devtools": [ 26 | "../modules/store-devtools" 27 | ], 28 | "@ngrx/router-store": [ 29 | "../modules/router-store" 30 | ], 31 | "@ngrx/entity": [ 32 | "../modules/entity" 33 | ] 34 | } 35 | }, 36 | "exclude": [ 37 | "../node_modules", 38 | "test.ts", 39 | "**/*.spec.ts" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /example-app/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "declaration": false, 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "lib": [ 9 | "es2017", 10 | "dom" 11 | ], 12 | "outDir": "../out-tsc/spec", 13 | "module": "commonjs", 14 | "target": "es6", 15 | "types": [ 16 | "jasmine", 17 | "node" 18 | ], 19 | "baseUrl": ".", 20 | "rootDir": "../" 21 | }, 22 | "files": [ 23 | "test.ts" 24 | ], 25 | "include": [ 26 | "**/*.spec.ts" 27 | ] 28 | } -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | files: [ 19 | { pattern: './example-app/test.ts', watched: false } 20 | ], 21 | preprocessors: { 22 | './example-app/test.ts': ['@angular/cli'] 23 | }, 24 | mime: { 25 | 'text/x-typescript': ['ts','tsx'] 26 | }, 27 | coverageIstanbulReporter: { 28 | reports: [ 'html', 'lcovonly', 'text-summary' ], 29 | fixWebpackSourcePaths: true 30 | }, 31 | angularCli: { 32 | environment: 'dev' 33 | }, 34 | reporters: config.angularCli && config.angularCli.codeCoverage 35 | ? ['progress', 'coverage-istanbul', 'kjhtml'] 36 | : ['progress', 'kjhtml'], 37 | port: 9876, 38 | colors: true, 39 | logLevel: config.LOG_INFO, 40 | autoWatch: true, 41 | browsers: ['Chrome'], 42 | singleRun: false 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0", 3 | "packages": [ 4 | "modules/*" 5 | ], 6 | "version": "5.1.0", 7 | "npmClient": "yarn" 8 | } 9 | -------------------------------------------------------------------------------- /modules/effects/BUILD: -------------------------------------------------------------------------------- 1 | load("//tools:defaults.bzl", "ts_library") 2 | 3 | ts_library( 4 | name = "effects", 5 | srcs = glob([ 6 | "*.ts", 7 | "src/**/*.ts", 8 | ]), 9 | module_name = "@ngrx/effects", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "//modules/store", 13 | "@rxjs", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /modules/effects/README.md: -------------------------------------------------------------------------------- 1 | @ngrx/effects 2 | ======= 3 | 4 | The sources for this package are in the main [ngrx/platform](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo. 5 | 6 | License: MIT 7 | -------------------------------------------------------------------------------- /modules/effects/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DO NOT EDIT 3 | * 4 | * This file is automatically generated at build 5 | */ 6 | 7 | export * from './public_api'; 8 | -------------------------------------------------------------------------------- /modules/effects/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngrx/effects", 3 | "version": "5.1.0", 4 | "description": "Side effect model for @ngrx/store", 5 | "module": "@ngrx/effects.es5.js", 6 | "es2015": "@ngrx/effects.js", 7 | "main": "bundles/effects.umd.js", 8 | "typings": "effects.d.ts", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/ngrx/platform.git" 12 | }, 13 | "authors": [ 14 | "Mike Ryan" 15 | ], 16 | "license": "MIT", 17 | "peerDependencies": { 18 | "@angular/core": "^5.0.0", 19 | "@ngrx/store": "^5.0.0", 20 | "rxjs": "^5.5.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /modules/effects/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './src/index'; 2 | -------------------------------------------------------------------------------- /modules/effects/rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | entry: './dist/effects/@ngrx/effects.es5.js', 3 | dest: './dist/effects/bundles/effects.umd.js', 4 | format: 'umd', 5 | exports: 'named', 6 | moduleName: 'ngrx.effects', 7 | globals: { 8 | '@angular/core': 'ng.core', 9 | '@ngrx/store': 'ngrx.store', 10 | 'rxjs/Observable': 'Rx', 11 | 'rxjs/Subscription': 'Rx', 12 | 'rxjs/operator/filter': 'Rx.Observable.prototype', 13 | 'rxjs/operator/ignoreElements': 'Rx.Observable.prototype', 14 | 'rxjs/observable/merge': 'Rx.Observable' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /modules/effects/spec/BUILD: -------------------------------------------------------------------------------- 1 | load("//tools:defaults.bzl", "ts_test_library", "jasmine_node_test") 2 | 3 | ts_test_library( 4 | name = "test_lib", 5 | srcs = glob( 6 | [ 7 | "**/*.ts", 8 | ], 9 | exclude = ["ngc/**/*.ts"], 10 | ), 11 | deps = [ 12 | "//modules/effects", 13 | "//modules/store", 14 | "@rxjs", 15 | ], 16 | ) 17 | 18 | jasmine_node_test( 19 | name = "test", 20 | deps = [ 21 | ":test_lib", 22 | "//modules/effects", 23 | "//modules/store", 24 | "@rxjs", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /modules/effects/spec/effects_resolver.spec.ts: -------------------------------------------------------------------------------- 1 | import 'rxjs/add/observable/of'; 2 | import { Observable } from 'rxjs/Observable'; 3 | import { Effect, mergeEffects } from '../'; 4 | 5 | describe('mergeEffects', () => {}); 6 | -------------------------------------------------------------------------------- /modules/effects/spec/effects_root_module.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { 3 | Store, 4 | StoreModule, 5 | ActionReducer, 6 | MetaReducer, 7 | Action, 8 | INIT, 9 | } from '@ngrx/store'; 10 | import { ROOT_EFFECTS_INIT } from '../src/effects_root_module'; 11 | import { EffectsModule } from '../src/effects_module'; 12 | 13 | describe('Effects Root Module', () => { 14 | const foo = 'foo'; 15 | const reducer = jasmine.createSpy('reducer').and.returnValue(foo); 16 | 17 | beforeEach(() => { 18 | reducer.calls.reset(); 19 | }); 20 | 21 | it('dispatches the root effects init action', () => { 22 | TestBed.configureTestingModule({ 23 | imports: [ 24 | StoreModule.forRoot({ reducer }, { initialState: { reducer: foo } }), 25 | EffectsModule.forRoot([]), 26 | ], 27 | }); 28 | 29 | const store = TestBed.get(Store); 30 | 31 | expect(reducer).toHaveBeenCalledWith(foo, { 32 | type: INIT, 33 | }); 34 | expect(reducer).toHaveBeenCalledWith(foo, { 35 | type: ROOT_EFFECTS_INIT, 36 | }); 37 | }); 38 | 39 | it("doesn't dispatch the root effects init action when EffectsModule isn't used", () => { 40 | TestBed.configureTestingModule({ 41 | imports: [ 42 | StoreModule.forRoot({ reducer }, { initialState: { reducer: foo } }), 43 | ], 44 | }); 45 | 46 | const store = TestBed.get(Store); 47 | 48 | expect(reducer).toHaveBeenCalledWith(foo, { 49 | type: INIT, 50 | }); 51 | expect(reducer).not.toHaveBeenCalledWith(foo, { 52 | type: ROOT_EFFECTS_INIT, 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /modules/effects/spec/ngc/ngc.spec.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | import { of } from 'rxjs/observable/of'; 3 | import { NgModule, Component, Injectable } from '@angular/core'; 4 | import { platformDynamicServer } from '@angular/platform-server'; 5 | import { BrowserModule } from '@angular/platform-browser'; 6 | import { Store, StoreModule, combineReducers } from '../../../store'; 7 | import { EffectsModule, Effect, Actions } from '../../'; 8 | 9 | @Injectable() 10 | export class NgcSpecFeatureEffects { 11 | constructor(actions$: Actions) {} 12 | 13 | @Effect() run$ = of({ type: 'NgcSpecFeatureAction' }); 14 | } 15 | 16 | @NgModule({ 17 | imports: [EffectsModule.forFeature([NgcSpecFeatureEffects])], 18 | }) 19 | export class NgcSpecFeatureModule {} 20 | 21 | @Injectable() 22 | export class NgcSpecRootEffects { 23 | constructor(actions$: Actions) {} 24 | 25 | @Effect() run$ = of({ type: 'NgcSpecRootAction' }); 26 | } 27 | 28 | export interface AppState { 29 | count: number; 30 | } 31 | 32 | @Component({ 33 | selector: 'ngc-spec-component', 34 | template: ` 35 |

Hello Effects

36 | `, 37 | }) 38 | export class NgcSpecComponent {} 39 | 40 | @NgModule({ 41 | imports: [ 42 | BrowserModule, 43 | StoreModule.forRoot({}), 44 | EffectsModule.forRoot([NgcSpecRootEffects]), 45 | NgcSpecFeatureModule, 46 | ], 47 | declarations: [NgcSpecComponent], 48 | bootstrap: [NgcSpecComponent], 49 | }) 50 | export class NgcSpecModule {} 51 | -------------------------------------------------------------------------------- /modules/effects/spec/ngc/tsconfig.ngc.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "outDir": "./output", 9 | "lib": ["es2015", "dom"], 10 | "baseUrl": ".", 11 | "paths": { 12 | "@ngrx/store": ["../../../store"] 13 | } 14 | }, 15 | "files": [ 16 | "ngc.spec.ts" 17 | ], 18 | "angularCompilerOptions": { 19 | "genDir": "ngfactory" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /modules/effects/src/actions.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { Action, ScannedActionsSubject } from '@ngrx/store'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { Operator } from 'rxjs/Operator'; 5 | import { filter } from 'rxjs/operators'; 6 | import { OperatorFunction } from 'rxjs/interfaces'; 7 | 8 | @Injectable() 9 | export class Actions extends Observable { 10 | constructor(@Inject(ScannedActionsSubject) source?: Observable) { 11 | super(); 12 | 13 | if (source) { 14 | this.source = source; 15 | } 16 | } 17 | 18 | lift(operator: Operator): Observable { 19 | const observable = new Actions(); 20 | observable.source = this; 21 | observable.operator = operator; 22 | return observable; 23 | } 24 | 25 | ofType(...allowedTypes: string[]): Actions { 26 | return ofType(...allowedTypes)(this as Actions) as Actions; 27 | } 28 | } 29 | 30 | export function ofType(...allowedTypes: string[]) { 31 | return filter((action: Action): action is T => 32 | allowedTypes.some(type => type === action.type) 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /modules/effects/src/effect_notification.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | import { Notification } from 'rxjs/Notification'; 3 | import { Action } from '@ngrx/store'; 4 | import { ErrorHandler } from '@angular/core'; 5 | 6 | export interface EffectNotification { 7 | effect: Observable | (() => Observable); 8 | propertyName: string; 9 | sourceName: string; 10 | sourceInstance: any; 11 | notification: Notification; 12 | } 13 | 14 | export function verifyOutput( 15 | output: EffectNotification, 16 | reporter: ErrorHandler 17 | ) { 18 | reportErrorThrown(output, reporter); 19 | reportInvalidActions(output, reporter); 20 | } 21 | 22 | function reportErrorThrown(output: EffectNotification, reporter: ErrorHandler) { 23 | if (output.notification.kind === 'E') { 24 | reporter.handleError(output.notification.error); 25 | } 26 | } 27 | 28 | function reportInvalidActions( 29 | output: EffectNotification, 30 | reporter: ErrorHandler 31 | ) { 32 | if (output.notification.kind === 'N') { 33 | const action = output.notification.value; 34 | const isInvalidAction = !isAction(action); 35 | 36 | if (isInvalidAction) { 37 | reporter.handleError( 38 | new Error( 39 | `Effect ${getEffectName(output)} dispatched an invalid action: ${ 40 | action 41 | }` 42 | ) 43 | ); 44 | } 45 | } 46 | } 47 | 48 | function isAction(action: any): action is Action { 49 | return action && action.type && typeof action.type === 'string'; 50 | } 51 | 52 | function getEffectName({ 53 | propertyName, 54 | sourceInstance, 55 | sourceName, 56 | }: EffectNotification) { 57 | const isMethod = typeof sourceInstance[propertyName] === 'function'; 58 | 59 | return `"${sourceName}.${propertyName}${isMethod ? '()' : ''}"`; 60 | } 61 | -------------------------------------------------------------------------------- /modules/effects/src/effect_sources.ts: -------------------------------------------------------------------------------- 1 | import { groupBy, GroupedObservable } from 'rxjs/operator/groupBy'; 2 | import { mergeMap } from 'rxjs/operator/mergeMap'; 3 | import { exhaustMap } from 'rxjs/operator/exhaustMap'; 4 | import { map } from 'rxjs/operator/map'; 5 | import { dematerialize } from 'rxjs/operator/dematerialize'; 6 | import { filter } from 'rxjs/operator/filter'; 7 | import { concat } from 'rxjs/observable/concat'; 8 | import { Observable } from 'rxjs/Observable'; 9 | import { Subject } from 'rxjs/Subject'; 10 | import { Notification } from 'rxjs/Notification'; 11 | import { ErrorHandler, Injectable } from '@angular/core'; 12 | import { Action } from '@ngrx/store'; 13 | import { EffectNotification, verifyOutput } from './effect_notification'; 14 | import { getSourceForInstance } from './effects_metadata'; 15 | import { resolveEffectSource } from './effects_resolver'; 16 | 17 | @Injectable() 18 | export class EffectSources extends Subject { 19 | constructor(private errorHandler: ErrorHandler) { 20 | super(); 21 | } 22 | 23 | addEffects(effectSourceInstance: any) { 24 | this.next(effectSourceInstance); 25 | } 26 | 27 | /** 28 | * @internal 29 | */ 30 | toActions(): Observable { 31 | return mergeMap.call( 32 | groupBy.call(this, getSourceForInstance), 33 | (source$: GroupedObservable) => 34 | dematerialize.call( 35 | filter.call( 36 | map.call( 37 | exhaustMap.call(source$, resolveEffectSource), 38 | (output: EffectNotification) => { 39 | verifyOutput(output, this.errorHandler); 40 | 41 | return output.notification; 42 | } 43 | ), 44 | (notification: Notification) => notification.kind === 'N' 45 | ) 46 | ) 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /modules/effects/src/effects_feature_module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, Inject, Optional } from '@angular/core'; 2 | import { StoreRootModule, StoreFeatureModule } from '@ngrx/store'; 3 | import { EffectsRootModule } from './effects_root_module'; 4 | import { FEATURE_EFFECTS } from './tokens'; 5 | 6 | @NgModule({}) 7 | export class EffectsFeatureModule { 8 | constructor( 9 | private root: EffectsRootModule, 10 | @Inject(FEATURE_EFFECTS) effectSourceGroups: any[][], 11 | @Optional() storeRootModule: StoreRootModule, 12 | @Optional() storeFeatureModule: StoreFeatureModule 13 | ) { 14 | effectSourceGroups.forEach(group => 15 | group.forEach(effectSourceInstance => 16 | root.addEffects(effectSourceInstance) 17 | ) 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /modules/effects/src/effects_metadata.ts: -------------------------------------------------------------------------------- 1 | import { merge } from 'rxjs/observable/merge'; 2 | import { ignoreElements } from 'rxjs/operator/ignoreElements'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { compose } from '@ngrx/store'; 5 | 6 | const METADATA_KEY = '__@ngrx/effects__'; 7 | const r: any = Reflect; 8 | 9 | export interface EffectMetadata { 10 | propertyName: string; 11 | dispatch: boolean; 12 | } 13 | 14 | function getEffectMetadataEntries(sourceProto: any): EffectMetadata[] { 15 | return sourceProto.constructor[METADATA_KEY] || []; 16 | } 17 | 18 | function setEffectMetadataEntries(sourceProto: any, entries: EffectMetadata[]) { 19 | const constructor = sourceProto.constructor; 20 | const meta: EffectMetadata[] = constructor.hasOwnProperty(METADATA_KEY) 21 | ? (constructor as any)[METADATA_KEY] 22 | : Object.defineProperty(constructor, METADATA_KEY, { value: [] })[ 23 | METADATA_KEY 24 | ]; 25 | Array.prototype.push.apply(meta, entries); 26 | } 27 | 28 | export function Effect({ dispatch } = { dispatch: true }): PropertyDecorator { 29 | return function(target: any, propertyName: string) { 30 | const metadata: EffectMetadata = { propertyName, dispatch }; 31 | setEffectMetadataEntries(target, [metadata]); 32 | } /*TODO(#823)*/ as any; 33 | } 34 | 35 | export function getSourceForInstance(instance: Object): any { 36 | return Object.getPrototypeOf(instance); 37 | } 38 | 39 | export const getSourceMetadata = compose( 40 | getEffectMetadataEntries, 41 | getSourceForInstance 42 | ); 43 | 44 | export type EffectsMetadata = { 45 | [key in keyof T]?: 46 | | undefined 47 | | { 48 | dispatch: boolean; 49 | } 50 | }; 51 | 52 | export function getEffectsMetadata(instance: T): EffectsMetadata { 53 | const metadata: EffectsMetadata = {}; 54 | 55 | getSourceMetadata(instance).forEach(({ propertyName, dispatch }) => { 56 | (metadata /*TODO(#823)*/ as any)[propertyName] = { dispatch }; 57 | }); 58 | 59 | return metadata; 60 | } 61 | -------------------------------------------------------------------------------- /modules/effects/src/effects_module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders, Type } from '@angular/core'; 2 | import { EffectSources } from './effect_sources'; 3 | import { Actions } from './actions'; 4 | import { ROOT_EFFECTS, FEATURE_EFFECTS } from './tokens'; 5 | import { EffectsFeatureModule } from './effects_feature_module'; 6 | import { EffectsRootModule } from './effects_root_module'; 7 | import { EffectsRunner } from './effects_runner'; 8 | 9 | @NgModule({}) 10 | export class EffectsModule { 11 | static forFeature(featureEffects: Type[]): ModuleWithProviders { 12 | return { 13 | ngModule: EffectsFeatureModule, 14 | providers: [ 15 | featureEffects, 16 | { 17 | provide: FEATURE_EFFECTS, 18 | multi: true, 19 | deps: featureEffects, 20 | useFactory: createSourceInstances, 21 | }, 22 | ], 23 | }; 24 | } 25 | 26 | static forRoot(rootEffects: Type[]): ModuleWithProviders { 27 | return { 28 | ngModule: EffectsRootModule, 29 | providers: [ 30 | EffectsRunner, 31 | EffectSources, 32 | Actions, 33 | rootEffects, 34 | { 35 | provide: ROOT_EFFECTS, 36 | deps: rootEffects, 37 | useFactory: createSourceInstances, 38 | }, 39 | ], 40 | }; 41 | } 42 | } 43 | 44 | export function createSourceInstances(...instances: any[]) { 45 | return instances; 46 | } 47 | -------------------------------------------------------------------------------- /modules/effects/src/effects_resolver.ts: -------------------------------------------------------------------------------- 1 | import { merge } from 'rxjs/observable/merge'; 2 | import { ignoreElements } from 'rxjs/operator/ignoreElements'; 3 | import { materialize } from 'rxjs/operator/materialize'; 4 | import { map } from 'rxjs/operator/map'; 5 | import { Observable } from 'rxjs/Observable'; 6 | import { Notification } from 'rxjs/Notification'; 7 | import { Action } from '@ngrx/store'; 8 | import { EffectNotification } from './effect_notification'; 9 | import { getSourceMetadata, getSourceForInstance } from './effects_metadata'; 10 | import { isOnRunEffects } from './on_run_effects'; 11 | 12 | export function mergeEffects( 13 | sourceInstance: any 14 | ): Observable { 15 | const sourceName = getSourceForInstance(sourceInstance).constructor.name; 16 | 17 | const observables: Observable[] = getSourceMetadata(sourceInstance).map( 18 | ({ propertyName, dispatch }): Observable => { 19 | const observable: Observable = 20 | typeof sourceInstance[propertyName] === 'function' 21 | ? sourceInstance[propertyName]() 22 | : sourceInstance[propertyName]; 23 | 24 | if (dispatch === false) { 25 | return ignoreElements.call(observable); 26 | } 27 | 28 | const materialized$ = materialize.call(observable); 29 | 30 | return map.call( 31 | materialized$, 32 | (notification: Notification): EffectNotification => ({ 33 | effect: sourceInstance[propertyName], 34 | notification, 35 | propertyName, 36 | sourceName, 37 | sourceInstance, 38 | }) 39 | ); 40 | } 41 | ); 42 | 43 | return merge(...observables); 44 | } 45 | 46 | export function resolveEffectSource(sourceInstance: any) { 47 | const mergedEffects$ = mergeEffects(sourceInstance); 48 | 49 | if (isOnRunEffects(sourceInstance)) { 50 | return sourceInstance.ngrxOnRunEffects(mergedEffects$); 51 | } 52 | 53 | return mergedEffects$; 54 | } 55 | -------------------------------------------------------------------------------- /modules/effects/src/effects_root_module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, Inject, Optional } from '@angular/core'; 2 | import { 3 | StoreModule, 4 | Store, 5 | StoreRootModule, 6 | StoreFeatureModule, 7 | } from '@ngrx/store'; 8 | import { EffectsRunner } from './effects_runner'; 9 | import { EffectSources } from './effect_sources'; 10 | import { ROOT_EFFECTS } from './tokens'; 11 | 12 | export const ROOT_EFFECTS_INIT = '@ngrx/effects/init'; 13 | 14 | @NgModule({}) 15 | export class EffectsRootModule { 16 | constructor( 17 | private sources: EffectSources, 18 | runner: EffectsRunner, 19 | store: Store, 20 | @Inject(ROOT_EFFECTS) rootEffects: any[], 21 | @Optional() storeRootModule: StoreRootModule, 22 | @Optional() storeFeatureModule: StoreFeatureModule 23 | ) { 24 | runner.start(); 25 | 26 | rootEffects.forEach(effectSourceInstance => 27 | sources.addEffects(effectSourceInstance) 28 | ); 29 | 30 | store.dispatch({ type: ROOT_EFFECTS_INIT }); 31 | } 32 | 33 | addEffects(effectSourceInstance: any) { 34 | this.sources.addEffects(effectSourceInstance); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /modules/effects/src/effects_runner.ts: -------------------------------------------------------------------------------- 1 | import { Subscription } from 'rxjs/Subscription'; 2 | import { Injectable, OnDestroy } from '@angular/core'; 3 | import { Store } from '@ngrx/store'; 4 | import { EffectSources } from './effect_sources'; 5 | 6 | @Injectable() 7 | export class EffectsRunner implements OnDestroy { 8 | private effectsSubscription: Subscription | null = null; 9 | 10 | constructor( 11 | private effectSources: EffectSources, 12 | private store: Store 13 | ) {} 14 | 15 | start() { 16 | if (!this.effectsSubscription) { 17 | this.effectsSubscription = this.effectSources 18 | .toActions() 19 | .subscribe(this.store); 20 | } 21 | } 22 | 23 | ngOnDestroy() { 24 | if (this.effectsSubscription) { 25 | this.effectsSubscription.unsubscribe(); 26 | this.effectsSubscription = null; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/effects/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | Effect, 3 | EffectsMetadata, 4 | getEffectsMetadata, 5 | } from './effects_metadata'; 6 | export { mergeEffects } from './effects_resolver'; 7 | export { Actions, ofType } from './actions'; 8 | export { EffectsModule } from './effects_module'; 9 | export { EffectSources } from './effect_sources'; 10 | export { OnRunEffects } from './on_run_effects'; 11 | export { EffectNotification } from './effect_notification'; 12 | export { ROOT_EFFECTS_INIT } from './effects_root_module'; 13 | -------------------------------------------------------------------------------- /modules/effects/src/on_run_effects.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | import { getSourceForInstance } from './effects_metadata'; 3 | import { EffectNotification } from './effect_notification'; 4 | 5 | export interface OnRunEffects { 6 | ngrxOnRunEffects( 7 | resolvedEffects$: Observable 8 | ): Observable; 9 | } 10 | 11 | const onRunEffectsKey: keyof OnRunEffects = 'ngrxOnRunEffects'; 12 | 13 | export function isOnRunEffects( 14 | sourceInstance: Object 15 | ): sourceInstance is OnRunEffects { 16 | const source = getSourceForInstance(sourceInstance); 17 | 18 | return ( 19 | onRunEffectsKey in source && typeof source[onRunEffectsKey] === 'function' 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /modules/effects/src/tokens.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken, Type } from '@angular/core'; 2 | 3 | export const IMMEDIATE_EFFECTS = new InjectionToken( 4 | 'ngrx/effects: Immediate Effects' 5 | ); 6 | export const ROOT_EFFECTS = new InjectionToken[]>( 7 | 'ngrx/effects: Root Effects' 8 | ); 9 | export const FEATURE_EFFECTS = new InjectionToken( 10 | 'ngrx/effects: Feature Effects' 11 | ); 12 | -------------------------------------------------------------------------------- /modules/effects/testing/BUILD: -------------------------------------------------------------------------------- 1 | load("//tools:defaults.bzl", "ts_library") 2 | 3 | ts_library( 4 | name = "testing", 5 | srcs = glob([ 6 | "*.ts", 7 | "src/**/*.ts", 8 | ]), 9 | module_name = "@ngrx/effects/testing", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "//modules/effects", 13 | "@rxjs", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /modules/effects/testing/README.md: -------------------------------------------------------------------------------- 1 | @ngrx/effects/testing 2 | ======= 3 | 4 | The sources for this package are in the main [ngrx/platform](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo. 5 | 6 | License: MIT 7 | -------------------------------------------------------------------------------- /modules/effects/testing/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/testing'; 2 | -------------------------------------------------------------------------------- /modules/effects/testing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngrx/effects/testing", 3 | "typings": "../testing.d.ts", 4 | "main": "../bundles/effects-testing.umd.js", 5 | "module": "../@ngrx/effects/testing.es5.js", 6 | "es2015": "../@ngrx/effects/testing.js" 7 | } 8 | -------------------------------------------------------------------------------- /modules/effects/testing/rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | entry: './dist/effects/@ngrx/effects/testing.es5.js', 3 | dest: './dist/effects/bundles/effects-testing.umd.js', 4 | format: 'umd', 5 | exports: 'named', 6 | moduleName: 'ngrx.effects.testing', 7 | globals: { 8 | '@angular/core': 'ng.core', 9 | '@ngrx/effects': 'ngrx.effects', 10 | 'rxjs/Observable': 'Rx', 11 | 'rxjs/observable/defer': 'Rx.Observable.defer', 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /modules/effects/testing/src/testing.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@angular/core'; 2 | import { Actions } from '@ngrx/effects'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { defer } from 'rxjs/observable/defer'; 5 | 6 | export function provideMockActions(source: Observable): Provider; 7 | export function provideMockActions(factory: () => Observable): Provider; 8 | export function provideMockActions( 9 | factoryOrSource: (() => Observable) | Observable 10 | ): Provider { 11 | return { 12 | provide: Actions, 13 | useFactory: (): Observable => { 14 | if (typeof factoryOrSource === 'function') { 15 | return new Actions(defer(factoryOrSource)); 16 | } 17 | 18 | return new Actions(factoryOrSource); 19 | }, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /modules/effects/testing/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-build", 3 | "compilerOptions": { 4 | "paths": { 5 | "@ngrx/store": ["../../dist/packages/store"], 6 | "@ngrx/effects": ["../../dist/packages/effects"] 7 | } 8 | }, 9 | "files": [ 10 | "index.ts" 11 | ], 12 | "angularCompilerOptions": { 13 | "strictMetadataEmit": true 14 | } 15 | } -------------------------------------------------------------------------------- /modules/effects/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "stripInternal": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "noEmitOnError": false, 11 | "noImplicitAny": true, 12 | "noImplicitReturns": true, 13 | "outDir": "../../dist/packages/effects", 14 | "paths": { 15 | "@ngrx/store": ["../../dist/packages/store"] 16 | }, 17 | "rootDir": ".", 18 | "sourceMap": true, 19 | "inlineSources": true, 20 | "lib": ["es2015", "dom"], 21 | "target": "es2015", 22 | "skipLibCheck": true 23 | }, 24 | "files": [ 25 | "public_api.ts" 26 | ], 27 | "angularCompilerOptions": { 28 | "annotateForClosureCompiler": true, 29 | "strictMetadataEmit": true, 30 | "flatModuleOutFile": "index.js", 31 | "flatModuleId": "@ngrx/effects" 32 | } 33 | } -------------------------------------------------------------------------------- /modules/entity/BUILD: -------------------------------------------------------------------------------- 1 | load("//tools:defaults.bzl", "ts_library") 2 | 3 | ts_library( 4 | name = "entity", 5 | srcs = glob([ 6 | "*.ts", 7 | "src/**/*.ts", 8 | ]), 9 | module_name = "@ngrx/entity", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "//modules/store", 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /modules/entity/README.md: -------------------------------------------------------------------------------- 1 | @ngrx/entity 2 | ======= 3 | 4 | The sources for this package are in the main [ngrx/platform](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo. 5 | 6 | License: MIT 7 | -------------------------------------------------------------------------------- /modules/entity/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DO NOT EDIT 3 | * 4 | * This file is automatically generated at build 5 | */ 6 | 7 | export * from './public_api'; 8 | -------------------------------------------------------------------------------- /modules/entity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngrx/entity", 3 | "version": "5.1.0", 4 | "description": "Common utilities for entity reducers", 5 | "module": "@ngrx/entity.es5.js", 6 | "es2015": "@ngrx/entity.js", 7 | "main": "bundles/entity.umd.js", 8 | "typings": "entity.d.ts", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/ngrx/platform.git" 12 | }, 13 | "authors": [ 14 | "Mike Ryan" 15 | ], 16 | "license": "MIT", 17 | "peerDependencies": { 18 | "@angular/core": "^5.0.0", 19 | "@ngrx/store": "^5.0.0", 20 | "rxjs": "^5.5.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /modules/entity/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './src/index'; 2 | -------------------------------------------------------------------------------- /modules/entity/rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | entry: './dist/entity/@ngrx/entity.es5.js', 3 | dest: './dist/entity/bundles/entity.umd.js', 4 | format: 'umd', 5 | exports: 'named', 6 | moduleName: 'ngrx.entity', 7 | globals: { 8 | '@ngrx/store': 'ngrx.store' 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /modules/entity/spec/BUILD: -------------------------------------------------------------------------------- 1 | load("//tools:defaults.bzl", "ts_test_library", "jasmine_node_test") 2 | 3 | ts_test_library( 4 | name = "test_lib", 5 | srcs = glob( 6 | [ 7 | "**/*.ts", 8 | ], 9 | ), 10 | deps = [ 11 | "//modules/entity", 12 | "@rxjs", 13 | ], 14 | ) 15 | 16 | jasmine_node_test( 17 | name = "test", 18 | deps = [ 19 | ":test_lib", 20 | "//modules/entity", 21 | "@rxjs", 22 | ], 23 | ) 24 | -------------------------------------------------------------------------------- /modules/entity/spec/entity_state.spec.ts: -------------------------------------------------------------------------------- 1 | import { createEntityAdapter, EntityAdapter } from '../src'; 2 | import { BookModel } from './fixtures/book'; 3 | 4 | describe('Entity State', () => { 5 | let adapter: EntityAdapter; 6 | 7 | beforeEach(() => { 8 | adapter = createEntityAdapter({ 9 | selectId: (book: BookModel) => book.id, 10 | }); 11 | }); 12 | 13 | it('should let you get the initial state', () => { 14 | const initialState = adapter.getInitialState(); 15 | 16 | expect(initialState).toEqual({ 17 | ids: [], 18 | entities: {}, 19 | }); 20 | }); 21 | 22 | it('should let you provide additional initial state properties', () => { 23 | const additionalProperties = { isHydrated: true }; 24 | 25 | const initialState = adapter.getInitialState(additionalProperties); 26 | 27 | expect(initialState).toEqual({ 28 | ...additionalProperties, 29 | ids: [], 30 | entities: {}, 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /modules/entity/spec/fixtures/book.ts: -------------------------------------------------------------------------------- 1 | const deepFreeze = require('deep-freeze'); 2 | 3 | export interface BookModel { 4 | id: string; 5 | title: string; 6 | } 7 | 8 | export const AClockworkOrange: BookModel = deepFreeze({ 9 | id: 'aco', 10 | title: 'A Clockwork Orange', 11 | }); 12 | 13 | export const AnimalFarm: BookModel = deepFreeze({ 14 | id: 'af', 15 | title: 'Animal Farm', 16 | }); 17 | 18 | export const TheGreatGatsby: BookModel = deepFreeze({ 19 | id: 'tgg', 20 | title: 'The Great Gatsby', 21 | }); 22 | -------------------------------------------------------------------------------- /modules/entity/src/create_adapter.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from '@ngrx/store'; 2 | import { 3 | EntityDefinition, 4 | Comparer, 5 | IdSelector, 6 | EntityAdapter, 7 | } from './models'; 8 | import { createInitialStateFactory } from './entity_state'; 9 | import { createSelectorsFactory } from './state_selectors'; 10 | import { createSortedStateAdapter } from './sorted_state_adapter'; 11 | import { createUnsortedStateAdapter } from './unsorted_state_adapter'; 12 | 13 | export function createEntityAdapter( 14 | options: { 15 | selectId?: IdSelector; 16 | sortComparer?: false | Comparer; 17 | } = {} 18 | ): EntityAdapter { 19 | const { selectId, sortComparer }: EntityDefinition = { 20 | sortComparer: false, 21 | selectId: (instance: any) => instance.id, 22 | ...options, 23 | }; 24 | 25 | const stateFactory = createInitialStateFactory(); 26 | const selectorsFactory = createSelectorsFactory(); 27 | const stateAdapter = sortComparer 28 | ? createSortedStateAdapter(selectId, sortComparer) 29 | : createUnsortedStateAdapter(selectId); 30 | 31 | return { 32 | ...stateFactory, 33 | ...selectorsFactory, 34 | ...stateAdapter, 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /modules/entity/src/entity_state.ts: -------------------------------------------------------------------------------- 1 | import { EntityState } from './models'; 2 | 3 | export function getInitialEntityState(): EntityState { 4 | return { 5 | ids: [], 6 | entities: {}, 7 | }; 8 | } 9 | 10 | export function createInitialStateFactory() { 11 | function getInitialState(): EntityState; 12 | function getInitialState( 13 | additionalState: S 14 | ): EntityState & S; 15 | function getInitialState(additionalState: any = {}): any { 16 | return Object.assign(getInitialEntityState(), additionalState); 17 | } 18 | 19 | return { getInitialState }; 20 | } 21 | -------------------------------------------------------------------------------- /modules/entity/src/index.ts: -------------------------------------------------------------------------------- 1 | export { createEntityAdapter } from './create_adapter'; 2 | export { EntityState, EntityAdapter, Update } from './models'; 3 | -------------------------------------------------------------------------------- /modules/entity/src/state_adapter.ts: -------------------------------------------------------------------------------- 1 | import { EntityState, EntityStateAdapter } from './models'; 2 | 3 | export enum DidMutate { 4 | EntitiesOnly, 5 | Both, 6 | None, 7 | } 8 | 9 | export function createStateOperator( 10 | mutator: (arg: R, state: EntityState) => DidMutate 11 | ): EntityState; 12 | export function createStateOperator( 13 | mutator: (arg: any, state: any) => DidMutate 14 | ): any { 15 | return function operation>(arg: R, state: any): S { 16 | const clonedEntityState: EntityState = { 17 | ids: [...state.ids], 18 | entities: { ...state.entities }, 19 | }; 20 | 21 | const didMutate = mutator(arg, clonedEntityState); 22 | 23 | if (didMutate === DidMutate.Both) { 24 | return Object.assign({}, state, clonedEntityState); 25 | } 26 | 27 | if (didMutate === DidMutate.EntitiesOnly) { 28 | return { 29 | ...state, 30 | entities: clonedEntityState.entities, 31 | }; 32 | } 33 | 34 | return state; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /modules/entity/src/state_selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from '@ngrx/store'; 2 | import { EntityState, EntitySelectors, Dictionary } from './models'; 3 | 4 | export function createSelectorsFactory() { 5 | function getSelectors(): EntitySelectors>; 6 | function getSelectors( 7 | selectState: (state: V) => EntityState 8 | ): EntitySelectors; 9 | function getSelectors( 10 | selectState?: (state: any) => EntityState 11 | ): EntitySelectors { 12 | const selectIds = (state: any) => state.ids; 13 | const selectEntities = (state: EntityState) => state.entities; 14 | const selectAll = createSelector( 15 | selectIds, 16 | selectEntities, 17 | (ids: T[], entities: Dictionary): any => 18 | ids.map((id: any) => (entities as any)[id]) 19 | ); 20 | 21 | const selectTotal = createSelector(selectIds, ids => ids.length); 22 | 23 | if (!selectState) { 24 | return { 25 | selectIds, 26 | selectEntities, 27 | selectAll, 28 | selectTotal, 29 | }; 30 | } 31 | 32 | return { 33 | selectIds: createSelector(selectState, selectIds), 34 | selectEntities: createSelector(selectState, selectEntities), 35 | selectAll: createSelector(selectState, selectAll), 36 | selectTotal: createSelector(selectState, selectTotal), 37 | }; 38 | } 39 | 40 | return { getSelectors }; 41 | } 42 | -------------------------------------------------------------------------------- /modules/entity/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "stripInternal": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "noEmitOnError": false, 11 | "noImplicitAny": true, 12 | "noImplicitReturns": true, 13 | "outDir": "../../dist/packages/entity", 14 | "paths": { 15 | "@ngrx/store": ["../../dist/packages/store"] 16 | }, 17 | "rootDir": ".", 18 | "sourceMap": true, 19 | "inlineSources": true, 20 | "lib": ["es2015", "dom"], 21 | "target": "es2015", 22 | "skipLibCheck": true 23 | }, 24 | "files": [ 25 | "public_api.ts" 26 | ], 27 | "angularCompilerOptions": { 28 | "annotateForClosureCompiler": true, 29 | "strictMetadataEmit": true, 30 | "flatModuleOutFile": "index.js", 31 | "flatModuleId": "@ngrx/entity" 32 | } 33 | } -------------------------------------------------------------------------------- /modules/router-store/BUILD: -------------------------------------------------------------------------------- 1 | load("//tools:defaults.bzl", "ts_library") 2 | 3 | ts_library( 4 | name = "router-store", 5 | srcs = glob([ 6 | "*.ts", 7 | "src/**/*.ts", 8 | ]), 9 | module_name = "@ngrx/router-store", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "//modules/store", 13 | "@rxjs", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /modules/router-store/README.md: -------------------------------------------------------------------------------- 1 | @ngrx/router-store 2 | ======= 3 | 4 | The sources for this package are in the main [ngrx/platform](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo. 5 | 6 | License: MIT 7 | -------------------------------------------------------------------------------- /modules/router-store/e2e/app.ts: -------------------------------------------------------------------------------- 1 | import { Component, NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { Router, RouterModule } from '@angular/router'; 5 | import { StoreModule } from '@ngrx/store'; 6 | import { 7 | ROUTER_NAVIGATION, 8 | ROUTER_CANCEL, 9 | ROUTER_ERROR, 10 | StoreRouterConnectingModule, 11 | } from '../src/index'; 12 | 13 | @Component({ 14 | selector: 'test-app', 15 | template: '', 16 | }) 17 | export class AppCmp {} 18 | 19 | @Component({ 20 | selector: 'simple-cmp', 21 | template: 'simple', 22 | }) 23 | export class SimpleCmp {} 24 | 25 | export function reducer(state: string = '', action: any) { 26 | if (action.type === ROUTER_NAVIGATION) { 27 | return action.payload.routerState.url.toString(); 28 | } else { 29 | return state; 30 | } 31 | } 32 | 33 | @NgModule({ 34 | declarations: [AppCmp, SimpleCmp], 35 | imports: [ 36 | BrowserModule, 37 | RouterModule.forRoot([ 38 | { path: '', component: SimpleCmp }, 39 | { path: 'next', component: SimpleCmp }, 40 | ]), 41 | StoreModule.forRoot({ reducer }), 42 | StoreRouterConnectingModule, 43 | ], 44 | bootstrap: [AppCmp], 45 | }) 46 | export class AppModule {} 47 | -------------------------------------------------------------------------------- /modules/router-store/e2e/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { AppModule } from './app'; 3 | 4 | platformBrowserDynamic().bootstrapModule(AppModule); 5 | -------------------------------------------------------------------------------- /modules/router-store/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DO NOT EDIT 3 | * 4 | * This file is automatically generated at build 5 | */ 6 | 7 | export * from './public_api'; 8 | -------------------------------------------------------------------------------- /modules/router-store/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngrx/router-store", 3 | "version": "5.0.1", 4 | "description": "Bindings to connect @angular/router to @ngrx/store", 5 | "module": "@ngrx/router-store.es5.js", 6 | "es2015": "@ngrx/router-store.js", 7 | "main": "bundles/router-store.umd.js", 8 | "typings": "router-store.d.ts", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ngrx/platform.git" 12 | }, 13 | "keywords": [ 14 | "RxJS", 15 | "Angular", 16 | "Redux" 17 | ], 18 | "authors": [ 19 | "Victor Savkin " 20 | ], 21 | "license": "MIT", 22 | "peerDependencies": { 23 | "@angular/common": "^5.0.0", 24 | "@angular/core": "^5.0.0", 25 | "@angular/router": "^5.0.0", 26 | "@ngrx/store": "^5.0.0", 27 | "rxjs": "^5.5.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/router-store/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './src/index'; 2 | -------------------------------------------------------------------------------- /modules/router-store/rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | entry: './dist/router-store/@ngrx/router-store.es5.js', 3 | dest: './dist/router-store/bundles/router-store.umd.js', 4 | format: 'umd', 5 | exports: 'named', 6 | moduleName: 'ngrx.routerStore', 7 | globals: { 8 | '@ngrx/store': 'ngrx.store', 9 | '@angular/core': 'ng.core', 10 | '@angular/router': 'ng.router', 11 | 'rxjs/Observable': 'Rx', 12 | 'rxjs/observable/of': 'Rx.Observable' 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /modules/router-store/spec/BUILD: -------------------------------------------------------------------------------- 1 | load("//tools:defaults.bzl", "ts_test_library", "jasmine_node_test") 2 | 3 | ts_test_library( 4 | name = "test_lib", 5 | srcs = glob( 6 | [ 7 | "**/*.ts", 8 | ], 9 | ), 10 | deps = [ 11 | "//modules/router-store", 12 | "//modules/store", 13 | "@rxjs", 14 | ], 15 | ) 16 | 17 | jasmine_node_test( 18 | name = "test", 19 | deps = [ 20 | ":test_lib", 21 | "//modules/router-store", 22 | "//modules/store", 23 | "@rxjs", 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /modules/router-store/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | ROUTER_ERROR, 3 | ROUTER_CANCEL, 4 | ROUTER_NAVIGATION, 5 | RouterNavigationAction, 6 | RouterCancelAction, 7 | RouterErrorAction, 8 | RouterAction, 9 | routerReducer, 10 | RouterErrorPayload, 11 | RouterReducerState, 12 | RouterCancelPayload, 13 | RouterNavigationPayload, 14 | StoreRouterConnectingModule, 15 | StoreRouterConfig, 16 | StoreRouterConfigFunction, 17 | ROUTER_CONFIG, 18 | DEFAULT_ROUTER_FEATURENAME, 19 | } from './router_store_module'; 20 | 21 | export { 22 | RouterStateSerializer, 23 | DefaultRouterStateSerializer, 24 | } from './serializer'; 25 | -------------------------------------------------------------------------------- /modules/router-store/src/serializer.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | import { RouterStateSnapshot } from '@angular/router'; 3 | 4 | export abstract class RouterStateSerializer { 5 | abstract serialize(routerState: RouterStateSnapshot): T; 6 | } 7 | 8 | export class DefaultRouterStateSerializer 9 | implements RouterStateSerializer { 10 | serialize(routerState: RouterStateSnapshot) { 11 | return routerState; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /modules/router-store/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "stripInternal": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "noEmitOnError": false, 11 | "noImplicitAny": true, 12 | "noImplicitReturns": true, 13 | "outDir": "../../dist/packages/router-store", 14 | "paths": { 15 | "@ngrx/store": ["../../dist/packages/store"] 16 | }, 17 | "rootDir": ".", 18 | "sourceMap": true, 19 | "inlineSources": true, 20 | "lib": ["es2015", "dom"], 21 | "target": "es2015", 22 | "skipLibCheck": true 23 | }, 24 | "files": [ 25 | "public_api.ts" 26 | ], 27 | "angularCompilerOptions": { 28 | "annotateForClosureCompiler": true, 29 | "strictMetadataEmit": true, 30 | "flatModuleOutFile": "index.js", 31 | "flatModuleId": "@ngrx/router-store" 32 | } 33 | } -------------------------------------------------------------------------------- /modules/router-store/webpack.e2e.config.js: -------------------------------------------------------------------------------- 1 | const ngtools = require('@ngtools/webpack'); 2 | 3 | module.exports = { 4 | resolve: { 5 | extensions: ['.ts', '.js'] 6 | }, 7 | entry: './e2e/main.ts', 8 | output: { 9 | path: "./dist/e2e", 10 | filename: "bundle.js" 11 | }, 12 | plugins: [ 13 | new ngtools.AotPlugin({ 14 | tsConfigPath: './tsconfig.e2e.json' 15 | }) 16 | ], 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.ts$/, 21 | use: [ 22 | '@ngtools/webpack' 23 | ] 24 | } 25 | ] 26 | } 27 | }; -------------------------------------------------------------------------------- /modules/schematics/README.md: -------------------------------------------------------------------------------- 1 | @ngrx/schematics 2 | ======= 3 | 4 | The sources for this package are in the main [ngrx/platform](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo. 5 | 6 | License: MIT 7 | -------------------------------------------------------------------------------- /modules/schematics/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngrx/schematics", 3 | "version": "5.1.0", 4 | "description": "NgRx Schematics for Angular", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/ngrx/platform.git" 8 | }, 9 | "keywords": [ 10 | "RxJS", 11 | "Angular", 12 | "Redux", 13 | "NgRx", 14 | "Schematics", 15 | "Angular CLI" 16 | ], 17 | "author": "Brandon Roberts ", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/ngrx/platform/issues" 21 | }, 22 | "homepage": "https://github.com/ngrx/platform#readme", 23 | "schematics": "./collection.json" 24 | } 25 | -------------------------------------------------------------------------------- /modules/schematics/src/action/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.actions.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | 3 | export enum <%= classify(name) %>ActionTypes { 4 | <%= classify(name) %>Action = '[<%= classify(name) %>] Action' 5 | } 6 | 7 | export class <%= classify(name) %> implements Action { 8 | readonly type = <%= classify(name) %>ActionTypes.<%= classify(name) %>Action; 9 | } 10 | 11 | export type <%= classify(name) %>Actions = <%= classify(name) %>; 12 | -------------------------------------------------------------------------------- /modules/schematics/src/action/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.actions__dot__spec.ts: -------------------------------------------------------------------------------- 1 | import { <%= classify(name) %> } from './<%= dasherize(name) %>.actions'; 2 | 3 | describe('<%= classify(name) %>', () => { 4 | it('should create an instance', () => { 5 | expect(new <%= classify(name) %>()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /modules/schematics/src/action/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; 2 | import * as path from 'path'; 3 | import { getFileContent } from '../utility/test'; 4 | import { Schema as ActionOptions } from './schema'; 5 | 6 | describe('Action Schematic', () => { 7 | const schematicRunner = new SchematicTestRunner( 8 | '@ngrx/schematics', 9 | path.join(__dirname, '../../collection.json') 10 | ); 11 | const defaultOptions: ActionOptions = { 12 | name: 'foo', 13 | path: 'app', 14 | sourceDir: 'src', 15 | spec: false, 16 | group: false, 17 | flat: true, 18 | }; 19 | 20 | it('should create one file', () => { 21 | const tree = schematicRunner.runSchematic('action', defaultOptions); 22 | expect(tree.files.length).toEqual(1); 23 | expect(tree.files[0]).toEqual('/src/app/foo.actions.ts'); 24 | }); 25 | 26 | it('should create two files if spec is true', () => { 27 | const options = { 28 | ...defaultOptions, 29 | spec: true, 30 | }; 31 | const tree = schematicRunner.runSchematic('action', options); 32 | expect(tree.files.length).toEqual(2); 33 | expect( 34 | tree.files.indexOf('/src/app/foo.actions.spec.ts') 35 | ).toBeGreaterThanOrEqual(0); 36 | expect( 37 | tree.files.indexOf('/src/app/foo.actions.ts') 38 | ).toBeGreaterThanOrEqual(0); 39 | }); 40 | 41 | it('should create an enum named "Foo"', () => { 42 | const tree = schematicRunner.runSchematic('action', defaultOptions); 43 | const fileEntry = tree.get(tree.files[0]); 44 | if (fileEntry) { 45 | const fileContent = fileEntry.content.toString(); 46 | expect(fileContent).toMatch(/export enum FooActionTypes/); 47 | } 48 | }); 49 | 50 | it('should group within an "actions" folder if group is set', () => { 51 | const tree = schematicRunner.runSchematic('action', { 52 | ...defaultOptions, 53 | group: true, 54 | }); 55 | expect(tree.files.length).toEqual(1); 56 | expect(tree.files[0]).toEqual('/src/app/actions/foo.actions.ts'); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /modules/schematics/src/action/index.ts: -------------------------------------------------------------------------------- 1 | import { normalize } from '@angular-devkit/core'; 2 | import { 3 | Rule, 4 | SchematicsException, 5 | apply, 6 | branchAndMerge, 7 | chain, 8 | filter, 9 | mergeWith, 10 | move, 11 | noop, 12 | template, 13 | url, 14 | } from '@angular-devkit/schematics'; 15 | import * as stringUtils from '../strings'; 16 | import { Schema as ActionOptions } from './schema'; 17 | 18 | export default function(options: ActionOptions): Rule { 19 | options.path = options.path ? normalize(options.path) : options.path; 20 | const sourceDir = options.sourceDir; 21 | if (!sourceDir) { 22 | throw new SchematicsException(`sourceDir option is required.`); 23 | } 24 | 25 | const templateSource = apply(url('./files'), [ 26 | options.spec ? noop() : filter(path => !path.endsWith('__spec.ts')), 27 | template({ 28 | 'if-flat': (s: string) => 29 | stringUtils.group( 30 | options.flat ? '' : s, 31 | options.group ? 'actions' : '' 32 | ), 33 | ...stringUtils, 34 | ...(options as object), 35 | dot: () => '.', 36 | }), 37 | move(sourceDir), 38 | ]); 39 | 40 | return chain([branchAndMerge(chain([mergeWith(templateSource)]))]); 41 | } 42 | -------------------------------------------------------------------------------- /modules/schematics/src/action/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | name: string; 3 | appRoot?: string; 4 | path?: string; 5 | sourceDir?: string; 6 | /** 7 | * Specifies if a spec file is generated. 8 | */ 9 | spec?: boolean; 10 | flat?: boolean; 11 | group?: boolean; 12 | } 13 | -------------------------------------------------------------------------------- /modules/schematics/src/action/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "id": "SchematicsNgRxAction", 4 | "title": "NgRx Action Options Schema", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "type": "string" 9 | }, 10 | "appRoot": { 11 | "type": "string" 12 | }, 13 | "path": { 14 | "type": "string", 15 | "default": "app" 16 | }, 17 | "sourceDir": { 18 | "type": "string", 19 | "default": "src" 20 | }, 21 | "spec": { 22 | "type": "boolean", 23 | "description": "Specifies if a spec file is generated.", 24 | "default": false 25 | }, 26 | "flat": { 27 | "type": "boolean", 28 | "default": true, 29 | "description": "Flag to indicate if a dir is created." 30 | }, 31 | "group": { 32 | "type": "boolean", 33 | "default": false, 34 | "description": "Group actions file within 'actions' folder", 35 | "aliases": ["g"] 36 | } 37 | }, 38 | "required": [ 39 | "name" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /modules/schematics/src/container/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component__dot__spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component'; 4 | import { Store, StoreModule } from '@ngrx/store'; 5 | 6 | describe('<%= classify(name) %>Component', () => { 7 | let component: <%= classify(name) %>Component; 8 | let fixture: ComponentFixture<<%= classify(name) %>Component>; 9 | let store: Store; 10 | 11 | beforeEach(async() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ StoreModule.forRoot({}) ], 14 | declarations: [ <%= classify(name) %>Component ] 15 | }); 16 | 17 | await TestBed.compileComponents(); 18 | }); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(<%= classify(name) %>Component); 22 | component = fixture.componentInstance; 23 | store = TestBed.get(Store); 24 | 25 | spyOn(store, 'dispatch').and.callThrough(); 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /modules/schematics/src/container/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | path?: string; 3 | appRoot?: string; 4 | sourceDir?: string; 5 | name: string; 6 | /** 7 | * Specifies if the style will be in the ts file. 8 | */ 9 | inlineStyle?: boolean; 10 | /** 11 | * Specifies if the template will be in the ts file. 12 | */ 13 | inlineTemplate?: boolean; 14 | /** 15 | * Specifies the view encapsulation strategy. 16 | */ 17 | viewEncapsulation?: 'Emulated' | 'Native' | 'None'; 18 | /** 19 | * Specifies the change detection strategy. 20 | */ 21 | changeDetection?: 'Default' | 'OnPush'; 22 | routing?: boolean; 23 | /** 24 | * The prefix to apply to generated selectors. 25 | */ 26 | prefix?: string; 27 | /** 28 | * The file extension to be used for style files. 29 | */ 30 | styleext?: string; 31 | spec?: boolean; 32 | /** 33 | * Flag to indicate if a dir is created. 34 | */ 35 | flat?: boolean; 36 | htmlTemplate?: string; 37 | skipImport?: boolean; 38 | selector?: string; 39 | /** 40 | * Allows specification of the declaring module. 41 | */ 42 | module?: string; 43 | /** 44 | * Specifies if declaring module exports the component. 45 | */ 46 | export?: boolean; 47 | /** 48 | * Specifies the path to the state exports 49 | */ 50 | state?: string; 51 | 52 | /** 53 | * Specifies the interface for the state 54 | */ 55 | stateInterface?: string; 56 | } 57 | -------------------------------------------------------------------------------- /modules/schematics/src/effect/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.effects.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Actions, Effect } from '@ngrx/effects'; 3 | <% if(feature) { %>import { <%= classify(name) %>Actions, <%= classify(name) %>ActionTypes } from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %> 4 | 5 | @Injectable() 6 | export class <%= classify(name) %>Effects { 7 | <% if(feature) { %> 8 | @Effect() 9 | effect$ = this.actions$.ofType(<%= classify(name) %>ActionTypes.<%= classify(name) %>Action); 10 | <% } %> 11 | constructor(private actions$: Actions) {} 12 | } 13 | -------------------------------------------------------------------------------- /modules/schematics/src/effect/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.effects__dot__spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { provideMockActions } from '@ngrx/effects/testing'; 3 | import { Observable } from 'rxjs/Observable'; 4 | 5 | import { <%= classify(name) %>Effects } from './<%= dasherize(name) %>.effects'; 6 | 7 | describe('<%= classify(name) %>Service', () => { 8 | let actions$: Observable; 9 | let effects: <%= classify(name) %>Effects; 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | providers: [ 14 | <%= classify(name) %>Effects, 15 | provideMockActions(() => actions$) 16 | ] 17 | }); 18 | 19 | effects = TestBed.get(<%= classify(name) %>Effects); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(effects).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /modules/schematics/src/effect/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | name: string; 3 | path?: string; 4 | appRoot?: string; 5 | sourceDir?: string; 6 | /** 7 | * Flag to indicate if a dir is created. 8 | */ 9 | flat?: boolean; 10 | /** 11 | * Specifies if a spec file is generated. 12 | */ 13 | spec?: boolean; 14 | /** 15 | * Allows specification of the declaring module. 16 | */ 17 | module?: string; 18 | root?: boolean; 19 | feature?: boolean; 20 | group?: boolean; 21 | } 22 | -------------------------------------------------------------------------------- /modules/schematics/src/effect/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "id": "SchematicsNgRxEffect", 4 | "title": "NgRx Effect Options Schema", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "type": "string" 9 | }, 10 | "path": { 11 | "type": "string", 12 | "default": "app" 13 | }, 14 | "appRoot": { 15 | "type": "string" 16 | }, 17 | "sourceDir": { 18 | "type": "string", 19 | "default": "src" 20 | }, 21 | "flat": { 22 | "type": "boolean", 23 | "default": true, 24 | "description": "Flag to indicate if a dir is created." 25 | }, 26 | "spec": { 27 | "type": "boolean", 28 | "default": true, 29 | "description": "Specifies if a spec file is generated." 30 | }, 31 | "module": { 32 | "type": "string", 33 | "default": "", 34 | "description": "Allows specification of the declaring module.", 35 | "alias": "m", 36 | "subtype": "filepath" 37 | }, 38 | "root": { 39 | "type": "boolean", 40 | "default": false, 41 | "description": "Flag to indicate whether the effects are registered as root." 42 | }, 43 | "feature": { 44 | "type": "boolean", 45 | "default": false, 46 | "description": "Flag to indicate if part of a feature schematic." 47 | }, 48 | "group": { 49 | "type": "boolean", 50 | "default": false, 51 | "description": "Group effects file within 'effects' folder", 52 | "aliases": ["g"] 53 | } 54 | }, 55 | "required": [ 56 | "name" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /modules/schematics/src/entity/files/__path__/__name@dasherize@if-flat__/__name@dasherize@group-models__.model.ts: -------------------------------------------------------------------------------- 1 | export interface <%= classify(name) %> { 2 | id: string; 3 | } 4 | -------------------------------------------------------------------------------- /modules/schematics/src/entity/files/__path__/__name@dasherize@if-flat__/__name@dasherize@group-reducers__.reducer__dot__spec.ts: -------------------------------------------------------------------------------- 1 | import { reducer, initialState } from '<%= featurePath(group, flat, "reducers", dasherize(name)) %><%= dasherize(name) %>.reducer'; 2 | 3 | describe('<%= classify(name) %> Reducer', () => { 4 | describe('unknown action', () => { 5 | it('should return the initial state', () => { 6 | const action = {} as any; 7 | 8 | const result = reducer(initialState, action); 9 | 10 | expect(result).toBe(initialState); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /modules/schematics/src/entity/index.ts: -------------------------------------------------------------------------------- 1 | import { normalize } from '@angular-devkit/core'; 2 | import { 3 | Rule, 4 | SchematicsException, 5 | apply, 6 | branchAndMerge, 7 | chain, 8 | filter, 9 | mergeWith, 10 | move, 11 | noop, 12 | template, 13 | url, 14 | Tree, 15 | SchematicContext, 16 | } from '@angular-devkit/schematics'; 17 | import * as stringUtils from '../strings'; 18 | import { Schema as EntityOptions } from './schema'; 19 | import { 20 | addReducerToState, 21 | addReducerImportToNgModule, 22 | } from '../utility/ngrx-utils'; 23 | import { findModuleFromOptions } from '../utility/find-module'; 24 | 25 | export default function(options: EntityOptions): Rule { 26 | options.path = options.path ? normalize(options.path) : options.path; 27 | const sourceDir = options.sourceDir; 28 | if (!sourceDir) { 29 | throw new SchematicsException(`sourceDir option is required.`); 30 | } 31 | 32 | return (host: Tree, context: SchematicContext) => { 33 | if (options.module) { 34 | options.module = findModuleFromOptions(host, options); 35 | } 36 | 37 | const templateSource = apply(url('./files'), [ 38 | options.spec ? noop() : filter(path => !path.endsWith('__spec.ts')), 39 | template({ 40 | ...stringUtils, 41 | 'if-flat': (s: string) => (options.flat ? '' : s), 42 | 'group-actions': (name: string) => 43 | stringUtils.group(name, options.group ? 'actions' : ''), 44 | 'group-models': (name: string) => 45 | stringUtils.group(name, options.group ? 'models' : ''), 46 | 'group-reducers': (s: string) => 47 | stringUtils.group(s, options.group ? 'reducers' : ''), 48 | ...(options as object), 49 | dot: () => '.', 50 | }), 51 | move(sourceDir), 52 | ]); 53 | 54 | return chain([ 55 | addReducerToState({ ...options }), 56 | addReducerImportToNgModule({ ...options }), 57 | branchAndMerge(chain([mergeWith(templateSource)])), 58 | ])(host, context); 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /modules/schematics/src/entity/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | name: string; 3 | appRoot?: string; 4 | path?: string; 5 | sourceDir?: string; 6 | /** 7 | * Specifies if a spec file is generated. 8 | */ 9 | spec?: boolean; 10 | module?: string; 11 | reducers?: string; 12 | flat?: boolean; 13 | group?: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /modules/schematics/src/entity/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "id": "SchematicsNgRxEntity", 4 | "title": "NgRx Entity Options Schema", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "type": "string" 9 | }, 10 | "appRoot": { 11 | "type": "string" 12 | }, 13 | "path": { 14 | "type": "string", 15 | "default": "app" 16 | }, 17 | "sourceDir": { 18 | "type": "string", 19 | "default": "src" 20 | }, 21 | "spec": { 22 | "type": "boolean", 23 | "description": "Specifies if a spec file is generated.", 24 | "default": true 25 | }, 26 | "reducers": { 27 | "type": "string", 28 | "description": "Specifies the reducers file.", 29 | "aliases": ["r"] 30 | }, 31 | "module": { 32 | "type": "string", 33 | "description": "Specifies the declaring module.", 34 | "aliases": ["m"] 35 | }, 36 | "flat": { 37 | "type": "boolean", 38 | "default": true, 39 | "description": "Flag to indicate if a dir is created." 40 | }, 41 | "group": { 42 | "type": "boolean", 43 | "default": false, 44 | "description": "Group actions, reducers and effects within relative subfolders", 45 | "aliases": ["g"] 46 | } 47 | }, 48 | "required": [ 49 | "name" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /modules/schematics/src/feature/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SchematicTestRunner, 3 | UnitTestTree, 4 | } from '@angular-devkit/schematics/testing'; 5 | import * as path from 'path'; 6 | import { getFileContent } from '../utility/test'; 7 | import { Schema as FeatureOptions } from './schema'; 8 | 9 | describe('Feature Schematic', () => { 10 | const schematicRunner = new SchematicTestRunner( 11 | '@ngrx/schematics', 12 | path.join(__dirname, '../../collection.json') 13 | ); 14 | const defaultOptions: FeatureOptions = { 15 | name: 'foo', 16 | path: 'app', 17 | sourceDir: 'src', 18 | module: '', 19 | spec: true, 20 | group: false, 21 | }; 22 | 23 | it('should create all files of a feature', () => { 24 | const options = { ...defaultOptions }; 25 | 26 | const tree = schematicRunner.runSchematic('feature', options); 27 | const files = tree.files; 28 | expect(files.indexOf('/src/app/foo.actions.ts')).toBeGreaterThanOrEqual(0); 29 | expect(files.indexOf('/src/app/foo.reducer.ts')).toBeGreaterThanOrEqual(0); 30 | expect( 31 | files.indexOf('/src/app/foo.reducer.spec.ts') 32 | ).toBeGreaterThanOrEqual(0); 33 | expect(files.indexOf('/src/app/foo.effects.ts')).toBeGreaterThanOrEqual(0); 34 | expect( 35 | files.indexOf('/src/app/foo.effects.spec.ts') 36 | ).toBeGreaterThanOrEqual(0); 37 | }); 38 | 39 | it('should create all files of a feature within grouped folders if group is set', () => { 40 | const options = { ...defaultOptions, group: true }; 41 | 42 | const tree = schematicRunner.runSchematic('feature', options); 43 | const files = tree.files; 44 | expect( 45 | files.indexOf('/src/app/actions/foo.actions.ts') 46 | ).toBeGreaterThanOrEqual(0); 47 | expect( 48 | files.indexOf('/src/app/reducers/foo.reducer.ts') 49 | ).toBeGreaterThanOrEqual(0); 50 | expect( 51 | files.indexOf('/src/app/reducers/foo.reducer.spec.ts') 52 | ).toBeGreaterThanOrEqual(0); 53 | expect( 54 | files.indexOf('/src/app/effects/foo.effects.ts') 55 | ).toBeGreaterThanOrEqual(0); 56 | expect( 57 | files.indexOf('/src/app/effects/foo.effects.spec.ts') 58 | ).toBeGreaterThanOrEqual(0); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /modules/schematics/src/feature/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MergeStrategy, 3 | Rule, 4 | SchematicContext, 5 | SchematicsException, 6 | Tree, 7 | apply, 8 | chain, 9 | filter, 10 | mergeWith, 11 | move, 12 | noop, 13 | schematic, 14 | template, 15 | url, 16 | } from '@angular-devkit/schematics'; 17 | import * as ts from 'typescript'; 18 | import * as stringUtils from '../strings'; 19 | import { addBootstrapToModule, addImportToModule } from '../utility/ast-utils'; 20 | import { InsertChange } from '../utility/change'; 21 | import { Schema as FeatureOptions } from './schema'; 22 | import { insertImport } from '../utility/route-utils'; 23 | import { buildRelativePath } from '../utility/find-module'; 24 | 25 | export default function(options: FeatureOptions): Rule { 26 | return (host: Tree, context: SchematicContext) => { 27 | return chain([ 28 | schematic('action', { 29 | flat: options.flat, 30 | group: options.group, 31 | name: options.name, 32 | path: options.path, 33 | sourceDir: options.sourceDir, 34 | spec: false, 35 | }), 36 | schematic('reducer', { 37 | flat: options.flat, 38 | group: options.group, 39 | module: options.module, 40 | name: options.name, 41 | path: options.path, 42 | sourceDir: options.sourceDir, 43 | spec: options.spec, 44 | reducers: options.reducers, 45 | feature: true, 46 | }), 47 | schematic('effect', { 48 | flat: options.flat, 49 | group: options.group, 50 | module: options.module, 51 | name: options.name, 52 | path: options.path, 53 | sourceDir: options.sourceDir, 54 | spec: options.spec, 55 | feature: true, 56 | }), 57 | ])(host, context); 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /modules/schematics/src/feature/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | path?: string; 3 | sourceDir?: string; 4 | name: string; 5 | module?: string; 6 | flat?: boolean; 7 | spec?: boolean; 8 | reducers?: string; 9 | group?: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /modules/schematics/src/feature/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "id": "SchematicsNgRxFeature", 4 | "title": "NgRx Feature State Options Schema", 5 | "type": "object", 6 | "properties": { 7 | "path": { 8 | "type": "string", 9 | "default": "app" 10 | }, 11 | "sourceDir": { 12 | "type": "string", 13 | "default": "src", 14 | "alias": "sd" 15 | }, 16 | "name": { 17 | "type": "string" 18 | }, 19 | "flat": { 20 | "type": "boolean", 21 | "default": true, 22 | "description": "Flag to indicate if a dir is created." 23 | }, 24 | "module": { 25 | "type": "string", 26 | "description": "Specifies the declaring module.", 27 | "aliases": ["m"] 28 | }, 29 | "spec": { 30 | "type": "boolean", 31 | "default": true, 32 | "description": "Specifies if a spec file is generated." 33 | }, 34 | "reducers": { 35 | "type": "string", 36 | "description": "Specifies the reducers file.", 37 | "aliases": ["r"] 38 | }, 39 | "group": { 40 | "type": "boolean", 41 | "default": false, 42 | "description": "Group actions, reducers and effects within relative subfolders", 43 | "aliases": ["g"] 44 | } 45 | }, 46 | "required": [ 47 | "name" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /modules/schematics/src/reducer/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | <% if(feature) { %>import { <%= classify(name) %>Actions, <%= classify(name) %>ActionTypes } from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %> 3 | 4 | export interface State { 5 | 6 | } 7 | 8 | export const initialState: State = { 9 | 10 | }; 11 | 12 | export function reducer(state = initialState, action: <% if(feature) { %><%= classify(name) %>Actions<% } else { %>Action<% } %>): State { 13 | switch (action.type) { 14 | <% if(feature) { %> 15 | case <%= classify(name) %>ActionTypes.<%= classify(name) %>Action: 16 | return state; 17 | 18 | <% } %> 19 | default: 20 | return state; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /modules/schematics/src/reducer/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.reducer__dot__spec.ts: -------------------------------------------------------------------------------- 1 | import { reducer, initialState } from './<%= dasherize(name) %>.reducer'; 2 | 3 | describe('<%= classify(name) %> Reducer', () => { 4 | describe('unknown action', () => { 5 | it('should return the initial state', () => { 6 | const action = {} as any; 7 | 8 | const result = reducer(initialState, action); 9 | 10 | expect(result).toBe(initialState); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /modules/schematics/src/reducer/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | name: string; 3 | appRoot?: string; 4 | path?: string; 5 | sourceDir?: string; 6 | /** 7 | * Specifies if a spec file is generated. 8 | */ 9 | spec?: boolean; 10 | /** 11 | * Flag to indicate if a dir is created. 12 | */ 13 | flat?: boolean; 14 | module?: string; 15 | feature?: boolean; 16 | reducers?: string; 17 | group?: boolean; 18 | } 19 | -------------------------------------------------------------------------------- /modules/schematics/src/reducer/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "id": "SchematicsNgRxReducer", 4 | "title": "NgRx Reducer Options Schema", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "type": "string" 9 | }, 10 | "appRoot": { 11 | "type": "string" 12 | }, 13 | "path": { 14 | "type": "string", 15 | "default": "app" 16 | }, 17 | "sourceDir": { 18 | "type": "string", 19 | "default": "src" 20 | }, 21 | "spec": { 22 | "type": "boolean", 23 | "description": "Specifies if a spec file is generated.", 24 | "default": true 25 | }, 26 | "module": { 27 | "type": "string", 28 | "description": "Specifies the declaring module.", 29 | "aliases": ["m"] 30 | }, 31 | "flat": { 32 | "type": "boolean", 33 | "default": true, 34 | "description": "Flag to indicate if a dir is created." 35 | }, 36 | "feature": { 37 | "type": "boolean", 38 | "default": false, 39 | "description": "Flag to indicate if part of a feature schematic." 40 | }, 41 | "reducers": { 42 | "type": "string", 43 | "description": "Specifies the reducers file.", 44 | "aliases": ["r"] 45 | }, 46 | "group": { 47 | "type": "boolean", 48 | "default": false, 49 | "description": "Group reducer file within 'reducers' folder", 50 | "aliases": ["g"] 51 | } 52 | }, 53 | "required": [ 54 | "name" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /modules/schematics/src/store/files/__path__/__statePath__/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionReducer, 3 | ActionReducerMap, 4 | createFeatureSelector, 5 | createSelector, 6 | MetaReducer 7 | } from '@ngrx/store'; 8 | import { environment } from '<%= environmentsPath %>'; 9 | 10 | export interface State { 11 | 12 | } 13 | 14 | export const reducers: ActionReducerMap = { 15 | 16 | }; 17 | 18 | 19 | export const metaReducers: MetaReducer[] = !environment.production ? [] : []; 20 | -------------------------------------------------------------------------------- /modules/schematics/src/store/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | name: string; 3 | path?: string; 4 | appRoot?: string; 5 | sourceDir?: string; 6 | /** 7 | * Flag to indicate if a dir is created. 8 | */ 9 | flat?: boolean; 10 | /** 11 | * Specifies if a spec file is generated. 12 | */ 13 | spec?: boolean; 14 | /** 15 | * Allows specification of the declaring module. 16 | */ 17 | module?: string; 18 | statePath?: string; 19 | root?: boolean; 20 | } 21 | -------------------------------------------------------------------------------- /modules/schematics/src/store/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "id": "SchematicsNgRxState", 4 | "title": "NgRx State Management Options Schema", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "type": "string" 9 | }, 10 | "path": { 11 | "type": "string", 12 | "default": "app" 13 | }, 14 | "appRoot": { 15 | "type": "string" 16 | }, 17 | "sourceDir": { 18 | "type": "string", 19 | "default": "src" 20 | }, 21 | "flat": { 22 | "type": "boolean", 23 | "default": true, 24 | "description": "Flag to indicate if a dir is created." 25 | }, 26 | "spec": { 27 | "type": "boolean", 28 | "default": true, 29 | "description": "Specifies if a spec file is generated." 30 | }, 31 | "module": { 32 | "type": "string", 33 | "default": "", 34 | "description": "Allows specification of the declaring module.", 35 | "alias": "m", 36 | "subtype": "filepath" 37 | }, 38 | "statePath": { 39 | "type": "string", 40 | "default": "reducers" 41 | }, 42 | "root": { 43 | "type": "boolean", 44 | "default": false, 45 | "description": "Flag to setup the root state or feature state." 46 | } 47 | }, 48 | "required": [ 49 | "name" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /modules/schematics/src/utility/test/create-app-module.ts: -------------------------------------------------------------------------------- 1 | import { Tree } from '@angular-devkit/schematics'; 2 | 3 | export function createAppModule(tree: Tree, path?: string): Tree { 4 | tree.create( 5 | path || '/src/app/app.module.ts', 6 | ` 7 | import { BrowserModule } from '@angular/platform-browser'; 8 | import { NgModule } from '@angular/core'; 9 | import { AppComponent } from './app.component'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | AppComponent 14 | ], 15 | imports: [ 16 | BrowserModule 17 | ], 18 | providers: [], 19 | bootstrap: [AppComponent] 20 | }) 21 | export class AppModule { } 22 | ` 23 | ); 24 | 25 | return tree; 26 | } 27 | 28 | export function createAppModuleWithEffects( 29 | tree: Tree, 30 | path: string, 31 | effects?: string 32 | ): Tree { 33 | tree.create( 34 | path || '/src/app/app.module.ts', 35 | ` 36 | import { BrowserModule } from '@angular/platform-browser'; 37 | import { NgModule } from '@angular/core'; 38 | import { AppComponent } from './app.component'; 39 | import { EffectsModule } from '@ngrx/effects'; 40 | 41 | @NgModule({ 42 | declarations: [ 43 | AppComponent 44 | ], 45 | imports: [ 46 | BrowserModule, 47 | ${effects} 48 | ], 49 | providers: [], 50 | bootstrap: [AppComponent] 51 | }) 52 | export class AppModule { } 53 | ` 54 | ); 55 | 56 | return tree; 57 | } 58 | -------------------------------------------------------------------------------- /modules/schematics/src/utility/test/create-reducers.ts: -------------------------------------------------------------------------------- 1 | import { Tree } from '@angular-devkit/schematics'; 2 | 3 | export function createReducers(tree: Tree, path?: string) { 4 | tree.create( 5 | path || '/src/app/reducers/index.ts', 6 | ` 7 | import { 8 | ActionReducer, 9 | ActionReducerMap, 10 | createFeatureSelector, 11 | createSelector, 12 | MetaReducer 13 | } from '@ngrx/store'; 14 | import { environment } from '../../environments/environment'; 15 | 16 | export interface State { 17 | 18 | } 19 | 20 | export const reducers: ActionReducerMap = { 21 | 22 | }; 23 | 24 | 25 | export const metaReducers: MetaReducer[] = !environment.production ? [] : []; 26 | ` 27 | ); 28 | 29 | return tree; 30 | } 31 | -------------------------------------------------------------------------------- /modules/schematics/src/utility/test/get-file-content.ts: -------------------------------------------------------------------------------- 1 | import { Tree } from '@angular-devkit/schematics'; 2 | 3 | export function getFileContent(tree: Tree, path: string): string { 4 | const fileEntry = tree.get(path); 5 | 6 | if (!fileEntry) { 7 | throw new Error(`The file (${path}) does not exist.`); 8 | } 9 | 10 | return fileEntry.content.toString(); 11 | } 12 | -------------------------------------------------------------------------------- /modules/schematics/src/utility/test/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-app-module'; 2 | export * from './get-file-content'; 3 | -------------------------------------------------------------------------------- /modules/schematics/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "stripInternal": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "../../dist/packages/schematics", 10 | "paths": { }, 11 | "rootDir": ".", 12 | "sourceMap": true, 13 | "inlineSources": true, 14 | "target": "es5", 15 | "lib": ["es2017", "dom"], 16 | "skipLibCheck": true, 17 | "strict": true 18 | }, 19 | "files": [ 20 | "src/action/index.ts", 21 | "src/container/index.ts", 22 | "src/effect/index.ts", 23 | "src/entity/index.ts", 24 | "src/feature/index.ts", 25 | "src/reducer/index.ts", 26 | "src/store/index.ts" 27 | ], 28 | "exclude": [ 29 | "src/*/files/**/*" 30 | ], 31 | "angularCompilerOptions": { 32 | "skipMetadataEmit": true, 33 | "enableSummariesForJit": false 34 | } 35 | } -------------------------------------------------------------------------------- /modules/store-devtools/BUILD: -------------------------------------------------------------------------------- 1 | load("//tools:defaults.bzl", "ts_library") 2 | 3 | ts_library( 4 | name = "store-devtools", 5 | srcs = glob([ 6 | "*.ts", 7 | "src/**/*.ts", 8 | ]), 9 | module_name = "@ngrx/store-devtools", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "//modules/store", 13 | "@rxjs", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /modules/store-devtools/README.md: -------------------------------------------------------------------------------- 1 | @ngrx/store-devtools 2 | ======= 3 | 4 | The sources for this package are in the main [ngrx/platform](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo. 5 | 6 | License: MIT 7 | -------------------------------------------------------------------------------- /modules/store-devtools/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DO NOT EDIT 3 | * 4 | * This file is automatically generated at build 5 | */ 6 | 7 | export * from './public_api'; 8 | -------------------------------------------------------------------------------- /modules/store-devtools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngrx/store-devtools", 3 | "version": "5.1.0", 4 | "description": "Developer tools for @ngrx/store", 5 | "module": "@ngrx/store-devtools.es5.js", 6 | "es2015": "@ngrx/store-devtools.js", 7 | "main": "bundles/store-devtools.umd.js", 8 | "typings": "store-devtools.d.ts", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ngrx/platform.git" 12 | }, 13 | "keywords": [ 14 | "RxJS", 15 | "Angular", 16 | "Redux", 17 | "Store", 18 | "@ngrx/store" 19 | ], 20 | "contributors": [ 21 | { 22 | "name": "Rob Wormald", 23 | "email": "robwormald@gmail.com" 24 | }, 25 | { 26 | "name": "Mike Ryan", 27 | "email": "mikeryan52@gmail.com" 28 | } 29 | ], 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/ngrx/platform/issues" 33 | }, 34 | "homepage": "https://github.com/ngrx/platform#readme", 35 | "peerDependencies": { 36 | "@ngrx/store": "^5.0.0", 37 | "rxjs": "^5.5.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /modules/store-devtools/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './src/index'; 2 | -------------------------------------------------------------------------------- /modules/store-devtools/rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | entry: './dist/store-devtools/@ngrx/store-devtools.es5.js', 3 | dest: './dist/store-devtools/bundles/store-devtools.umd.js', 4 | format: 'umd', 5 | exports: 'named', 6 | moduleName: 'ngrx.storeDevtools', 7 | globals: { 8 | '@angular/core': 'ng.core', 9 | '@ngrx/store': 'ngrx.store', 10 | 11 | 'rxjs/BehaviorSubject': 'Rx', 12 | 'rxjs/Observable': 'Rx', 13 | 'rxjs/Observer': 'Rx', 14 | 'rxjs/ReplaySubject': 'Rx', 15 | 'rxjs/Subscriber': 'Rx', 16 | 'rxjs/Subscription': 'Rx', 17 | 18 | 'rxjs/scheduler/queue': 'Rx.Scheduler', 19 | 20 | 'rxjs/observable/empty': 'Rx.Observable', 21 | 22 | 'rxjs/operator/filter': 'Rx.Observable.prototype', 23 | 'rxjs/operator/map': 'Rx.Observable.prototype', 24 | 'rxjs/operator/merge': 'Rx.Observable.prototype', 25 | 'rxjs/operator/observeOn': 'Rx.Observable.prototype', 26 | 'rxjs/operator/publishReplay': 'Rx.Observable.prototype', 27 | 'rxjs/operator/scan': 'Rx.Observable.prototype', 28 | 'rxjs/operator/share': 'Rx.Observable.prototype', 29 | 'rxjs/operator/skip': 'Rx.Observable.prototype', 30 | 'rxjs/operator/switchMap': 'Rx.Observable.prototype', 31 | 'rxjs/operator/takeUntil': 'Rx.Observable.prototype', 32 | 'rxjs/operator/withLatestFrom': 'Rx.Observable.prototype' 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /modules/store-devtools/spec/BUILD: -------------------------------------------------------------------------------- 1 | load("//tools:defaults.bzl", "ts_test_library", "jasmine_node_test") 2 | 3 | ts_test_library( 4 | name = "test_lib", 5 | srcs = glob( 6 | [ 7 | "**/*.ts", 8 | ], 9 | ), 10 | deps = [ 11 | "//modules/store", 12 | "//modules/store-devtools", 13 | "@rxjs", 14 | ], 15 | ) 16 | 17 | jasmine_node_test( 18 | name = "test", 19 | deps = [ 20 | ":test_lib", 21 | "//modules/store", 22 | "//modules/store-devtools", 23 | "@rxjs", 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /modules/store-devtools/spec/config.spec.ts: -------------------------------------------------------------------------------- 1 | import { ActionReducer, Action } from '@ngrx/store'; 2 | import { StoreDevtoolsConfig } from '../'; 3 | 4 | describe('StoreDevtoolsOptions', () => { 5 | it('can be initialized with name', () => { 6 | const options = new StoreDevtoolsConfig(); 7 | options.name = 'my instance'; 8 | expect(options.name).toBe('my instance'); 9 | }); 10 | 11 | it('can be initialized with actionSanitizer', () => { 12 | const options = new StoreDevtoolsConfig(); 13 | function sanitizer(action: Action, id: number): Action { 14 | return action; 15 | } 16 | options.actionSanitizer = sanitizer; 17 | expect(options.actionSanitizer).toEqual(sanitizer); 18 | }); 19 | 20 | it('can be initialized with stateSanitizer', () => { 21 | const options = new StoreDevtoolsConfig(); 22 | function stateSanitizer(state: any, index: number): any { 23 | return state; 24 | } 25 | options.stateSanitizer = stateSanitizer; 26 | expect(options.stateSanitizer).toEqual(stateSanitizer); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /modules/store-devtools/spec/integration.spec.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { StoreModule, Store, ActionsSubject } from '@ngrx/store'; 4 | import { StoreDevtoolsModule, StoreDevtools } from '@ngrx/store-devtools'; 5 | 6 | describe('Devtools Integration', () => { 7 | let store: Store; 8 | 9 | @NgModule({ 10 | imports: [StoreModule.forFeature('a', (state: any, action: any) => state)], 11 | }) 12 | class EagerFeatureModule {} 13 | 14 | @NgModule({ 15 | imports: [ 16 | StoreModule.forRoot({}), 17 | EagerFeatureModule, 18 | StoreDevtoolsModule.instrument(), 19 | ], 20 | }) 21 | class RootModule {} 22 | 23 | beforeEach(() => { 24 | TestBed.configureTestingModule({ 25 | imports: [RootModule], 26 | }); 27 | }); 28 | 29 | it('should load the store eagerly', () => { 30 | let error = false; 31 | 32 | try { 33 | let store = TestBed.get(Store); 34 | store.subscribe(); 35 | } catch (e) { 36 | error = true; 37 | } 38 | 39 | expect(error).toBeFalsy(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /modules/store-devtools/src/config.ts: -------------------------------------------------------------------------------- 1 | import { ActionReducer, Action } from '@ngrx/store'; 2 | import { InjectionToken, Type } from '@angular/core'; 3 | 4 | export type ActionSanitizer = (action: Action, id: number) => Action; 5 | export type StateSanitizer = (state: any, index: number) => any; 6 | 7 | export class StoreDevtoolsConfig { 8 | maxAge: number | false; 9 | monitor: ActionReducer; 10 | actionSanitizer?: ActionSanitizer; 11 | stateSanitizer?: StateSanitizer; 12 | name?: string; 13 | serialize?: boolean; 14 | logOnly?: boolean; 15 | features?: any; 16 | } 17 | 18 | export const STORE_DEVTOOLS_CONFIG = new InjectionToken( 19 | '@ngrx/devtools Options' 20 | ); 21 | export const INITIAL_OPTIONS = new InjectionToken( 22 | '@ngrx/devtools Initial Config' 23 | ); 24 | 25 | export type StoreDevtoolsOptions = 26 | | Partial 27 | | (() => Partial); 28 | -------------------------------------------------------------------------------- /modules/store-devtools/src/index.ts: -------------------------------------------------------------------------------- 1 | export { StoreDevtoolsModule } from './instrument'; 2 | export { LiftedState } from './reducer'; 3 | export { StoreDevtools } from './devtools'; 4 | export { StoreDevtoolsConfig, StoreDevtoolsOptions } from './config'; 5 | -------------------------------------------------------------------------------- /modules/store-devtools/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { Observable } from 'rxjs/Observable'; 3 | import { LiftedState } from './reducer'; 4 | import * as Actions from './actions'; 5 | 6 | export function difference(first: any[], second: any[]) { 7 | return first.filter(item => second.indexOf(item) < 0); 8 | } 9 | 10 | /** 11 | * Provides an app's view into the state of the lifted store. 12 | */ 13 | export function unliftState(liftedState: LiftedState) { 14 | const { computedStates, currentStateIndex } = liftedState; 15 | const { state } = computedStates[currentStateIndex]; 16 | 17 | return state; 18 | } 19 | 20 | export function unliftAction(liftedState: LiftedState) { 21 | return liftedState.actionsById[liftedState.nextActionId - 1]; 22 | } 23 | 24 | /** 25 | * Lifts an app's action into an action on the lifted store. 26 | */ 27 | export function liftAction(action: Action) { 28 | return new Actions.PerformAction(action); 29 | } 30 | 31 | export function applyOperators( 32 | input$: Observable, 33 | operators: any[][] 34 | ): Observable { 35 | return operators.reduce((source$, [operator, ...args]) => { 36 | return operator.apply(source$, args); 37 | }, input$); 38 | } 39 | -------------------------------------------------------------------------------- /modules/store-devtools/tests.js: -------------------------------------------------------------------------------- 1 | require('ts-node/register'); 2 | require('core-js/es7/reflect'); 3 | require('zone.js/dist/zone-node.js'); 4 | require('zone.js/dist/long-stack-trace-zone.js'); 5 | require('zone.js/dist/proxy.js'); 6 | require('zone.js/dist/sync-test.js'); 7 | require('zone.js/dist/async-test.js'); 8 | require('zone.js/dist/fake-async-test.js'); 9 | const Jasmine = require('jasmine'); 10 | 11 | const runner = new Jasmine(); 12 | 13 | global.jasmine = runner.jasmine; 14 | 15 | require('zone.js/dist/jasmine-patch.js'); 16 | 17 | const { getTestBed } = require('@angular/core/testing'); 18 | const { ServerTestingModule, platformServerTesting } = require('@angular/platform-server/testing'); 19 | 20 | getTestBed().initTestEnvironment(ServerTestingModule, platformServerTesting()); 21 | 22 | runner.loadConfig({ 23 | spec_dir: 'spec', 24 | spec_files: [ '**/*.spec.ts' ] 25 | }); 26 | 27 | runner.execute(); 28 | -------------------------------------------------------------------------------- /modules/store-devtools/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "stripInternal": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "noEmitOnError": false, 11 | "noImplicitAny": true, 12 | "noImplicitReturns": true, 13 | "outDir": "../../dist/packages/store-devtools", 14 | "paths": { 15 | "@ngrx/store": ["../../dist/packages/store"] 16 | }, 17 | "rootDir": ".", 18 | "sourceMap": true, 19 | "inlineSources": true, 20 | "lib": ["es2015", "dom"], 21 | "target": "es2015", 22 | "skipLibCheck": true 23 | }, 24 | "files": [ 25 | "public_api.ts" 26 | ], 27 | "angularCompilerOptions": { 28 | "annotateForClosureCompiler": true, 29 | "strictMetadataEmit": true, 30 | "flatModuleOutFile": "index.js", 31 | "flatModuleId": "@ngrx/store-devtools" 32 | } 33 | } -------------------------------------------------------------------------------- /modules/store/BUILD: -------------------------------------------------------------------------------- 1 | load("//tools:defaults.bzl", "ts_library") 2 | 3 | ts_library( 4 | name = "store", 5 | srcs = glob([ 6 | "*.ts", 7 | "src/**/*.ts", 8 | ]), 9 | module_name = "@ngrx/store", 10 | visibility = ["//visibility:public"], 11 | deps = ["@rxjs"], 12 | ) 13 | -------------------------------------------------------------------------------- /modules/store/README.md: -------------------------------------------------------------------------------- 1 | @ngrx/store 2 | ======= 3 | 4 | The sources for this package are in the main [ngrx/platform](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo. 5 | 6 | License: MIT 7 | -------------------------------------------------------------------------------- /modules/store/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DO NOT EDIT 3 | * 4 | * This file is automatically generated at build 5 | */ 6 | 7 | export * from './public_api'; 8 | -------------------------------------------------------------------------------- /modules/store/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngrx/store", 3 | "version": "5.1.0", 4 | "description": "RxJS powered Redux for Angular apps", 5 | "module": "@ngrx/store.es5.js", 6 | "es2015": "@ngrx/store.js", 7 | "main": "bundles/store.umd.js", 8 | "typings": "store.d.ts", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ngrx/platform.git" 12 | }, 13 | "keywords": [ 14 | "RxJS", 15 | "Angular", 16 | "Redux" 17 | ], 18 | "author": "Rob Wormald ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/ngrx/platform/issues" 22 | }, 23 | "homepage": "https://github.com/ngrx/platform#readme", 24 | "peerDependencies": { 25 | "@angular/core": "^5.0.0", 26 | "rxjs": "^5.5.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/store/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './src/index'; 2 | -------------------------------------------------------------------------------- /modules/store/rollup.config.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | entry: './dist/store/@ngrx/store.es5.js', 4 | dest: './dist/store/bundles/store.umd.js', 5 | format: 'umd', 6 | exports: 'named', 7 | moduleName: 'ngrx.store', 8 | globals: { 9 | '@angular/core': 'ng.core', 10 | 'rxjs/Observable': 'Rx', 11 | 'rxjs/BehaviorSubject': 'Rx', 12 | 'rxjs/Subject': 'Rx', 13 | 'rxjs/Subscriber': 'Rx', 14 | 'rxjs/scheduler/queue': 'Rx.Scheduler', 15 | 'rxjs/operator/map': 'Rx.Observable.prototype', 16 | 'rxjs/operator/pluck': 'Rx.Observable.prototype', 17 | 'rxjs/operator/distinctUntilChanged': 'Rx.Observable.prototype', 18 | 'rxjs/operator/observeOn': 'Rx.Observable.prototype', 19 | 'rxjs/operator/scan': 'Rx.Observable.prototype', 20 | 'rxjs/operator/withLatestFrom': 'Rx.Observable' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /modules/store/spec/BUILD: -------------------------------------------------------------------------------- 1 | load("//tools:defaults.bzl", "ts_test_library", "jasmine_node_test") 2 | 3 | ts_test_library( 4 | name = "test_lib", 5 | srcs = glob( 6 | [ 7 | "**/*.ts", 8 | ], 9 | exclude = ["ngc/**/*.ts"], 10 | ), 11 | deps = [ 12 | "//modules/store", 13 | "@rxjs", 14 | ], 15 | ) 16 | 17 | jasmine_node_test( 18 | name = "test", 19 | deps = [ 20 | ":test_lib", 21 | "//modules/store", 22 | "@rxjs", 23 | ], 24 | ) 25 | -------------------------------------------------------------------------------- /modules/store/spec/edge.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { Observable } from 'rxjs/Observable'; 3 | import { todos, todoCount } from './fixtures/edge_todos'; 4 | import { Store, StoreModule, select } from '@ngrx/store'; 5 | 6 | interface TestAppSchema { 7 | counter1: number; 8 | counter2: number; 9 | counter3: number; 10 | } 11 | 12 | interface Todo {} 13 | 14 | interface TodoAppSchema { 15 | todoCount: number; 16 | todos: Todo[]; 17 | } 18 | 19 | describe('ngRx Store', () => { 20 | describe('basic store actions', () => { 21 | let store: Store; 22 | 23 | beforeEach(() => { 24 | TestBed.configureTestingModule({ 25 | imports: [ 26 | StoreModule.forRoot({ todos, todoCount } as any), 27 | ], 28 | }); 29 | 30 | store = TestBed.get(Store); 31 | }); 32 | 33 | it('should provide an Observable Store', () => { 34 | expect(store).toBeDefined(); 35 | }); 36 | 37 | it('should handle re-entrancy', (done: any) => { 38 | let todosNextCount = 0; 39 | let todosCountNextCount = 0; 40 | 41 | store.pipe(select('todos')).subscribe(todos => { 42 | todosNextCount++; 43 | store.dispatch({ type: 'SET_COUNT', payload: todos.length }); 44 | }); 45 | 46 | store.pipe(select('todoCount')).subscribe(count => { 47 | todosCountNextCount++; 48 | }); 49 | 50 | store.dispatch({ type: 'ADD_TODO', payload: { name: 'test' } }); 51 | expect(todosNextCount).toBe(2); 52 | expect(todosCountNextCount).toBe(2); 53 | 54 | setTimeout(() => { 55 | expect(todosNextCount).toBe(2); 56 | expect(todosCountNextCount).toBe(2); 57 | done(); 58 | }, 10); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /modules/store/spec/fixtures/counter.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | 3 | export const INCREMENT = 'INCREMENT'; 4 | export const DECREMENT = 'DECREMENT'; 5 | export const RESET = 'RESET'; 6 | 7 | export function counterReducer(state = 0, action: Action) { 8 | switch (action.type) { 9 | case INCREMENT: 10 | return state + 1; 11 | case DECREMENT: 12 | return state - 1; 13 | case RESET: 14 | return 0; 15 | default: 16 | return state; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /modules/store/spec/fixtures/edge_todos.ts: -------------------------------------------------------------------------------- 1 | interface State { 2 | id: string; 3 | text: string; 4 | completed: boolean; 5 | } 6 | 7 | const todo = (state: State | undefined, action: any) => { 8 | switch (action.type) { 9 | case 'ADD_TODO': 10 | return { 11 | id: action.payload.id, 12 | text: action.payload.text, 13 | completed: false, 14 | }; 15 | case 'TOGGLE_TODO': 16 | if (state!.id !== action.id) { 17 | return state; 18 | } 19 | 20 | return Object.assign({}, state, { 21 | completed: !state!.completed, 22 | }); 23 | 24 | default: 25 | return state; 26 | } 27 | }; 28 | 29 | export const todos = (state = [], action: any) => { 30 | switch (action.type) { 31 | case 'ADD_TODO': 32 | return [...state, todo(undefined, action)]; 33 | case 'TOGGLE_TODO': 34 | return state.map(t => todo(t, action)); 35 | default: 36 | return state; 37 | } 38 | }; 39 | 40 | export const todoCount = (state = 0, action: any) => { 41 | switch (action.type) { 42 | case 'SET_COUNT': 43 | return action.payload; 44 | default: 45 | return state; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /modules/store/spec/fixtures/todos.ts: -------------------------------------------------------------------------------- 1 | export interface TodoItem { 2 | id: number; 3 | completed: boolean; 4 | text: string; 5 | } 6 | 7 | export const ADD_TODO = 'ADD_TODO'; 8 | export const COMPLETE_TODO = 'COMPLETE_TODO'; 9 | export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'; 10 | export const COMPLETE_ALL_TODOS = 'COMPLETE_ALL_TODOS'; 11 | 12 | let _id = 0; 13 | 14 | export const VisibilityFilters = { 15 | SHOW_ALL: 'SHOW_ALL', 16 | SHOW_COMPLETED: 'SHOW_COMPLETED', 17 | SHOW_ACTIVE: 'SHOW_ACTIVE', 18 | }; 19 | 20 | export function visibilityFilter( 21 | state = VisibilityFilters.SHOW_ALL, 22 | { type, payload }: any 23 | ) { 24 | switch (type) { 25 | case SET_VISIBILITY_FILTER: 26 | return payload; 27 | default: 28 | return state; 29 | } 30 | } 31 | 32 | export function todos( 33 | state: TodoItem[] = [], 34 | { type, payload }: any 35 | ): TodoItem[] { 36 | switch (type) { 37 | case ADD_TODO: 38 | return [ 39 | ...state, 40 | { 41 | id: ++_id, 42 | text: payload.text, 43 | completed: false, 44 | }, 45 | ]; 46 | case COMPLETE_ALL_TODOS: 47 | return state.map(todo => ({ ...todo, completed: true })); 48 | case COMPLETE_TODO: 49 | return state.map( 50 | todo => (todo.id === payload.id ? { ...todo, completed: true } : todo) 51 | ); 52 | default: 53 | return state; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /modules/store/spec/ngc/main.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, Component, InjectionToken } from '@angular/core'; 2 | import { platformDynamicServer } from '@angular/platform-server'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | import { Store, StoreModule, combineReducers, select } from '../../'; 5 | import { counterReducer, INCREMENT, DECREMENT } from '../fixtures/counter'; 6 | import { todos } from '../fixtures/todos'; 7 | import { Observable } from 'rxjs/Observable'; 8 | 9 | @Component({ 10 | selector: 'ngc-spec-child-component', 11 | template: ` 12 | 13 | `, 14 | }) 15 | export class NgcSpecChildComponent {} 16 | 17 | @NgModule({ 18 | imports: [StoreModule.forFeature('feature', { todos: todos })], 19 | declarations: [NgcSpecChildComponent], 20 | exports: [NgcSpecChildComponent], 21 | }) 22 | export class FeatureModule {} 23 | 24 | export interface AppState { 25 | count: number; 26 | } 27 | 28 | export const reducerToken = new InjectionToken('Reducers'); 29 | 30 | @Component({ 31 | selector: 'ngc-spec-component', 32 | template: ` 33 | 34 | Count : {{ count | async }} 35 | 36 | 37 | 38 | `, 39 | }) 40 | export class NgcSpecComponent { 41 | count: Observable; 42 | constructor(public store: Store) { 43 | this.count = store.pipe(select(state => state.count)); 44 | } 45 | increment() { 46 | this.store.dispatch({ type: INCREMENT }); 47 | } 48 | decrement() { 49 | this.store.dispatch({ type: DECREMENT }); 50 | } 51 | } 52 | 53 | @NgModule({ 54 | imports: [ 55 | BrowserModule, 56 | StoreModule.forRoot(reducerToken, { 57 | initialState: { count: 0 }, 58 | reducerFactory: combineReducers, 59 | }), 60 | FeatureModule, 61 | ], 62 | providers: [ 63 | { 64 | provide: reducerToken, 65 | useValue: { count: counterReducer }, 66 | }, 67 | ], 68 | declarations: [NgcSpecComponent], 69 | bootstrap: [NgcSpecComponent], 70 | }) 71 | export class NgcSpecModule {} 72 | -------------------------------------------------------------------------------- /modules/store/spec/ngc/tsconfig.ngc.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "outDir": "./output", 9 | "lib": ["es2015", "dom"], 10 | "baseUrl": ".", 11 | "paths": { 12 | "@ngrx/store": ["../../../store"] 13 | } 14 | }, 15 | "files": [ 16 | "main.ts" 17 | ], 18 | "angularCompilerOptions": { 19 | "genDir": "ngfactory" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /modules/store/spec/state.spec.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | import { Subject } from 'rxjs/Subject'; 3 | import { ReflectiveInjector } from '@angular/core'; 4 | import { TestBed } from '@angular/core/testing'; 5 | import { StoreModule, Store, INIT } from '@ngrx/store'; 6 | 7 | describe('ngRx State', () => { 8 | const initialState = 123; 9 | const reducer = jasmine.createSpy('reducer').and.returnValue(initialState); 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ 14 | StoreModule.forRoot( 15 | { key: reducer }, 16 | { initialState: { key: initialState } } 17 | ), 18 | ], 19 | }); 20 | }); 21 | 22 | it('should call the reducer to scan over the dispatcher', function() { 23 | TestBed.get(Store); 24 | 25 | expect(reducer).toHaveBeenCalledWith(initialState, { 26 | type: INIT, 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /modules/store/src/actions_subject.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnDestroy, Provider } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { Observer } from 'rxjs/Observer'; 5 | import { Action } from './models'; 6 | 7 | export const INIT = '@ngrx/store/init' as '@ngrx/store/init'; 8 | 9 | @Injectable() 10 | export class ActionsSubject extends BehaviorSubject 11 | implements OnDestroy { 12 | constructor() { 13 | super({ type: INIT }); 14 | } 15 | 16 | next(action: Action): void { 17 | if (typeof action === 'undefined') { 18 | throw new TypeError(`Actions must be objects`); 19 | } else if (typeof action.type === 'undefined') { 20 | throw new TypeError(`Actions must have a type property`); 21 | } 22 | 23 | super.next(action); 24 | } 25 | 26 | complete() { 27 | /* noop */ 28 | } 29 | 30 | ngOnDestroy() { 31 | super.complete(); 32 | } 33 | } 34 | 35 | export const ACTIONS_SUBJECT_PROVIDERS: Provider[] = [ActionsSubject]; 36 | -------------------------------------------------------------------------------- /modules/store/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | Action, 3 | ActionReducer, 4 | ActionReducerMap, 5 | ActionReducerFactory, 6 | MetaReducer, 7 | Selector, 8 | } from './models'; 9 | export { Store, select } from './store'; 10 | export { combineReducers, compose, createReducerFactory } from './utils'; 11 | export { ActionsSubject, INIT } from './actions_subject'; 12 | export { 13 | ReducerManager, 14 | ReducerObservable, 15 | ReducerManagerDispatcher, 16 | UPDATE, 17 | } from './reducer_manager'; 18 | export { ScannedActionsSubject } from './scanned_actions_subject'; 19 | export { 20 | createSelector, 21 | createSelectorFactory, 22 | createFeatureSelector, 23 | defaultMemoize, 24 | defaultStateFn, 25 | MemoizeFn, 26 | MemoizedProjection, 27 | MemoizedSelector, 28 | } from './selector'; 29 | export { State, StateObservable, reduceState } from './state'; 30 | export { 31 | INITIAL_STATE, 32 | _REDUCER_FACTORY, 33 | REDUCER_FACTORY, 34 | _INITIAL_REDUCERS, 35 | INITIAL_REDUCERS, 36 | STORE_FEATURES, 37 | _INITIAL_STATE, 38 | META_REDUCERS, 39 | _STORE_REDUCERS, 40 | _FEATURE_REDUCERS, 41 | FEATURE_REDUCERS, 42 | _FEATURE_REDUCERS_TOKEN, 43 | } from './tokens'; 44 | export { 45 | StoreModule, 46 | StoreRootModule, 47 | StoreFeatureModule, 48 | _initialStateFactory, 49 | _createStoreReducers, 50 | _createFeatureReducers, 51 | } from './store_module'; 52 | -------------------------------------------------------------------------------- /modules/store/src/models.ts: -------------------------------------------------------------------------------- 1 | export interface Action { 2 | type: string; 3 | } 4 | 5 | export type TypeId = () => T; 6 | 7 | export type InitialState = Partial | TypeId> | void; 8 | 9 | export interface ActionReducer { 10 | (state: T | undefined, action: V): T; 11 | } 12 | 13 | export type ActionReducerMap = { 14 | [p in keyof T]: ActionReducer 15 | }; 16 | 17 | export interface ActionReducerFactory { 18 | ( 19 | reducerMap: ActionReducerMap, 20 | initialState?: InitialState 21 | ): ActionReducer; 22 | } 23 | 24 | export type MetaReducer = ( 25 | reducer: ActionReducer 26 | ) => ActionReducer; 27 | 28 | export interface StoreFeature { 29 | key: string; 30 | reducers: ActionReducerMap | ActionReducer; 31 | reducerFactory: ActionReducerFactory; 32 | initialState?: InitialState; 33 | metaReducers?: MetaReducer[]; 34 | } 35 | 36 | export interface Selector { 37 | (state: T): V; 38 | } 39 | -------------------------------------------------------------------------------- /modules/store/src/private_export.ts: -------------------------------------------------------------------------------- 1 | export { ActionsSubject } from './actions_subject'; 2 | export { ReducerManager, ReducerObservable } from './reducer_manager'; 3 | export { ScannedActionsSubject } from './scanned_actions_subject'; 4 | export { State, StateObservable, reduceState } from './state'; 5 | export { 6 | INITIAL_STATE, 7 | REDUCER_FACTORY, 8 | INITIAL_REDUCERS, 9 | STORE_FEATURES, 10 | } from './tokens'; 11 | export { StoreRootModule, StoreFeatureModule } from './store_module'; 12 | -------------------------------------------------------------------------------- /modules/store/src/scanned_actions_subject.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Provider, OnDestroy } from '@angular/core'; 2 | import { Subject } from 'rxjs/Subject'; 3 | import { Action } from './models'; 4 | 5 | @Injectable() 6 | export class ScannedActionsSubject extends Subject 7 | implements OnDestroy { 8 | ngOnDestroy() { 9 | this.complete(); 10 | } 11 | } 12 | 13 | export const SCANNED_ACTIONS_SUBJECT_PROVIDERS: Provider[] = [ 14 | ScannedActionsSubject, 15 | ]; 16 | -------------------------------------------------------------------------------- /modules/store/src/tokens.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | 3 | export const _INITIAL_STATE = new InjectionToken( 4 | '@ngrx/store Internal Initial State' 5 | ); 6 | export const INITIAL_STATE = new InjectionToken('@ngrx/store Initial State'); 7 | export const REDUCER_FACTORY = new InjectionToken( 8 | '@ngrx/store Reducer Factory' 9 | ); 10 | export const _REDUCER_FACTORY = new InjectionToken( 11 | '@ngrx/store Reducer Factory Provider' 12 | ); 13 | export const INITIAL_REDUCERS = new InjectionToken( 14 | '@ngrx/store Initial Reducers' 15 | ); 16 | export const _INITIAL_REDUCERS = new InjectionToken( 17 | '@ngrx/store Internal Initial Reducers' 18 | ); 19 | export const META_REDUCERS = new InjectionToken('@ngrx/store Meta Reducers'); 20 | export const STORE_FEATURES = new InjectionToken('@ngrx/store Store Features'); 21 | export const _STORE_REDUCERS = new InjectionToken( 22 | '@ngrx/store Internal Store Reducers' 23 | ); 24 | export const _FEATURE_REDUCERS = new InjectionToken( 25 | '@ngrx/store Internal Feature Reducers' 26 | ); 27 | export const _FEATURE_REDUCERS_TOKEN = new InjectionToken( 28 | '@ngrx/store Internal Feature Reducers Token' 29 | ); 30 | export const FEATURE_REDUCERS = new InjectionToken( 31 | '@ngrx/store Feature Reducers' 32 | ); 33 | -------------------------------------------------------------------------------- /modules/store/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "stripInternal": true, 6 | "experimentalDecorators": true, 7 | "module": "es2015", 8 | "moduleResolution": "node", 9 | "outDir": "../../dist/packages/store", 10 | "paths": { }, 11 | "rootDir": ".", 12 | "sourceMap": true, 13 | "inlineSources": true, 14 | "target": "es2015", 15 | "lib": ["es2015", "dom"], 16 | "skipLibCheck": true, 17 | "strict": true 18 | }, 19 | "files": [ 20 | "public_api.ts" 21 | ], 22 | "angularCompilerOptions": { 23 | "annotateForClosureCompiler": true, 24 | "strictMetadataEmit": true, 25 | "flatModuleOutFile": "index.js", 26 | "flatModuleId": "@ngrx/store" 27 | } 28 | } -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | /*global jasmine */ 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | exports.config = { 8 | allScriptsTimeout: 11000, 9 | specs: [ 10 | './e2e/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | directConnect: true, 16 | baseUrl: 'http://localhost:4200/', 17 | framework: 'jasmine', 18 | jasmineNodeOpts: { 19 | showColors: true, 20 | defaultTimeoutInterval: 30000, 21 | print: function() {} 22 | }, 23 | beforeLaunch: function() { 24 | require('ts-node').register({ 25 | project: 'e2e/tsconfig.e2e.json' 26 | }); 27 | }, 28 | onPrepare() { 29 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /setup-jest.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | global['CSS'] = null; 3 | 4 | /** 5 | * ISSUE: https://github.com/angular/material2/issues/7101 6 | * Workaround for JSDOM missing transform property 7 | */ 8 | Object.defineProperty(document.body.style, 'transform', { 9 | value: () => { 10 | return { 11 | enumerable: true, 12 | configurable: true, 13 | }; 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | require('ts-node/register'); 2 | require('core-js/es7/reflect'); 3 | require('zone.js/dist/zone-node.js'); 4 | require('zone.js/dist/long-stack-trace-zone.js'); 5 | require('zone.js/dist/proxy.js'); 6 | require('zone.js/dist/sync-test.js'); 7 | require('zone.js/dist/async-test.js'); 8 | require('zone.js/dist/fake-async-test.js'); 9 | const Jasmine = require('jasmine'); 10 | const moduleAlias = require('module-alias'); 11 | 12 | const runner = new Jasmine(); 13 | 14 | global.jasmine = runner.jasmine; 15 | 16 | require('zone.js/dist/jasmine-patch.js'); 17 | 18 | const { getTestBed } = require('@angular/core/testing'); 19 | const { ServerTestingModule, platformServerTesting } = require('@angular/platform-server/testing'); 20 | 21 | getTestBed().initTestEnvironment(ServerTestingModule, platformServerTesting()); 22 | 23 | moduleAlias.addAlias('@ngrx', __dirname + '/modules'); 24 | 25 | runner.loadConfig({ 26 | spec_dir: 'modules', 27 | spec_files: [ '**/*.spec.ts' ] 28 | }); 29 | 30 | runner.execute(); 31 | 32 | -------------------------------------------------------------------------------- /tools/BUILD: -------------------------------------------------------------------------------- 1 | # Marker file indicating this folder is a Bazel package 2 | -------------------------------------------------------------------------------- /tools/bazel.rc: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Typescript / Angular / Sass # 3 | ############################### 4 | 5 | # Make compilation fast, by keeping a few copies of the compilers 6 | # running as daemons, and cache SourceFile AST's to reduce parse time. 7 | build --strategy=TypeScriptCompile=worker 8 | build --strategy=AngularTemplateCompile=worker 9 | 10 | # Enable debugging tests with --config=debug 11 | test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results 12 | 13 | ############################### 14 | # Filesystem interactions # 15 | ############################### 16 | 17 | # Put bazel's symlinks under dist, so results go to dist/bin 18 | # There is still a `bazel-out` symlink created in the project root. 19 | build --symlink_prefix=dist/ 20 | 21 | # Performance: avoid stat'ing input files 22 | build --watchfs 23 | 24 | ############################### 25 | # Output # 26 | ############################### 27 | 28 | # A more useful default output mode for bazel query 29 | # Prints eg. "ng_module rule //foo:bar" rather than just "//foo:bar" 30 | query --output=label_kind 31 | 32 | # Don't print every dependency in :node_modules, for example 33 | query --noimplicit_deps 34 | 35 | # By default, failing tests don't print any output, it goes to the log file 36 | test --test_output=errors 37 | 38 | # Show which actions are run under workers, 39 | # and print all the actions running in parallel. 40 | # Helps to demonstrate that bazel uses all the cores on the machine. 41 | build --experimental_ui 42 | test --experimental_ui 43 | -------------------------------------------------------------------------------- /tools/defaults.bzl: -------------------------------------------------------------------------------- 1 | """Re-export of some bazel rules with repository-wide defaults.""" 2 | load("@build_bazel_rules_typescript//:defs.bzl", _ts_library = "ts_library") 3 | load("@build_bazel_rules_nodejs//:defs.bzl", _jasmine_node_test = "jasmine_node_test") 4 | 5 | def ts_library(tsconfig = None, node_modules = None, **kwargs): 6 | if not tsconfig: 7 | tsconfig = "//:tsconfig.json" 8 | if not node_modules: 9 | node_modules = "@ngrx_compiletime_deps//:node_modules" 10 | _ts_library(tsconfig = tsconfig, node_modules = node_modules, **kwargs) 11 | 12 | def ts_test_library(node_modules = None, **kwargs): 13 | if not node_modules: 14 | node_modules = "//:ngrx_test_dependencies" 15 | ts_library(node_modules = node_modules, testonly = 1, **kwargs) 16 | 17 | def jasmine_node_test(node_modules = None, bootstrap = None, deps = [], **kwargs): 18 | if not node_modules: 19 | node_modules = "//:ngrx_test_dependencies" 20 | if not bootstrap: 21 | bootstrap = ["ngrx/tools/testing/bootstrap_node_tests.js"] 22 | _jasmine_node_test( 23 | bootstrap = bootstrap, 24 | node_modules = node_modules, 25 | deps = ["//tools/testing:node"] + deps, 26 | **kwargs 27 | ) 28 | -------------------------------------------------------------------------------- /tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "compile-time dependencies under Bazel", 3 | "devDependencies": { 4 | "@angular/core": "6.0.0-beta.4", 5 | "@angular/router": "6.0.0-beta.4", 6 | "tsutils": "2.20.0", 7 | "typescript": "~2.6.0" 8 | } 9 | } -------------------------------------------------------------------------------- /tools/rxjs-patch-pr3322.js: -------------------------------------------------------------------------------- 1 | // patch https://github.com/ReactiveX/rxjs/pull/3322 2 | const replace = require('replace-in-file'); 3 | 4 | try { 5 | console.log('Patch in rxjs/pull/3322 until next release...'); 6 | const replacements = []; 7 | replacements.push(...replace.sync({ 8 | files: ['node_modules/rxjs/src/BUILD.bazel'], 9 | from: 'tsconfig =', 10 | // Replace with an extra space before = so it doesn't get applied more than once 11 | to: `node_modules = "@build_bazel_rules_typescript_tsc_wrapped_deps//:node_modules", 12 | tsconfig =` 13 | })); 14 | replacements.push(...replace.sync({ 15 | files: ['node_modules/rxjs/src/tsconfig.json'], 16 | from: '"files":', 17 | // Replace with an extra space before : so it doesn't get applied more than once 18 | to: `"bazelOptions": { 19 | "suppressTsconfigOverrideWarnings": true 20 | }, 21 | "files" :` 22 | })); 23 | console.log(` Modified files: ${JSON.stringify(replacements)}`); 24 | } catch (error) { 25 | console.error('Error occurred:', error); 26 | } -------------------------------------------------------------------------------- /tools/testing/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("//tools:defaults.bzl", "ts_test_library") 4 | 5 | ts_test_library( 6 | name = "node", 7 | srcs = ["bootstrap_node_tests.ts"], 8 | ) 9 | -------------------------------------------------------------------------------- /tools/testing/bootstrap_node_tests.ts: -------------------------------------------------------------------------------- 1 | import 'core-js/es7/reflect'; 2 | import 'zone.js/dist/zone-node.js'; 3 | import 'zone.js/dist/long-stack-trace-zone.js'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test.js'; 6 | import 'zone.js/dist/async-test.js'; 7 | import 'zone.js/dist/fake-async-test.js'; 8 | 9 | const jasmineCore: any = require('jasmine-core'); 10 | const patchedJasmine = jasmineCore.boot(jasmineCore); 11 | (global as any)['jasmine'] = patchedJasmine; 12 | 13 | jasmineCore.boot = function() { 14 | return patchedJasmine; 15 | }; 16 | 17 | import { TestBed } from '@angular/core/testing'; 18 | import { 19 | ServerTestingModule, 20 | platformServerTesting, 21 | } from '@angular/platform-server/testing'; 22 | 23 | require('zone.js/dist/jasmine-patch.js'); 24 | 25 | const originalConfigureTestingModule = TestBed.configureTestingModule; 26 | 27 | TestBed.configureTestingModule = function() { 28 | TestBed.resetTestingModule(); 29 | 30 | return originalConfigureTestingModule.apply(null, arguments); 31 | }; 32 | 33 | TestBed.initTestEnvironment(ServerTestingModule, platformServerTesting()); 34 | -------------------------------------------------------------------------------- /tools/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@angular/core@6.0.0-beta.4": 6 | version "6.0.0-beta.4" 7 | resolved "https://registry.yarnpkg.com/@angular/core/-/core-6.0.0-beta.4.tgz#82622bb0cbd6c79919962f0f106585a068781e81" 8 | dependencies: 9 | tslib "^1.7.1" 10 | 11 | "@angular/router@6.0.0-beta.4": 12 | version "6.0.0-beta.4" 13 | resolved "https://registry.yarnpkg.com/@angular/router/-/router-6.0.0-beta.4.tgz#2ab5b8d64ba8548856a589752e8e03ba483a2b0f" 14 | dependencies: 15 | tslib "^1.7.1" 16 | 17 | tslib@^1.7.1, tslib@^1.8.1: 18 | version "1.9.0" 19 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" 20 | 21 | tsutils@2.20.0: 22 | version "2.20.0" 23 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.20.0.tgz#303394064bc80be8ee04e10b8609ae852e9312d3" 24 | dependencies: 25 | tslib "^1.8.1" 26 | 27 | typescript@~2.6.0: 28 | version "2.6.2" 29 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "declaration": false, 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "noStrictGenericChecks": true, 9 | "lib": [ 10 | "es2016", 11 | "dom" 12 | ], 13 | "outDir": "../out-tsc/app", 14 | "target": "es5", 15 | "module": "commonjs", 16 | "baseUrl": "", 17 | "rootDir": "./", 18 | "strict": true, 19 | "paths": { 20 | "@ngrx/effects": [ 21 | "./modules/effects" 22 | ], 23 | "@ngrx/store": [ 24 | "./modules/store" 25 | ], 26 | "@ngrx/store-devtools": [ 27 | "./modules/store-devtools" 28 | ], 29 | "@ngrx/router-store": [ 30 | "./modules/router-store" 31 | ], 32 | "@ngrx/entity": [ 33 | "./modules/entity" 34 | ] 35 | } 36 | }, 37 | "exclude": [ 38 | "bazel-out", 39 | "dist", 40 | "node_modules", 41 | "**/*/node_modules", 42 | "modules/schematics/src/*/files/**/*" 43 | ], 44 | "compileOnSave": false, 45 | "buildOnSave": false, 46 | "atom": { 47 | "rewriteTsconfig": false 48 | }, 49 | "bazelOptions": { 50 | "suppressTsconfigOverrideWarnings": true 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "indent": [ 9 | true, 10 | "spaces" 11 | ], 12 | "no-duplicate-variable": true, 13 | "no-eval": true, 14 | "no-internal-module": true, 15 | "no-trailing-whitespace": true, 16 | "no-var-keyword": true, 17 | "one-line": [ 18 | true, 19 | "check-open-brace", 20 | "check-whitespace" 21 | ], 22 | "quotemark": [ 23 | true, 24 | "single" 25 | ], 26 | "semicolon": true, 27 | "triple-equals": [ 28 | true, 29 | "allow-null-check" 30 | ], 31 | "typedef-whitespace": [ 32 | true, 33 | { 34 | "call-signature": "nospace", 35 | "index-signature": "nospace", 36 | "parameter": "nospace", 37 | "property-declaration": "nospace", 38 | "variable-declaration": "nospace" 39 | } 40 | ], 41 | "variable-name": [ 42 | true, 43 | "ban-keywords", 44 | "allow-leading-underscore" 45 | ], 46 | "whitespace": [ 47 | true, 48 | "check-branch", 49 | "check-decl", 50 | "check-operator", 51 | "check-separator", 52 | "check-type" 53 | ] 54 | } 55 | } 56 | --------------------------------------------------------------------------------