├── .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 |
8 | menu
9 |
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 | Take Me Home
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 |
--------------------------------------------------------------------------------