├── .github
└── workflows
│ └── nodejs-build-validation.yml
├── .gitignore
├── .npmignore
├── .vscode
├── launch.json
└── settings.json
├── LICENSE
├── README.md
├── images
├── ObservableStore.png
└── reduxDevTools.png
├── labs
├── begin
│ ├── .editorconfig
│ ├── .gitignore
│ ├── .vscode
│ │ └── settings.json
│ ├── README.md
│ ├── angular.json
│ ├── browserslist
│ ├── e2e
│ │ ├── app.e2e-spec.ts
│ │ ├── app.po.ts
│ │ └── tsconfig.e2e.json
│ ├── karma.conf.js
│ ├── lab.md
│ ├── package-lock.json
│ ├── package.json
│ ├── protractor.conf.js
│ ├── src
│ │ ├── app
│ │ │ ├── app-dev.module.ts
│ │ │ ├── app-routing.module.ts
│ │ │ ├── app.component.html
│ │ │ ├── app.component.scss
│ │ │ ├── app.component.ts
│ │ │ ├── app.module.ts
│ │ │ ├── core
│ │ │ │ ├── in-memory-data.service.ts
│ │ │ │ ├── model
│ │ │ │ │ ├── customer.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── order.ts
│ │ │ │ ├── sorter.service.ts
│ │ │ │ └── user-settings.service.ts
│ │ │ ├── customers
│ │ │ │ ├── customers-edit
│ │ │ │ │ ├── customers-edit.component.html
│ │ │ │ │ ├── customers-edit.component.scss
│ │ │ │ │ └── customers-edit.component.ts
│ │ │ │ ├── customers-list
│ │ │ │ │ ├── customers-list.component.html
│ │ │ │ │ ├── customers-list.component.ts
│ │ │ │ │ └── filter-textbox.component.ts
│ │ │ │ ├── customers-routing.module.ts
│ │ │ │ ├── customers.component.html
│ │ │ │ ├── customers.component.ts
│ │ │ │ ├── customers.module.ts
│ │ │ │ └── customers.service.ts
│ │ │ ├── orders
│ │ │ │ ├── orders-routing.module.ts
│ │ │ │ ├── orders.component.html
│ │ │ │ ├── orders.component.scss
│ │ │ │ ├── orders.component.ts
│ │ │ │ ├── orders.module.ts
│ │ │ │ └── orders.service.ts
│ │ │ ├── shared
│ │ │ │ ├── capitalize.pipe.ts
│ │ │ │ ├── enums.ts
│ │ │ │ ├── interfaces.ts
│ │ │ │ └── shared.module.ts
│ │ │ └── user-settings
│ │ │ │ ├── user-settings.component.html
│ │ │ │ ├── user-settings.component.scss
│ │ │ │ └── user-settings.component.ts
│ │ ├── assets
│ │ │ └── styles.css
│ │ ├── environments
│ │ │ ├── environment.prod.ts
│ │ │ └── environment.ts
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── main.ts
│ │ ├── polyfills.ts
│ │ ├── test.ts
│ │ ├── tsconfig.app.json
│ │ ├── tsconfig.spec.json
│ │ └── typings.d.ts
│ ├── tsconfig.json
│ └── tslint.json
└── end
│ └── readme.md
├── modules
├── observable-store-extensions
│ ├── README.md
│ ├── angular
│ │ └── angular-devtools-extension.ts
│ ├── index.ts
│ ├── interfaces.ts
│ ├── package-lock.json
│ ├── package.json
│ ├── redux-devtools.extension.ts
│ └── tsconfig.json
├── observable-store
│ ├── README.md
│ ├── index.ts
│ ├── interfaces.ts
│ ├── observable-store-base.ts
│ ├── observable-store.ts
│ ├── package-lock.json
│ ├── package.json
│ ├── solvedIssues.md
│ ├── spec
│ │ ├── helpers
│ │ │ └── reporter.js
│ │ ├── jasmine.json
│ │ └── tsconfig.json
│ ├── tests
│ │ ├── mocks.ts
│ │ └── observable-store.spec.ts
│ ├── tsconfig.json
│ └── utilities
│ │ ├── cloner.service.spec.ts
│ │ └── cloner.service.ts
└── readme.md
└── samples
├── angular-simple-store
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── core
│ │ │ ├── store
│ │ │ │ ├── customer.ts
│ │ │ │ └── customers.service.ts
│ │ │ └── utilities
│ │ │ │ ├── property-resolver.ts
│ │ │ │ └── sorter.service.ts
│ │ └── shared
│ │ │ ├── auto-unsubscribe.decorator.ts
│ │ │ └── shared.module.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── angular-stateChanged
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
│ ├── app
│ │ ├── app-routing.module.ts
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── child
│ │ │ ├── child.component.css
│ │ │ ├── child.component.html
│ │ │ ├── child.component.spec.ts
│ │ │ └── child.component.ts
│ │ └── core
│ │ │ ├── customers.service.spec.ts
│ │ │ └── customers.service.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.css
│ └── test.ts
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── angular-store-edits
├── .editorconfig
├── .gitignore
├── README.md
├── angular.json
├── browserslist
├── e2e
│ ├── app.e2e-spec.ts
│ ├── app.po.ts
│ └── tsconfig.e2e.json
├── package-lock.json
├── package.json
├── src
│ ├── app
│ │ ├── app-dev.module.ts
│ │ ├── app-routing.module.ts
│ │ ├── app.component.html
│ │ ├── app.component.scss
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── core
│ │ │ ├── in-memory-data.service.ts
│ │ │ ├── model
│ │ │ │ ├── customer.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── order.ts
│ │ │ ├── sorter.service.ts
│ │ │ └── user-settings.service.ts
│ │ ├── customers
│ │ │ ├── customers-edit
│ │ │ │ ├── customers-edit.component.html
│ │ │ │ ├── customers-edit.component.scss
│ │ │ │ └── customers-edit.component.ts
│ │ │ ├── customers-list
│ │ │ │ ├── customers-list.component.html
│ │ │ │ ├── customers-list.component.ts
│ │ │ │ └── filter-textbox.component.ts
│ │ │ ├── customers-routing.module.ts
│ │ │ ├── customers.component.html
│ │ │ ├── customers.component.ts
│ │ │ ├── customers.module.ts
│ │ │ └── customers.service.ts
│ │ ├── orders
│ │ │ ├── orders-routing.module.ts
│ │ │ ├── orders.component.html
│ │ │ ├── orders.component.scss
│ │ │ ├── orders.component.ts
│ │ │ ├── orders.module.ts
│ │ │ └── orders.service.ts
│ │ ├── shared
│ │ │ ├── capitalize.pipe.ts
│ │ │ ├── enums.ts
│ │ │ ├── interfaces.ts
│ │ │ └── shared.module.ts
│ │ └── user-settings
│ │ │ ├── user-settings.component.html
│ │ │ ├── user-settings.component.scss
│ │ │ └── user-settings.component.ts
│ ├── assets
│ │ └── styles.css
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ └── test.ts
├── tsconfig.app.json
└── tsconfig.json
├── angular-store
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── config
│ └── nginx.conf
├── package-lock.json
├── package.json
├── server
│ ├── data
│ │ ├── customers.json
│ │ └── orders.json
│ ├── node.dockerfile
│ ├── package-lock.json
│ ├── package.json
│ └── server.js
├── src
│ ├── app
│ │ ├── app-routing.module.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── core
│ │ │ ├── core.module.ts
│ │ │ ├── services
│ │ │ │ ├── customers.service.ts
│ │ │ │ └── orders.service.ts
│ │ │ ├── sorter.service.ts
│ │ │ └── store
│ │ │ │ └── store-state.ts
│ │ ├── customers
│ │ │ ├── customers-list
│ │ │ │ ├── customers-list.component.html
│ │ │ │ ├── customers-list.component.ts
│ │ │ │ └── filter-textbox.component.ts
│ │ │ ├── customers-routing.module.ts
│ │ │ ├── customers.component.html
│ │ │ ├── customers.component.ts
│ │ │ └── customers.module.ts
│ │ ├── orders
│ │ │ ├── orders-routing.module.ts
│ │ │ ├── orders.component.css
│ │ │ ├── orders.component.html
│ │ │ ├── orders.component.ts
│ │ │ └── orders.module.ts
│ │ └── shared
│ │ │ ├── auto-unsubscribe.decorator.ts
│ │ │ ├── capitalize.pipe.ts
│ │ │ ├── interfaces.ts
│ │ │ └── shared.module.ts
│ ├── assets
│ │ ├── .gitkeep
│ │ ├── customers.json
│ │ ├── orders.json
│ │ └── styles.css
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ ├── test.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ └── typings.d.ts
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── javascript-demo
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── bundle.js
│ ├── customers.json
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── customers-store.js
│ └── main.js
└── webpack.config.js
├── react-store
├── .editorconfig
├── .gitignore
├── README.md
├── config-overrides.js
├── package.json
├── public
│ ├── customers.json
│ ├── favicon.ico
│ ├── index.html
│ ├── manifest.json
│ └── orders.json
├── solvedIssues.md
└── src
│ ├── Routes.jsx
│ ├── customers
│ ├── CustomerEdit.jsx
│ ├── CustomerRow.jsx
│ ├── CustomersContainer.jsx
│ └── CustomersList.jsx
│ ├── index.css
│ ├── index.js
│ ├── orders
│ ├── OrdersContainer.jsx
│ └── OrdersList.jsx
│ ├── serviceWorker.js
│ ├── stores
│ ├── CustomersStore.js
│ └── OrdersStore.js
│ └── utils
│ └── index.js
└── vue-store
├── .browserslistrc
├── .eslintrc.js
├── .gitignore
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
└── src
├── App.vue
├── assets
└── logo.png
├── components
└── HelloWorld.vue
├── main.js
├── router.js
└── views
├── About.vue
└── Home.vue
/.github/workflows/nodejs-build-validation.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: Build
5 |
6 | on:
7 | push:
8 | branches: [ "main" ]
9 | pull_request:
10 | branches: [ "main" ]
11 |
12 | jobs:
13 | build-observable-store:
14 |
15 | runs-on: ubuntu-latest
16 | defaults:
17 | run:
18 | working-directory: ./modules/observable-store/
19 |
20 | strategy:
21 | matrix:
22 | node-version: [14.x, 16.x, 18.x]
23 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
24 |
25 | steps:
26 | - uses: actions/checkout@v3
27 | - name: Use Node.js ${{ matrix.node-version }}
28 | uses: actions/setup-node@v3
29 | with:
30 | node-version: ${{ matrix.node-version }}
31 | cache: 'npm'
32 | cache-dependency-path: ./modules/observable-store/package-lock.json
33 | - run: npm ci
34 | - run: npm run build --if-present
35 | - run: npm test
36 |
37 | build-observable-store-extensions:
38 |
39 | runs-on: ubuntu-latest
40 | defaults:
41 | run:
42 | working-directory: ./modules/observable-store-extensions/
43 |
44 | strategy:
45 | matrix:
46 | node-version: [14.x, 16.x, 18.x]
47 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
48 |
49 | steps:
50 | - uses: actions/checkout@v3
51 | - name: Use Node.js ${{ matrix.node-version }}
52 | uses: actions/setup-node@v3
53 | with:
54 | node-version: ${{ matrix.node-version }}
55 | cache: 'npm'
56 | cache-dependency-path: ./modules/observable-store-extensions/package-lock.json
57 | - run: npm ci
58 | - run: npm run build --if-present
59 |
60 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | modules/**/dist
5 | tmp
6 | out-tsc
7 | .angular
8 |
9 | samples/angular/src/app/**/*.js
10 | samples/angular/src/app/**/*.js.map
11 |
12 | # dependencies
13 | node_modules
14 |
15 | # IDEs and editors
16 | .idea
17 | .project
18 | .classpath
19 | .c9/
20 | *.launch
21 | .settings/
22 | *.sublime-workspace
23 |
24 | # IDE - VSCode
25 | .vscode/*
26 | !.vscode/settings.json
27 | !.vscode/tasks.json
28 | !.vscode/launch.json
29 | !.vscode/extensions.json
30 |
31 | # misc
32 | /.sass-cache
33 | /connect.lock
34 | /coverage
35 | /libpeerconnection.log
36 | npm-debug.log
37 | yarn-error.log
38 | testem.log
39 | /typings
40 |
41 | # System Files
42 | .DS_Store
43 | Thumbs.db
44 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .idea
3 | src
4 | samples
5 | tsconfig.json
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Jasmine Current File",
11 | "program": "${workspaceFolder}/node_modules/jasmine-ts/lib/index",
12 | "args": ["--config=./spec/jasmine.json", "${file}"],
13 | "console": "integratedTerminal",
14 | "internalConsoleOptions": "neverOpen"
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "**/angular-simple/src/**/*.js.map": true,
5 | "**/angular-simple/src/**/*.js": true,
6 | "**/angular/src/**/*.js.map": true,
7 | "**/angular/src/**/*.js": true
8 | // "src/**/*.js.map": true,
9 | // "src/**/*.js": true,
10 | },
11 | "typescript.tsdk": "node_modules/typescript/lib"
12 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Dan Wahlin
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.
--------------------------------------------------------------------------------
/images/ObservableStore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/images/ObservableStore.png
--------------------------------------------------------------------------------
/images/reduxDevTools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/images/reduxDevTools.png
--------------------------------------------------------------------------------
/labs/begin/.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 |
--------------------------------------------------------------------------------
/labs/begin/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /dist-server
6 | /tmp
7 | /out-tsc
8 |
9 | # dependencies
10 | /node_modules
11 |
12 | # IDEs and editors
13 | /.idea
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 | *.sublime-workspace
20 |
21 | # IDE - VSCode
22 | .vscode/*
23 | !.vscode/settings.json
24 | !.vscode/tasks.json
25 | !.vscode/launch.json
26 | !.vscode/extensions.json
27 |
28 | # misc
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | yarn-error.log
35 | testem.log
36 | /typings
37 |
38 | # e2e
39 | /e2e/*.js
40 | /e2e/*.map
41 |
42 | # System Files
43 | .DS_Store
44 | Thumbs.db
45 |
--------------------------------------------------------------------------------
/labs/begin/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.fontFamily": "Fira Code, Menlo, Monaco, 'Courier New', monospace",
3 | "editor.fontSize": 15
4 | }
--------------------------------------------------------------------------------
/labs/begin/README.md:
--------------------------------------------------------------------------------
1 | ## Observable Store for Angular
2 |
3 | Open the `src/app/core/stores` folder for examples.
4 |
5 | ## Running the Project
6 |
7 | 1. Install the Angular CLI
8 |
9 | `npm install -g @angular/cli`
10 |
11 | 1. Run `npm install` within this folder.
12 |
13 | 1. Run `ng serve -o`
--------------------------------------------------------------------------------
/labs/begin/browserslist:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | > 0.5%
9 | last 2 versions
10 | Firefox ESR
11 | not dead
12 | not IE 9-11 # For IE 9-11 support, remove 'not'.
--------------------------------------------------------------------------------
/labs/begin/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('angular-jumpstart App', () => {
4 | let page: AppPage;
5 |
6 | beforeEach(() => {
7 | page = new AppPage();
8 | });
9 |
10 | it('should display welcome message', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('Welcome to cm!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/labs/begin/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('cm-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/labs/begin/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": [
9 | "jasmine",
10 | "jasminewd2",
11 | "node"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/labs/begin/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function(config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
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-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, 'coverage'),
20 | reports: ['html', 'lcovonly'],
21 | fixWebpackSourcePaths: true
22 | },
23 | angularCli: {
24 | environment: 'dev'
25 | },
26 | reporters: ['progress', 'kjhtml'],
27 | port: 9876,
28 | colors: true,
29 | logLevel: config.LOG_INFO,
30 | autoWatch: true,
31 | browsers: ['Chrome'],
32 | singleRun: false
33 | });
34 | };
35 |
--------------------------------------------------------------------------------
/labs/begin/lab.md:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | courseType: code
4 | categories: javascript, typescript, state management, store
5 | published: true
6 | public: true
7 | showLabNumbers: false
8 | showExerciseNumbers: false
9 |
10 | ---
11 |
12 | # Course: Using Observable Store
13 |
14 | In these labs you'll learn about Observable Store fundamentals and see how it can be used to manage state in an Angular application.
15 |
16 | Topics covered include:
17 |
18 | * Installing the Observable Store package
19 | * Configuring Observable Store
20 | * Adding Observable Store to Angular services
21 | * Subscribing to state changes
22 |
23 | ## Lab 1: Installing and Configuring Observable Store
24 |
25 | In this lab you'll install Observable Store using **npm** and configure Observable Store in **main.ts**.
26 |
27 |
36 |
--------------------------------------------------------------------------------
/labs/begin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "observable-store-demo",
3 | "version": "0.0.1",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve -o",
8 | "build": "ng build --prod",
9 | "test": "ng test",
10 | "lint": "ng lint",
11 | "e2e": "ng e2e"
12 | },
13 | "dependencies": {
14 | "@angular/animations": "~8.2.12",
15 | "@angular/common": "~8.2.12",
16 | "@angular/compiler": "~8.2.12",
17 | "@angular/core": "~11.0.5",
18 | "@angular/forms": "~8.2.12",
19 | "@angular/platform-browser": "~8.2.12",
20 | "@angular/platform-browser-dynamic": "~8.2.12",
21 | "@angular/router": "~8.2.12",
22 | "@codewithdan/observable-store": "^2.2.9",
23 | "@codewithdan/observable-store-extensions": "^2.2.7",
24 | "angular-in-memory-web-api": "^0.8.0",
25 | "core-js": "^2.5.4",
26 | "primeicons": "^2.0.0",
27 | "primeng": "^9.0.5",
28 | "rxjs": "^6.5.3",
29 | "subsink": "^1.0.0",
30 | "tslib": "^1.9.0",
31 | "zone.js": "~0.9.1"
32 | },
33 | "devDependencies": {
34 | "@angular-devkit/build-angular": "^14.2.9",
35 | "@angular/cli": "~8.3.29",
36 | "@angular/compiler-cli": "8.2.12",
37 | "@angular/language-service": "8.2.12",
38 | "@types/jasmine": "~3.3.2",
39 | "@types/jasminewd2": "~2.0.6",
40 | "@types/node": "^10.12.15",
41 | "codelyzer": "^5.0.1",
42 | "jasmine-core": "~2.8.0",
43 | "jasmine-spec-reporter": "~4.2.1",
44 | "karma": "^6.3.16",
45 | "karma-chrome-launcher": "~2.2.0",
46 | "karma-coverage-istanbul-reporter": "^1.4.2",
47 | "karma-jasmine": "~1.1.0",
48 | "karma-jasmine-html-reporter": "^0.2.2",
49 | "protractor": "^5.4.3",
50 | "ts-node": "~4.1.0",
51 | "tslint": "~5.9.1",
52 | "typescript": "3.5.3"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/labs/begin/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 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: ['./e2e/**/*.e2e-spec.ts'],
9 | capabilities: {
10 | browserName: 'chrome'
11 | },
12 | directConnect: true,
13 | baseUrl: 'http://localhost:4200/',
14 | framework: 'jasmine',
15 | jasmineNodeOpts: {
16 | showColors: true,
17 | defaultTimeoutInterval: 30000,
18 | print: function() {}
19 | },
20 | SELENIUM_PROMISE_MANAGER: false,
21 | onPrepare() {
22 | require('ts-node').register({
23 | project: 'e2e/tsconfig.e2e.json'
24 | });
25 | jasmine
26 | .getEnv()
27 | .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/labs/begin/src/app/app-dev.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { AppModule } from './app.module';
4 | import { AppComponent } from './app.component';
5 |
6 | import { HttpClientInMemoryWebApiModule, InMemoryDbService, InMemoryBackendConfigArgs} from 'angular-in-memory-web-api';
7 | import { InMemoryDataService } from './core/in-memory-data.service';
8 |
9 | @NgModule({
10 | imports: [
11 | AppModule,
12 | HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, {
13 | dataEncapsulation: false,
14 | delay: 100,
15 | passThruUnknownUrl: true,
16 | post204: false,
17 | put204: false
18 | })
19 | ],
20 | providers: [ { provide: InMemoryDataService, useExisting: InMemoryDbService } ],
21 | bootstrap: [ AppComponent ]
22 | })
23 | export class AppDevModule {}
24 |
--------------------------------------------------------------------------------
/labs/begin/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { UserSettingsComponent } from './user-settings/user-settings.component';
4 |
5 | const routes: Routes = [
6 | { path: '', pathMatch: 'full', redirectTo: 'customers' },
7 | { path: 'customers', loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule) },
8 | { path: 'orders/:id', loadChildren: () => import('./orders/orders.module').then(m => m.OrdersModule) },
9 | { path: 'settings', component: UserSettingsComponent }
10 | ];
11 |
12 | @NgModule({
13 | imports: [RouterModule.forRoot(routes)],
14 | exports: [RouterModule]
15 | })
16 | export class AppRoutingModule {}
17 |
--------------------------------------------------------------------------------
/labs/begin/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/labs/begin/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | .title {
2 | margin-right: 1em;
3 | }
4 |
5 | .preferred-name {
6 | color: #fff;
7 | place-self: center;
8 | margin-right: 30px;
9 | font-size: 15pt;
10 | }
11 |
12 | .circle {
13 | place-self: center;
14 | height:25px;
15 | width:25px;
16 | border-radius: 50px;
17 | background: red;
18 | color: white;
19 | margin-right: 10px;
20 | font-size: 13pt;
21 | text-align: center;
22 | cursor: pointer;
23 | }
24 |
25 | .settings {
26 | color: #fff;
27 | font-size: 15pt;
28 | margin-left: 15px;
29 | }
30 |
31 | header {
32 | background-color: #007bff;
33 | }
34 |
35 | :host-context(.light-theme) header{
36 | background-color: #007bff;
37 | }
38 |
39 | :host-context(.dark-theme) header{
40 | background-color: #000;
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/labs/begin/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
2 | import { DOCUMENT } from '@angular/common';
3 |
4 | import { CustomersService } from './customers/customers.service';
5 | import { UserSettingsService } from './core/user-settings.service';
6 | import { Theme } from './shared/enums';
7 | import { UserSettings } from './shared/interfaces';
8 | import { Observable, merge } from 'rxjs';
9 | import { map, tap } from 'rxjs/operators';
10 |
11 | @Component({
12 | selector: 'app-root',
13 | templateUrl: './app.component.html',
14 | styleUrls: ['./app.component.scss']
15 | })
16 | export class AppComponent implements OnInit {
17 | customersLength$: Observable;
18 | userSettings$: Observable;
19 |
20 | constructor(@Inject(DOCUMENT) private document: HTMLDocument,
21 | private customersService: CustomersService,
22 | private userSettingsService: UserSettingsService) { }
23 |
24 | ngOnInit() {
25 | this.userSettings$ = merge(
26 | this.userSettingsService.getUserSettings(), // Get initial data
27 | this.userSettingsService.userSettingsChanged() // Handle any changes
28 | .pipe(
29 | // tap(userSettings => console.log('userSettingsChanged: ', userSettings)),
30 | map(userSettings => this.updateTheme(userSettings))
31 | )
32 | );
33 |
34 | this.customersLength$ = this.customersService.stateChanged
35 | .pipe(
36 | map(state => {
37 | if (state && state.customers) {
38 | return state.customers.length;
39 | }
40 | })
41 | );
42 | }
43 |
44 | updateTheme(userSettings: UserSettings) {
45 | this.document.documentElement.className = (userSettings && userSettings.theme === Theme.Dark) ? 'dark-theme' : 'light-theme';
46 | return userSettings;
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/labs/begin/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
4 | import { HttpClientModule } from '@angular/common/http';
5 | import { FormsModule } from '@angular/forms';
6 |
7 | import { AppRoutingModule } from './app-routing.module';
8 | import { AppComponent } from './app.component';
9 | import { SelectButtonModule } from 'primeng/selectbutton';
10 | import { UserSettingsComponent } from './user-settings/user-settings.component';
11 |
12 |
13 | @NgModule({
14 | imports: [
15 | BrowserModule,
16 | BrowserAnimationsModule,
17 | HttpClientModule,
18 | FormsModule,
19 | AppRoutingModule,
20 | SelectButtonModule
21 | ],
22 | declarations: [AppComponent, UserSettingsComponent],
23 | bootstrap: [AppComponent]
24 | })
25 | export class AppModule {}
26 |
--------------------------------------------------------------------------------
/labs/begin/src/app/core/model/customer.ts:
--------------------------------------------------------------------------------
1 | export interface Customer {
2 | id: number;
3 | name: string;
4 | city: string;
5 | orderTotal: number;
6 | }
7 |
--------------------------------------------------------------------------------
/labs/begin/src/app/core/model/index.ts:
--------------------------------------------------------------------------------
1 | export * from './customer';
2 | export * from './order';
--------------------------------------------------------------------------------
/labs/begin/src/app/core/model/order.ts:
--------------------------------------------------------------------------------
1 | export interface Order {
2 | id: number;
3 | customerId: number;
4 | orderItems: OrderItem[];
5 | }
6 |
7 | export interface OrderItem {
8 | id: number;
9 | productName: string;
10 | itemCost: number;
11 | }
12 |
--------------------------------------------------------------------------------
/labs/begin/src/app/core/sorter.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable({ providedIn: 'root' })
4 | export class SorterService {
5 |
6 | property: string = null;
7 | direction = 1;
8 |
9 | sort(collection: any[], prop: any) {
10 | this.property = prop;
11 | this.direction = (this.property === prop) ? this.direction * -1 : 1;
12 |
13 | collection.sort((a: any, b: any) => {
14 | let aVal: any;
15 | let bVal: any;
16 |
17 | // Handle resolving complex properties such as 'state.name' for prop value
18 | if (prop && prop.indexOf('.') > -1) {
19 | aVal = this.resolveProperty(prop, a);
20 | bVal = this.resolveProperty(prop, b);
21 | } else {
22 | aVal = a[prop];
23 | bVal = b[prop];
24 | }
25 |
26 | // Fix issues that spaces before/after string value can cause such as ' San Francisco'
27 | if (this.isString(aVal)) { aVal = aVal.trim().toUpperCase(); }
28 | if (this.isString(bVal)) { bVal = bVal.trim().toUpperCase(); }
29 |
30 | if (aVal === bVal) {
31 | return 0;
32 | } else if (aVal > bVal) {
33 | return this.direction * -1;
34 | } else {
35 | return this.direction * 1;
36 | }
37 | });
38 | }
39 |
40 | isString(val: any): boolean {
41 | return (val && (typeof val === 'string' || val instanceof String));
42 | }
43 |
44 | resolveProperty(path: string, obj: any) {
45 | return path.split('.').reduce(function(prev, curr) {
46 | return (prev ? prev[curr] : undefined);
47 | }, obj || self);
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/labs/begin/src/app/core/user-settings.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 |
4 | import { Theme, Actions } from '../shared/enums';
5 | import { ObservableStore } from '@codewithdan/observable-store';
6 | import { StoreState, UserSettings } from '../shared/interfaces';
7 | import { catchError, map, switchMap } from 'rxjs/operators';
8 | import { Observable } from 'rxjs';
9 |
10 | @Injectable({
11 | providedIn: 'root'
12 | })
13 | export class UserSettingsService {
14 |
15 | apiUrl = 'api/userSettings';
16 |
17 | constructor(private http: HttpClient) {
18 |
19 | }
20 |
21 | getUserSettings() : Observable {
22 | return this.http.get(this.apiUrl)
23 | .pipe(
24 | map(userSettings => {
25 | let settings = userSettings[0]; // in-memory API returns an array but we only want one item
26 | // Add State to Store Here
27 |
28 |
29 | return settings;
30 | }),
31 | catchError(this.handleError)
32 | );
33 | }
34 |
35 | updateUserSettings(userSettings: UserSettings) {
36 | return this.http.put(this.apiUrl + '/' + userSettings.id, userSettings)
37 | .pipe(
38 | switchMap(settings => {
39 | // Update userSettings Here
40 |
41 |
42 | return this.getUserSettings();
43 | }),
44 | catchError(this.handleError)
45 | );
46 | }
47 |
48 | userSettingsChanged() : Observable {
49 | return this.stateChanged
50 | .pipe(
51 | // stateSliceSelector could be added to UserSettingsService contructor to filter the store down to userSettings
52 | map(state => {
53 | if (state) {
54 | return state.userSettings;
55 | }
56 | })
57 | );
58 | }
59 |
60 | private handleError(error: any) {
61 | console.error('server error:', error);
62 | if (error.error instanceof Error) {
63 | const errMessage = error.error.message;
64 | return Observable.throw(errMessage);
65 | }
66 | return Observable.throw(error || 'Server error');
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/labs/begin/src/app/customers/customers-edit/customers-edit.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/labs/begin/src/app/customers/customers-edit/customers-edit.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/labs/begin/src/app/customers/customers-edit/customers-edit.component.scss
--------------------------------------------------------------------------------
/labs/begin/src/app/customers/customers-edit/customers-edit.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy } from '@angular/core';
2 | import { FormBuilder, Validators } from '@angular/forms';
3 | import { SubSink } from 'subsink';
4 |
5 | import { Customer } from '../../core/model/customer';
6 | import { ActivatedRoute, Router } from '@angular/router';
7 | import { CustomersService } from '../customers.service';
8 |
9 | @Component({
10 | selector: 'app-customers-edit',
11 | templateUrl: './customers-edit.component.html',
12 | styleUrls: ['./customers-edit.component.scss']
13 | })
14 | export class CustomersEditComponent implements OnInit, OnDestroy {
15 |
16 | customerForm = this.formBuilder.group({
17 | id: [],
18 | name: [ '', Validators.required ],
19 | city: [ '', Validators.required ]
20 | });
21 |
22 | customer: Customer;
23 | subsink = new SubSink();
24 |
25 | constructor(
26 | private customersService: CustomersService,
27 | private router: Router,
28 | private formBuilder: FormBuilder,
29 | private route: ActivatedRoute) { }
30 |
31 | ngOnInit() {
32 | const id = +this.route.snapshot.paramMap.get('id');
33 | this.subsink.sink = this.customersService.get(id).subscribe(customer => {
34 | if (customer) {
35 | this.customer = customer;
36 | this.customerForm.patchValue(this.customer);
37 | }
38 | });
39 | }
40 |
41 | submit() {
42 | if (this.customerForm.valid) {
43 | const customerValue = { ...this.customer, ...this.customerForm.value } as Customer;
44 | if (customerValue.id) {
45 | this.update(customerValue);
46 | }
47 | else {
48 | this.add(customerValue);
49 | }
50 | }
51 | }
52 |
53 | add(customer: Customer) {
54 | this.subsink.sink = this.customersService.add(customer).subscribe(() => {
55 | this.navigateHome();
56 | });
57 | }
58 |
59 | delete() {
60 | this.subsink.sink = this.customersService.delete(this.customer.id).subscribe(() => {
61 | this.navigateHome();
62 | });
63 | }
64 |
65 | update(customer: Customer) {
66 | this.subsink.sink = this.customersService.update(customer).subscribe(() => {
67 | this.navigateHome();
68 | });
69 |
70 | }
71 |
72 | navigateHome() {
73 | this.router.navigate(['/customers']);
74 | }
75 |
76 | ngOnDestroy() {
77 | this.subsink.unsubscribe();
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/labs/begin/src/app/customers/customers-list/customers-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Name |
8 | City |
9 | Order Total |
10 | |
11 |
12 |
13 |
14 | {{ cust.name | capitalize }} |
15 | {{ cust.city }} |
16 | {{ cust.orderTotal | currency:currencyCode:'symbol' }} |
17 | Orders |
18 | Edit |
19 |
20 |
21 | |
22 |
23 | {{ customersOrderTotal | currency:currencyCode:'symbol' }}
24 | |
25 | |
26 |
27 |
28 | No customers found |
29 |
30 |
31 | Number of Customers: {{ filteredCustomers.length }}
32 |
--------------------------------------------------------------------------------
/labs/begin/src/app/customers/customers-list/customers-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnInit } from '@angular/core';
2 |
3 | import { SorterService } from '../../core/sorter.service';
4 | import { Customer } from '../../core/model/customer';
5 |
6 | @Component({
7 | selector: 'app-customers-list',
8 | templateUrl: './customers-list.component.html'
9 | })
10 | export class CustomersListComponent implements OnInit {
11 | private _customers: Customer[] = [];
12 | @Input() get customers(): Customer[] {
13 | return this._customers;
14 | }
15 | set customers(value: Customer[]) {
16 | if (value) {
17 | this.filteredCustomers = this._customers = value;
18 | this.calculateOrders();
19 | }
20 | }
21 | filteredCustomers: Customer[] = [];
22 | customersOrderTotal: number;
23 | currencyCode = 'USD';
24 |
25 | constructor(private sorterService: SorterService) { }
26 |
27 | ngOnInit() {
28 |
29 | }
30 |
31 | calculateOrders() {
32 | this.customersOrderTotal = 0;
33 | this.filteredCustomers.forEach((cust: Customer) => {
34 | this.customersOrderTotal += cust.orderTotal;
35 | });
36 | }
37 |
38 | filter(data: string) {
39 | if (data) {
40 | this.filteredCustomers = this.customers.filter((cust: Customer) => {
41 | return cust.name.toLowerCase().indexOf(data.toLowerCase()) > -1 ||
42 | cust.city.toLowerCase().indexOf(data.toLowerCase()) > -1 ||
43 | cust.orderTotal.toString().indexOf(data) > -1;
44 | });
45 | } else {
46 | this.filteredCustomers = this.customers;
47 | }
48 | this.calculateOrders();
49 | }
50 |
51 | sort(prop: string) {
52 | this.sorterService.sort(this.filteredCustomers, prop);
53 | }
54 |
55 | customerTrackBy(index: number, customer: Customer) {
56 | return customer.id;
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/labs/begin/src/app/customers/customers-list/filter-textbox.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-filter-textbox',
5 | template: `
6 | Filter:
7 | `
8 | })
9 | export class FilterTextboxComponent implements OnInit {
10 |
11 | private _filter = '';
12 |
13 | @Input() get filter() {
14 | return this._filter;
15 | }
16 |
17 | set filter(val: string) {
18 | this._filter = val;
19 | this.changed.emit(this.filter); // Raise changed event
20 | }
21 |
22 | @Output() changed: EventEmitter = new EventEmitter();
23 |
24 | constructor() { }
25 |
26 | ngOnInit() {
27 |
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/labs/begin/src/app/customers/customers-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 |
4 | import { CustomersComponent } from './customers.component';
5 | import { CustomersEditComponent } from './customers-edit/customers-edit.component';
6 |
7 | const routes: Routes = [
8 | { path: '', component: CustomersComponent },
9 | { path: ':id', component: CustomersEditComponent }
10 | ];
11 |
12 | @NgModule({
13 | imports: [ RouterModule.forChild(routes) ],
14 | exports: [ RouterModule ]
15 | })
16 | export class CustomersRoutingModule {}
17 |
--------------------------------------------------------------------------------
/labs/begin/src/app/customers/customers.component.html:
--------------------------------------------------------------------------------
1 | {{ title }}
2 |
3 |
--------------------------------------------------------------------------------
/labs/begin/src/app/customers/customers.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | import { Customer } from '../core/model/customer';
4 | import { Observable, of, merge } from 'rxjs';
5 | import { map } from 'rxjs/operators';
6 | import { CustomersService } from './customers.service';
7 |
8 | @Component({
9 | selector: 'app-customers',
10 | templateUrl: './customers.component.html'
11 | })
12 | export class CustomersComponent implements OnInit {
13 | title = 'Customers';
14 | customers$: Observable;
15 |
16 | constructor(private customersService: CustomersService) {}
17 |
18 | ngOnInit() {
19 | this.customers$ = this.customersService.getAll();
20 |
21 | // Could do this to get initial customers plus
22 | // listen for any changes
23 | // this.customers$ = merge(
24 | // // Get initial
25 | // this.customersService.getAll(),
26 | // // Capture any changes to the store
27 | // this.customersService.stateChanged.pipe(
28 | // map(state => {
29 | // if (state) {
30 | // return state.customers;
31 | // }
32 | // })
33 | // ));
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/labs/begin/src/app/customers/customers.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 |
4 | import { SharedModule } from '../shared/shared.module';
5 | import { CustomersListComponent } from './customers-list/customers-list.component';
6 | import { FilterTextboxComponent } from './customers-list/filter-textbox.component';
7 | import { CustomersComponent } from './customers.component';
8 | import { CustomersRoutingModule } from './customers-routing.module';
9 | import { CustomersEditComponent } from './customers-edit/customers-edit.component';
10 |
11 | @NgModule({
12 | imports: [ CommonModule, SharedModule, CustomersRoutingModule ],
13 | declarations: [ CustomersListComponent, FilterTextboxComponent, CustomersComponent, CustomersEditComponent]
14 | })
15 | export class CustomersModule { }
16 |
--------------------------------------------------------------------------------
/labs/begin/src/app/orders/orders-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 |
4 | import { OrdersComponent } from '../orders/orders.component';
5 |
6 | const routes: Routes = [
7 | { path: '', component: OrdersComponent }
8 | ];
9 |
10 | @NgModule({
11 | imports: [ RouterModule.forChild(routes) ],
12 | exports: [ RouterModule ]
13 | })
14 | export class OrdersRoutingModule {
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/labs/begin/src/app/orders/orders.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Orders
3 |
4 |
6 |
7 | {{ orderItem.productName }} |
8 | {{ orderItem.itemCost | currency:'USD':'symbol' }} |
9 |
10 |
11 |
12 |
13 | View All Customers
14 |
15 |
--------------------------------------------------------------------------------
/labs/begin/src/app/orders/orders.component.scss:
--------------------------------------------------------------------------------
1 | .orders-table {
2 | margin-top: 20px;
3 | }
--------------------------------------------------------------------------------
/labs/begin/src/app/orders/orders.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ActivatedRoute } from '@angular/router';
3 | import { Order } from '../core/model/order';
4 | import { Observable } from 'rxjs';
5 | import { OrdersService } from './orders.service';
6 |
7 | @Component({
8 | selector: 'app-orders',
9 | templateUrl: './orders.component.html',
10 | styleUrls: [ './orders.component.scss' ]
11 | })
12 | export class OrdersComponent implements OnInit {
13 |
14 | orders$: Observable;
15 |
16 | constructor(private ordersService: OrdersService,
17 | private route: ActivatedRoute) {
18 |
19 | }
20 |
21 | ngOnInit() {
22 | const id = +this.route.snapshot.paramMap.get('id');
23 | this.orders$ = this.ordersService.get(id);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/labs/begin/src/app/orders/orders.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 | import { FormsModule } from '@angular/forms';
4 | import { RouterModule } from '@angular/router';
5 |
6 | import { SharedModule } from '../shared/shared.module';
7 | import { OrdersComponent } from './orders.component';
8 | import { OrdersRoutingModule } from './orders-routing.module';
9 |
10 | @NgModule({
11 | imports: [ CommonModule, FormsModule, SharedModule, OrdersRoutingModule ],
12 | declarations: [ OrdersComponent ]
13 | })
14 | export class OrdersModule { }
15 |
--------------------------------------------------------------------------------
/labs/begin/src/app/shared/capitalize.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({ name: 'capitalize' })
4 | export class CapitalizePipe implements PipeTransform {
5 | transform(value: any) {
6 | if (value) {
7 | return value.charAt(0).toUpperCase() + value.slice(1);
8 | }
9 | return value;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/labs/begin/src/app/shared/enums.ts:
--------------------------------------------------------------------------------
1 | export enum Theme {
2 | Light,
3 | Dark
4 | }
5 |
6 | export enum Actions {
7 | AddUserSettings = 'SET_USER_SETTINGS',
8 | UpdateUserSettings = 'UPDATE_USERSETTINGS'
9 | }
--------------------------------------------------------------------------------
/labs/begin/src/app/shared/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { Customer, Order } from '../core/model';
2 | import { Theme } from './enums';
3 |
4 | export interface StoreState {
5 | customers: Customer[];
6 | customer: Customer;
7 | orders: Order[];
8 | userSettings: UserSettings;
9 | }
10 |
11 | export interface UserSettings {
12 | id: number;
13 | preferredName: string;
14 | email: string;
15 | theme: Theme;
16 | }
--------------------------------------------------------------------------------
/labs/begin/src/app/shared/shared.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 | import { CapitalizePipe } from './capitalize.pipe';
5 |
6 | @NgModule({
7 | imports: [ CommonModule, ReactiveFormsModule ],
8 | exports: [ ReactiveFormsModule, CapitalizePipe ],
9 | declarations: [ CapitalizePipe ]
10 | })
11 | export class SharedModule {}
12 |
--------------------------------------------------------------------------------
/labs/begin/src/app/user-settings/user-settings.component.html:
--------------------------------------------------------------------------------
1 | User Settings
2 |
8 |
9 |
15 |
16 |
22 |
23 |
--------------------------------------------------------------------------------
/labs/begin/src/app/user-settings/user-settings.component.scss:
--------------------------------------------------------------------------------
1 | .form-group > label {
2 | font-weight: 500;
3 | }
--------------------------------------------------------------------------------
/labs/begin/src/app/user-settings/user-settings.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy } from '@angular/core';
2 | import { Theme } from '../shared/enums';
3 | import { UserSettingsService } from '../core/user-settings.service';
4 | import { UserSettings } from '../shared/interfaces';
5 | import { SubSink } from 'subsink';
6 |
7 | @Component({
8 | selector: 'app-user-settings',
9 | templateUrl: './user-settings.component.html',
10 | styleUrls: ['./user-settings.component.scss']
11 | })
12 | export class UserSettingsComponent implements OnInit, OnDestroy {
13 |
14 | themes: any[];
15 | selectedTheme: Theme;
16 | userSettings: UserSettings = { id: 1, preferredName: null, email: null, theme: null };
17 | subsink = new SubSink();
18 |
19 | constructor(private userSettingsService: UserSettingsService) { }
20 |
21 | ngOnInit() {
22 | this.themes = [{label: 'Light', value: 0}, {label: 'Dark', value: 1}];
23 | this.subsink.sink = this.userSettingsService.getUserSettings().subscribe(settings => {
24 | this.userSettings = settings;
25 | if (settings) {
26 | this.selectedTheme = settings.theme;
27 | }
28 | });
29 | }
30 |
31 | updateUserSettings() {
32 | this.userSettingsService.updateUserSettings(this.userSettings)
33 | .subscribe(userSettings => this.userSettings = userSettings);
34 | }
35 |
36 | ngOnDestroy() {
37 | this.subsink.unsubscribe();
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/labs/begin/src/assets/styles.css:
--------------------------------------------------------------------------------
1 | html {
2 | overflow-y: scroll;
3 | overflow-x: hidden;
4 | }
5 |
6 | main {
7 | position: relative;
8 | padding-top: 70px;
9 | }
10 |
11 | /* Ensure display:flex and others don't override a [hidden] */
12 | [hidden] { display: none !important; }
13 |
14 | footer {
15 | margin-top: 15px;
16 | }
17 |
18 | th {
19 | cursor: pointer;
20 | }
21 |
22 | td {
23 | width:33%;
24 | }
25 |
26 | .app-title {
27 | line-height:50px;
28 | font-size:20px;
29 | color: white;
30 | }
31 |
32 | .light-theme body {
33 | background-color: #fff;
34 | color: #000;
35 | }
36 |
37 | .light-theme thead {
38 | background-color: #efefef;
39 | }
40 |
41 | .dark-theme body {
42 | background-color: #3f3f3f;
43 | color: #fff;
44 | }
45 |
46 | .dark-theme thead {
47 | background-color: #000;
48 | }
49 |
50 |
51 | .white {
52 | color: white;
53 | }
--------------------------------------------------------------------------------
/labs/begin/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | apiUrlBase: '/api'
4 | };
5 |
--------------------------------------------------------------------------------
/labs/begin/src/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 | apiUrlBase: '/api'
9 | };
10 |
--------------------------------------------------------------------------------
/labs/begin/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/labs/begin/src/favicon.ico
--------------------------------------------------------------------------------
/labs/begin/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Observable Store Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Loading...
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/labs/begin/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { environment } from './environments/environment';
5 | import { AppDevModule } from './app/app-dev.module';
6 | import { ObservableStore } from '@codewithdan/observable-store';
7 | import { ReduxDevToolsExtension } from '@codewithdan/observable-store-extensions';
8 |
9 | if (environment.production) {
10 | enableProdMode();
11 | }
12 |
13 | // Set ObservableStore globalSettings here since
14 | // it'll be called before the rest of the app loads
15 |
16 |
17 |
18 | // Optional: Initialize store state
19 |
20 |
21 | // Add Redux DevTools extensions support
22 |
23 |
24 | // platformBrowserDynamic().bootstrapModule(AppModule)
25 | // Bootstrap dev module that uses HttpClientInMemoryWebApiModule
26 | platformBrowserDynamic()
27 | .bootstrapModule(AppDevModule)
28 | .catch(err => console.log(err));
29 |
--------------------------------------------------------------------------------
/labs/begin/src/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/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting()
16 | );
17 | // Then we find all the tests.
18 | const context = require.context('./', true, /\.spec\.ts$/);
19 | // And load the modules.
20 | context.keys().map(context);
21 |
--------------------------------------------------------------------------------
/labs/begin/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "types": []
6 | },
7 | "exclude": [
8 | "test.ts",
9 | "**/*.spec.ts"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/labs/begin/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "baseUrl": "./",
6 | "types": ["jasmine", "node"]
7 | },
8 | "files": ["test.ts", "polyfills.ts"],
9 | "include": ["**/*.spec.ts", "**/*.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/labs/begin/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/labs/begin/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "downlevelIteration": true,
6 | "importHelpers": true,
7 | "outDir": "./dist/out-tsc",
8 | "sourceMap": true,
9 | "declaration": false,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "emitDecoratorMetadata": true,
13 | "experimentalDecorators": true,
14 | "target": "es2015",
15 | "typeRoots": [
16 | "node_modules/@types"
17 | ],
18 | "lib": [
19 | "es2017",
20 | "dom"
21 | ],
22 | "paths": {
23 | "rxjs": [
24 | "node_modules/rxjs"
25 | ],
26 | "rxjs/*": [
27 | "node_modules/rxjs/*"
28 | ],
29 | // "@codewithdan/observable-store": [
30 | // "../../modules/observable-store"
31 | // ],
32 | // "@codewithdan/observable-store-extensions": [
33 | // "../../modules/observable-store-extensions"
34 | // ]
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/labs/end/readme.md:
--------------------------------------------------------------------------------
1 | ## Lab Solution
2 |
3 | The lab solution can be found in the `observable-store/samples/angular-store-edits` directory.
--------------------------------------------------------------------------------
/modules/observable-store-extensions/README.md:
--------------------------------------------------------------------------------
1 | ## Observable Store Extensions
2 |
3 | [Observable Store](https://github.com/DanWahlin/Observable-Store) is a front-end state management library that provides a simple yet powerful way to manage state in front-end applications.
4 |
5 | This package can be used to integrate Observable Store with the [Redux DevTools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd). View more details about using the extensions [here](https://github.com/DanWahlin/Observable-Store#extensions).
6 |
7 | ### Changes
8 |
9 | #### 2.2.6 - January 25, 2020
10 |
11 | Change how detection for presence of redux devtools is done. Don't send state back to devtools while using time travel or jumping to a specific action (debugging scenarios).
12 |
13 | #### 2.2.7 - May 11, 2020
14 |
15 | Added workaround for changed APIs for accessing providers/services in Angular 9+. User must now pass the Router and ngZone symbols when
16 | enabling the Redux Devtools. See project repository repo for more details.
17 |
18 | ```typescript
19 | import { environment } from './environments/environment';
20 | import { NgZone } from '@angular/core';
21 | import { Router } from '@angular/router';
22 |
23 | if (!environment.production) {
24 | ObservableStore.addExtension(new ReduxDevToolsExtension());
25 | }
26 | ```
27 |
28 | #### 2.2.8 - May 20, 2020
29 |
30 | Added support for Angular Ivy with the Redux DevTools. Breaking changes in Ivy prevented this functionality initially, but thanks to help from
31 | Andrew Scott and Colum Ferry it's now working with the following code:
32 |
33 | ```typescript
34 | import { environment } from './environments/environment';
35 | import { NgZone } from '@angular/core';
36 | import { Router } from '@angular/router';
37 |
38 | if (!environment.production) {
39 | ObservableStore.addExtension(new ReduxDevToolsExtension({ router: Router, ngZone: NgZone }));
40 | }
41 | ```
--------------------------------------------------------------------------------
/modules/observable-store-extensions/angular/angular-devtools-extension.ts:
--------------------------------------------------------------------------------
1 | import { ReduxDevtoolsExtensionConfig } from "interfaces";
2 |
3 | export class AngularDevToolsExtension {
4 | private window = (window as any);
5 | private router: any;
6 | private ngZone: any;
7 |
8 | constructor(private config?: ReduxDevtoolsExtensionConfig) {
9 |
10 | // Angular with NO Ivy
11 | if (this.window.ng && this.window.ng.probe && this.window.getAllAngularRootElements) {
12 | const rootElements = this.window.ng.probe(this.window.getAllAngularRootElements()[0]);
13 | const providers = rootElements.injector.view.root.ngModule._providers;
14 | this.router = providers.find(p => p && p.constructor && p.constructor.name === 'Router');
15 | try {
16 | this.ngZone = rootElements.injector.get(this.window.ng.coreTokens.NgZone);
17 | }
18 | catch (e) {
19 | console.log(e);
20 | }
21 | return;
22 | }
23 |
24 | // Angular with Ivy
25 | if (this.window.ng && this.window.ng.getInjector && this.window.getAllAngularRootElements &&
26 | this.config && this.config.router && this.config.ngZone) {
27 | try {
28 | const injector = this.window.ng.getInjector(this.window.getAllAngularRootElements()[0]);
29 | this.router = injector.get(this.config.router);
30 | this.ngZone = injector.get(this.config.ngZone);
31 | }
32 | catch (e) {
33 | console.log(e);
34 | }
35 | return;
36 | }
37 | }
38 |
39 | navigate(path: string) {
40 | if (this.ngZone && this.router) {
41 | this.runInZone(() => {
42 | this.router.navigateByUrl(path);
43 | });
44 | }
45 | }
46 |
47 | runInZone(action: any) {
48 | if (this.ngZone) {
49 | this.ngZone.run(() => {
50 | action();
51 | });
52 | }
53 | }
54 |
55 | }
--------------------------------------------------------------------------------
/modules/observable-store-extensions/index.ts:
--------------------------------------------------------------------------------
1 | export { ReduxDevToolsExtension } from './redux-devtools.extension';
--------------------------------------------------------------------------------
/modules/observable-store-extensions/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { Observable, Subscription } from 'rxjs';
2 |
3 | export interface ReduxDevtoolsExtensionConnection {
4 | subscribe(listener: (change: any) => void): void;
5 | unsubscribe(): void;
6 | send(action: any, state: any): void;
7 | init(state?: any): void;
8 | error(anyErr: any): void;
9 | }
10 |
11 | export interface ReduxDevtoolsExtensionConfig {
12 | name?: string;
13 | features?: object | boolean;
14 | latency?: number;
15 | maxAge?: number;
16 | trace?: boolean;
17 | traceLimit?: number;
18 | serialize?: boolean | object;
19 | actionSanitizer?: any;
20 | stateSanitizer?: any;
21 | routerPropertyName?: string;
22 | reactRouterHistory?: any;
23 | customRouteNavigator?: CustomReduxDevtoolsRouteNavigator;
24 | router?: any;
25 | ngZone?: any;
26 | }
27 |
28 | export interface ObservableStoreExtension {
29 | /**
30 | * Function used to initialize the extension.
31 | */
32 | init(): void;
33 | }
34 |
35 | export interface CustomReduxDevtoolsRouteNavigator {
36 | navigate(path: string): void;
37 | }
--------------------------------------------------------------------------------
/modules/observable-store-extensions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@codewithdan/observable-store-extensions",
3 | "version": "2.2.9",
4 | "author": "Dan Wahlin",
5 | "description": "Observable Store redux devtools extension.",
6 | "license": "MIT",
7 | "main": "./dist/index.js",
8 | "typings": "./dist/index.d.ts",
9 | "repository": {
10 | "url": "https://github.com/danwahlin/observable-store"
11 | },
12 | "keywords": [
13 | "store",
14 | "state management",
15 | "Angular store",
16 | "React store",
17 | "Vue.js store",
18 | "state store",
19 | "JavaScript store",
20 | "Front-end store",
21 | "redux devtools"
22 | ],
23 | "scripts": {
24 | "tsc": "tsc",
25 | "build": "rm -rf ./dist && tsc --build ./tsconfig.json",
26 | "build:w": "tsc --build ./tsconfig.json --watch"
27 | },
28 | "dependencies": {},
29 | "peerDependencies": {
30 | "rxjs": ">=6.4.0 <8",
31 | "@codewithdan/observable-store": ">=2.2.12"
32 | },
33 | "devDependencies": {
34 | "@codewithdan/observable-store": "latest",
35 | "@types/node": "^18.11.9",
36 | "typescript": "4.9.3",
37 | "rxjs": "^7.5.7"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/modules/observable-store-extensions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "baseUrl": "./",
7 | "outDir": "./dist",
8 | "declaration": true,
9 | "sourceMap": true,
10 | "noImplicitUseStrict": true,
11 | "experimentalDecorators": true,
12 | "lib": [
13 | "dom",
14 | "es2015",
15 | "es2017"
16 | ],
17 | "paths": {
18 | // "@codewithdan/observable-store": [
19 | // "../observable-store"
20 | // ]
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/modules/observable-store/index.ts:
--------------------------------------------------------------------------------
1 | export { ObservableStore } from './observable-store';
2 | export { ObservableStoreSettings, ObservableStoreGlobalSettings,
3 | StateWithPropertyChanges, StateHistory, StateSliceSelector, ObservableStoreExtension } from './interfaces';
--------------------------------------------------------------------------------
/modules/observable-store/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { Subscription, Observable } from "rxjs";
2 |
3 | export interface StateSliceSelector {
4 | /**
5 | * Function to select the slice of the store being managed by this particular service.
6 | * If specified then the specific state slice is returned.
7 | * If not specified then the total state is returned (defaults to `null`).
8 | */
9 | stateSliceSelector?: (state: any) => any;
10 | }
11 |
12 | export interface BaseStoreSettings {
13 | /**
14 | * Determines if the store's state will be tracked or not (defaults to `false`).
15 | * Pass it when initializing the Observable Store.
16 | * When `true`, you can access the store's state history by calling the `stateHistory` property.
17 | */
18 | trackStateHistory?: boolean;
19 |
20 | /**
21 | * Log any store state changes to the browser console (defaults to `false`).
22 | */
23 | logStateChanges?: boolean;
24 |
25 | /**
26 | * DEPRECATED. Since this is deprecated, use `stateWithPropertyChanges` or `globalStateWithPropertyChanges` instead.
27 | */
28 | includeStateChangesOnSubscribe?: boolean;
29 | }
30 |
31 | export interface ObservableStoreSettings extends BaseStoreSettings, StateSliceSelector { }
32 |
33 | export interface ObservableStoreGlobalSettings extends BaseStoreSettings {
34 | /**
35 | * Not currently used. Reserved for future use.
36 | */
37 | isProduction?: boolean;
38 | }
39 |
40 | export interface StateHistory{
41 | action: string;
42 | beginState: T,
43 | endState: T
44 | }
45 |
46 | export interface StateWithPropertyChanges {
47 | state: T,
48 | stateChanges: Partial
49 | }
50 |
51 | export interface ObservableStoreExtension {
52 | /**
53 | * Function used to initialize the extension.
54 | */
55 | init(): void;
56 | }
--------------------------------------------------------------------------------
/modules/observable-store/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@codewithdan/observable-store",
3 | "version": "2.2.15",
4 | "author": "Dan Wahlin",
5 | "description": "Observable Store provides a simple way to store and retrieve state in Angular, React, Vue.js and other front-end applications.",
6 | "license": "MIT",
7 | "main": "./dist/index.js",
8 | "typings": "./dist/index.d.ts",
9 | "repository": {
10 | "url": "https://github.com/danwahlin/observable-store"
11 | },
12 | "keywords": [
13 | "store",
14 | "state management",
15 | "Angular store",
16 | "React store",
17 | "Vue.js store",
18 | "state store",
19 | "JavaScript store",
20 | "Front-end store"
21 | ],
22 | "scripts": {
23 | "tsc": "tsc",
24 | "build": "cp ../../README.md ./ && rm -rf ./dist && tsc --build ./tsconfig.json",
25 | "build:w": "tsc --build ./tsconfig.json --watch",
26 | "test": "jasmine-ts --project ./spec/tsconfig.json --config=./spec/jasmine.json",
27 | "test:w": "nodemon --ext ts --exec 'jasmine-ts --project ./spec/tsconfig.json --config=./spec/jasmine.json'"
28 | },
29 | "peerDependencies": {
30 | "rxjs": ">=6.4.0 <8"
31 | },
32 | "devDependencies": {
33 | "@types/jasmine": "^3.8.2",
34 | "@types/node": "^18.11.9",
35 | "jasmine": "^3.9.0",
36 | "jasmine-spec-reporter": "^7.0.0",
37 | "jasmine-ts": "^0.4.0",
38 | "ts-node": "^10.2.1",
39 | "typescript": "4.9.3",
40 | "rxjs": "^7.5.7"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/modules/observable-store/solvedIssues.md:
--------------------------------------------------------------------------------
1 | ## To build the project(s)
2 |
3 | To build Observable Store:
4 |
5 | `npm run build` or `npm run build:w`
6 |
7 | To build Observable Store extensions cd into the folder and run:
8 |
9 | `npm run build` or `npm run build:w`
10 |
11 | ## To publish to npm
12 |
13 | 1. Update the version in `package.json`.
14 | 1. Run `npm run build` to create the dist folder (ensure tsconfig.json is set to copy the build there)
15 | 1. Run `npm publish --access public` and enter the 2FA code for npm in 1Password
16 |
17 | ## To run the Observable Store npm module locally without publishing to npm
18 |
19 | 1. Run `npm link` at the root of the `observable-store` project
20 | 1. Go into the target sample project and run `npm link @codewithdan/observable-store`
21 |
22 | When done you can unlink by running `npm unlink @codewithdan/observable-store` in the target sample project
23 |
24 | ## Access Angular Zone Outside of Angular
25 |
26 | https://stackoverflow.com/questions/47619350/access-angular-ngzone-instance-from-window-object
27 | https://medium.com/nextfaze/devmod-probing-your-angular-application-for-fun-and-debugging-d7e07c688247
28 |
29 | Window object will have `ng` and `getAllAngularRootElements`:
30 |
31 | ng.probe(getAllAngularRootElements()[0]).injector.get(ng.coreTokens.NgZone)
32 |
33 | ## To run samples with local Observable Store Code for testing (old)
34 |
35 | 1. Run `npm install` at root of the project
36 | 1. Remove `@codewithdan/observable-store` from `package.json` file for sample.
37 | 1. Change the import in the service(s) from:
38 |
39 | `import { ObservableStore } from '@codewithdan/observable-store';`
40 |
41 | To:
42 |
43 | `import { ObservableStore } from '../../../../../src/observable-store';`
44 |
45 | 1. Delete `node_modules` and run `npm install` again for the sample project.
46 | 1. Run `ng server -o`
47 |
48 | ## Copy README.md from root into modules/observable-store folder
49 |
50 | `npm run build` now automatically copies the file into the folder for deployment. Any updates should be made to the root README.md.
--------------------------------------------------------------------------------
/modules/observable-store/spec/helpers/reporter.js:
--------------------------------------------------------------------------------
1 | const { SpecReporter } = require('jasmine-spec-reporter')
2 |
3 | jasmine.getEnv().clearReporters()
4 | jasmine.getEnv().addReporter(new SpecReporter({
5 | spec: {
6 | displayStacktrace: 'pretty',
7 | }
8 | }
9 | ))
--------------------------------------------------------------------------------
/modules/observable-store/spec/jasmine.json:
--------------------------------------------------------------------------------
1 | {
2 | "spec_dir": "../observable-store",
3 | "spec_files": ["**/*.spec.ts"],
4 | "stopSpecOnExpectationFailure": false,
5 | "random": false,
6 | "helpers": ["spec/helpers/*.js"]
7 | }
8 |
--------------------------------------------------------------------------------
/modules/observable-store/spec/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "esModuleInterop": true,
5 | "target": "esnext",
6 | "moduleResolution": "node",
7 | "sourceMap": true,
8 | "outDir": "dist",
9 | "baseUrl": "../"
10 | },
11 | "include": [
12 | "./**/*"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/modules/observable-store/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "baseUrl": "./",
7 | "outDir": "./dist",
8 | "declaration": true,
9 | "sourceMap": true,
10 | "noImplicitUseStrict": true,
11 | "experimentalDecorators": true,
12 | "lib": [
13 | "dom",
14 | "es2015",
15 | "es2017"
16 | ]
17 | }
18 | }
--------------------------------------------------------------------------------
/modules/observable-store/utilities/cloner.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { ClonerService } from './cloner.service';
2 |
3 | class FakeClass {
4 | constructor(public prop1: string, public prop2: string) {}
5 | }
6 |
7 | describe('ClonerService', () => {
8 | it('should clone a class', () => {
9 | const fake = new FakeClass('foo', 'bar');
10 |
11 | const cloneService = new ClonerService();
12 | const clonedFake = cloneService.deepClone(fake);
13 |
14 | expect(clonedFake.prop1).toEqual('foo');
15 | expect(clonedFake.prop2).toEqual('bar');
16 | });
17 |
18 | it('should clone a Map', () => {
19 | let map = new Map();
20 | map.set('key', 22);
21 | const cloneService = new ClonerService();
22 | const clonedMap = cloneService.deepClone(map);
23 | expect(map).toBe(map);
24 | expect(clonedMap).not.toBe(map);
25 | expect (clonedMap.size).toEqual(map.size);
26 | });
27 |
28 | it('should clone a Set', () => {
29 | let set = new Set();
30 | set.add('value1');
31 | set.add('value2');
32 | const cloneService = new ClonerService();
33 | const clonedSet = cloneService.deepClone(set);
34 | expect(set).toBe(set);
35 | expect(clonedSet).not.toBe(set);
36 | expect (clonedSet.size).toEqual(clonedSet.size);
37 | });
38 |
39 | it('should not be the original class that was cloned', () => {
40 | const fake = new FakeClass('foo', 'bar');
41 |
42 | const cloneService = new ClonerService();
43 | const clonedFake = cloneService.deepClone(fake);
44 |
45 | expect(fake).toBe(fake);
46 | expect(clonedFake).not.toBe(fake);
47 | });
48 |
49 | interface DeepWithFakeClass {
50 | prop1: string;
51 | fake: FakeClass;
52 | }
53 |
54 | it('should deep clone an interface', () => {
55 | const deepWithFakeClass: DeepWithFakeClass = { prop1: 'test', fake: new FakeClass('foo', 'bar') };
56 |
57 | const clonedObject = new ClonerService().deepClone(deepWithFakeClass);
58 |
59 | expect(clonedObject.prop1).toEqual('test');
60 | expect(clonedObject.fake.prop1).toEqual('foo');
61 | expect(clonedObject.fake.prop2).toEqual('bar');
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/modules/readme.md:
--------------------------------------------------------------------------------
1 | ## Instructions
2 |
3 | 1. Open `modules/observable-store-extensions` and run `npm run build` or `npm run build:w`.
4 |
5 | 1. Open `modules/observable-store` and run `npm run build` or `npm run build:w`.
6 |
7 | 1. Run `npm run test` in `modules/observable-store` and ensure all tests pass.
8 |
9 | 1. Open one of the samples and uncomment the lines in `tsconfig.json` for `@codewithdan` (makes local testing easier).
10 |
11 | 1. Open a sample project in `samples` to test it out.
12 |
13 | 1. Change versions in `package.json` files.
14 |
15 | 1. Run `npm publish --access public` in the root of the `modules/observable-store` and `modules/observable-store-extensions` folders (enter the 2FA code for npm).
16 |
17 |
18 | ## Publishing Extensions Project
19 |
20 | 1. Open `modules/observable-store` and run `npm link` to setup linking to `@codewithdan/observable-store`. Alternatively, you can uncomment the lines in `modules/observable-store-extensions/tsconfig.json` to work with it locally.
21 |
22 | 1. Open `modules/observable-store-extensions` and run `npm link @codewithdan/observable-store`
23 |
24 | 1. Open `modules/observable-store-extensions/tsconfig.json` and comment out the `paths` property
25 |
26 | 1. Open `modules/observable-store-extensions` and run `npm run build` or `npm run build:w`.
27 |
28 | 1. Copy the root `readme.md` file into `modules/observable-store` (need to update the build to automate this).
29 |
30 | 1. Run `npm publish --access public` and enter the 2FA code for npm
31 |
32 | 1. When done you can run `npm unlink @codewithdan/observable-store` in `modules/observable-store-extensions`
33 |
34 | 1. Then run `npm unlink` in `modules/observable-store`
35 |
36 | 1. IMPORTANT: Make sure that samples have `@codewithdan` sections in `tsconfig.json` commented out before pushing to github.
37 |
38 |
39 | ## import { Observable } from 'rxjs' Error Building Extensions
40 |
41 | Delete `dist/observable-store` and rebuild the extensions project. If using linking (see above) there shouldn't be a problem.
--------------------------------------------------------------------------------
/samples/angular-simple-store/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://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 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "pwa-chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/README.md:
--------------------------------------------------------------------------------
1 | ## Observable Store for Angular
2 |
3 | Open the `src/app/core/stores` folder for examples.
4 |
5 | ## Running the Project
6 |
7 | 1. Install the Angular CLI
8 |
9 | `npm install -g @angular/cli`
10 |
11 | 1. Run `npm install` within this folder.
12 |
13 | 1. Run `ng serve -o`
--------------------------------------------------------------------------------
/samples/angular-simple-store/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-simple-store",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve -o",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^15.0.0",
14 | "@angular/common": "^15.0.0",
15 | "@angular/compiler": "^15.0.0",
16 | "@angular/core": "^15.0.0",
17 | "@angular/forms": "^15.0.0",
18 | "@angular/platform-browser": "^15.0.0",
19 | "@angular/platform-browser-dynamic": "^15.0.0",
20 | "@angular/router": "^15.0.0",
21 | "@codewithdan/observable-store": "^2.2.15",
22 | "@codewithdan/observable-store-extensions": "^2.2.9",
23 | "rxjs": "~7.5.0",
24 | "tslib": "^2.3.0",
25 | "zone.js": "~0.12.0"
26 | },
27 | "devDependencies": {
28 | "@angular-devkit/build-angular": "^15.0.0",
29 | "@angular/cli": "~15.0.0",
30 | "@angular/compiler-cli": "^15.0.0",
31 | "@types/jasmine": "~4.3.0",
32 | "jasmine-core": "~4.5.0",
33 | "karma": "~6.4.0",
34 | "karma-chrome-launcher": "~3.1.0",
35 | "karma-coverage": "~2.2.0",
36 | "karma-jasmine": "~5.1.0",
37 | "karma-jasmine-html-reporter": "~2.0.0",
38 | "typescript": "~4.8.2"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/angular-simple-store/src/app/app.component.css
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/app/app.component.html:
--------------------------------------------------------------------------------
1 | Customers
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{ customers | json }}
12 |
13 |
14 |
15 |
16 |
State History
17 |
18 | {{ stateHistory | json }}
19 |
20 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { RouterModule } from '@angular/router';
4 |
5 | import { AppComponent } from './app.component';
6 |
7 | @NgModule({
8 | declarations: [
9 | AppComponent
10 | ],
11 | imports: [
12 | BrowserModule,
13 | RouterModule.forRoot([])
14 | ],
15 | providers: [],
16 | bootstrap: [AppComponent]
17 | })
18 | export class AppModule { }
19 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/app/core/store/customer.ts:
--------------------------------------------------------------------------------
1 | export class Customer {
2 | id: number = 0;
3 | name = '';
4 | address: Address = {
5 | street:'',
6 | city: '',
7 | state: '',
8 | zip: '',
9 | };
10 | }
11 |
12 | export class Address {
13 | street = '';
14 | city = '';
15 | state = '';
16 | zip = '';
17 | }
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/app/core/utilities/property-resolver.ts:
--------------------------------------------------------------------------------
1 | export class PropertyResolver {
2 | static resolve(path: string, obj: any) {
3 | return path.split('.').reduce((prev, curr) => {
4 | return (prev ? prev[curr] : undefined);
5 | }, obj || self);
6 | }
7 | }
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/app/core/utilities/sorter.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | import { PropertyResolver } from './property-resolver';
4 |
5 | @Injectable({
6 | providedIn: 'root'
7 | })
8 | export class SorterService {
9 |
10 | private property: string = '';
11 | private direction = 1;
12 |
13 | sort(collection: any[], prop: any) {
14 | this.property = prop;
15 | this.direction = (this.property === prop) ? this.direction * -1 : 1;
16 |
17 | return collection.sort((a: any, b: any) => {
18 | let aVal: any;
19 | let bVal: any;
20 |
21 | // Handle resolving complex properties such as 'state.name' for prop value
22 | if (prop && prop.indexOf('.') > -1) {
23 | aVal = PropertyResolver.resolve(prop, a);
24 | bVal = PropertyResolver.resolve(prop, b);
25 | }
26 | else {
27 | aVal = a[prop];
28 | bVal = b[prop];
29 | }
30 |
31 | // Fix issues that spaces before/after string value can cause such as ' San Francisco'
32 | if (this.isString(aVal)) {
33 | aVal = aVal.trim().toUpperCase();
34 | }
35 |
36 | if (this.isString(bVal)) {
37 | bVal = bVal.trim().toUpperCase();
38 | }
39 |
40 | if (aVal === bVal) {
41 | return 0;
42 | }
43 | else if (aVal > bVal) {
44 | return this.direction * -1;
45 | }
46 | else {
47 | return this.direction * 1;
48 | }
49 | });
50 | }
51 |
52 | private isString(val: any): boolean {
53 | return (val && (typeof val === 'string' || val instanceof String));
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/app/shared/auto-unsubscribe.decorator.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/NetanelBasal/ngx-auto-unsubscribe/blob/master/src/auto-unsubscribe.ts
2 | const isFunction = (fn) => typeof fn === 'function';
3 |
4 | const doUnsubscribe = (subscription) => {
5 | subscription && isFunction(subscription.unsubscribe) && subscription.unsubscribe();
6 | }
7 |
8 | const doUnsubscribeIfArray = (subscriptionsArray) => {
9 | Array.isArray(subscriptionsArray) && subscriptionsArray.forEach(doUnsubscribe);
10 | }
11 |
12 | export function AutoUnsubscribe({ blackList = [], includeArrays = false, arrayName = '', event = 'ngOnDestroy'} = {}) {
13 |
14 | return function (constructor: Function) {
15 | const original = constructor.prototype[event];
16 |
17 | if (!isFunction(original) && !disableAutoUnsubscribeAot()) {
18 | console.warn(`${constructor.name} is using @AutoUnsubscribe but does not implement OnDestroy`);
19 | }
20 |
21 | constructor.prototype[event] = function () {
22 | if (arrayName) {
23 | return doUnsubscribeIfArray(this[arrayName]);
24 | }
25 |
26 | for (let propName in this) {
27 | if (blackList.includes(propName)) continue;
28 |
29 | const property = this[propName];
30 | doUnsubscribe(property);
31 | doUnsubscribeIfArray(property);
32 | }
33 |
34 | isFunction(original) && original.apply(this, arguments);
35 | };
36 | }
37 |
38 |
39 | function disableAutoUnsubscribeAot() {
40 | return window && window['disableAutoUnsubscribeAot'] || window['disableAuthUnsubscribeAot'];
41 | }
42 | }
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/app/shared/shared.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 |
4 | @NgModule({
5 | imports: [
6 | CommonModule
7 | ],
8 | declarations: []
9 | })
10 | export class SharedModule { }
11 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/angular-simple-store/src/assets/.gitkeep
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * In development mode, for easier debugging, you can ignore zone related error
11 | * stack frames such as `zone.run`/`zoneDelegate.invokeTask` by importing the
12 | * below file. Don't forget to comment it out in production mode
13 | * because it will have a performance impact when errors are thrown
14 | */
15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
16 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/angular-simple-store/src/favicon.ico
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Observable Store
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowser } from '@angular/platform-browser';
3 | import { NgZone } from '@angular/core';
4 | import { Router } from '@angular/router';
5 |
6 | import { ObservableStore } from '@codewithdan/observable-store';
7 | import { ReduxDevToolsExtension } from '@codewithdan/observable-store-extensions';
8 |
9 | import { AppModule } from './app/app.module';
10 | import { environment } from './environments/environment';
11 |
12 | if (environment.production) {
13 | enableProdMode();
14 | }
15 |
16 | // Set Observable Store globalSettings here since
17 | // it'll be called before the rest of the app loads
18 | ObservableStore.globalSettings = { isProduction: environment.production };
19 |
20 | // Enable Redux DevTools support. Ensure that RouterModule.forRoot() is called in app.module.
21 | if (!environment.production) {
22 | ObservableStore.addExtension(new ReduxDevToolsExtension({ router: Router, ngZone: NgZone }));
23 | }
24 |
25 | platformBrowser().bootstrapModule(AppModule)
26 | .catch(err => console.log(err));
27 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
--------------------------------------------------------------------------------
/samples/angular-simple-store/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ]
26 | },
27 | "angularCompilerOptions": {
28 | "enableI18nLegacyMessageIdFormat": false,
29 | "strictInjectionParameters": true,
30 | "strictInputAccessModifiers": true,
31 | "strictTemplates": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/samples/angular-simple-store/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://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 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "pwa-chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/README.md:
--------------------------------------------------------------------------------
1 | ## Observable Store for Angular - State Changed Example
2 |
3 | Open the `src/app/core/customer.service.ts` folder for the example.
4 |
5 | ## Running the Project
6 |
7 | 1. Install the Angular CLI
8 |
9 | `npm install -g @angular/cli`
10 |
11 | 1. Run `npm install` within this folder.
12 |
13 | 1. Run `ng serve -o`
--------------------------------------------------------------------------------
/samples/angular-stateChanged/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-state-changed",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve -o",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^15.0.0",
14 | "@angular/common": "^15.0.0",
15 | "@angular/compiler": "^15.0.0",
16 | "@angular/core": "^15.0.0",
17 | "@angular/forms": "^15.0.0",
18 | "@angular/platform-browser": "^15.0.0",
19 | "@angular/platform-browser-dynamic": "^15.0.0",
20 | "@angular/router": "^15.0.0",
21 | "@codewithdan/observable-store": "^2.2.15",
22 | "@codewithdan/observable-store-extensions": "^2.2.9",
23 | "rxjs": "~7.5.0",
24 | "tslib": "^2.3.0",
25 | "zone.js": "~0.12.0"
26 | },
27 | "devDependencies": {
28 | "@angular-devkit/build-angular": "^15.0.0",
29 | "@angular/cli": "~15.0.0",
30 | "@angular/compiler-cli": "^15.0.0",
31 | "@types/jasmine": "~4.3.0",
32 | "jasmine-core": "~4.5.0",
33 | "karma": "~6.4.0",
34 | "karma-chrome-launcher": "~3.1.0",
35 | "karma-coverage": "~2.2.0",
36 | "karma-jasmine": "~5.1.0",
37 | "karma-jasmine-html-reporter": "~2.0.0",
38 | "typescript": "~4.8.2"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 |
5 | const routes: Routes = [];
6 |
7 | @NgModule({
8 | imports: [RouterModule.forRoot(routes)],
9 | exports: [RouterModule]
10 | })
11 | export class AppRoutingModule { }
12 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/angular-stateChanged/src/app/app.component.css
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/app/app.component.html:
--------------------------------------------------------------------------------
1 | Customers
2 |
3 |
4 | -
5 | {{ customer.firstName }} {{ customer.lastName }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | State with Property Changes
15 | This shows the store state as well as which properties changed.
16 |
17 | {{ (storeStateWithPropertyChanges$ | async) | json }}
18 |
19 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(async(() => {
7 | TestBed.configureTestingModule({
8 | imports: [
9 | RouterTestingModule
10 | ],
11 | declarations: [
12 | AppComponent
13 | ],
14 | }).compileComponents();
15 | }));
16 |
17 | it('should create the app', () => {
18 | const fixture = TestBed.createComponent(AppComponent);
19 | const app = fixture.debugElement.componentInstance;
20 | expect(app).toBeTruthy();
21 | });
22 |
23 | it(`should have as title 'my-angular-app'`, () => {
24 | const fixture = TestBed.createComponent(AppComponent);
25 | const app = fixture.debugElement.componentInstance;
26 | expect(app.title).toEqual('my-angular-app');
27 | });
28 |
29 | it('should render title', () => {
30 | const fixture = TestBed.createComponent(AppComponent);
31 | fixture.detectChanges();
32 | const compiled = fixture.debugElement.nativeElement;
33 | expect(compiled.querySelector('.content span').textContent).toContain('my-angular-app app is running!');
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable} from 'rxjs';
3 | import { Customer, CustomersService, StoreState } from './core/customers.service';
4 | import { StateWithPropertyChanges } from '@codewithdan/observable-store';
5 |
6 | @Component({
7 | selector: 'app-root',
8 | templateUrl: './app.component.html',
9 | styleUrls: ['./app.component.css']
10 | })
11 | export class AppComponent implements OnInit {
12 |
13 | storeState$: Observable = new Observable();
14 | storeStateWithPropertyChanges$: Observable> = new Observable>();
15 |
16 | constructor(private customersService: CustomersService) {}
17 |
18 | ngOnInit() {
19 | this.storeState$ = this.customersService.stateChanged;
20 | this.storeStateWithPropertyChanges$ = this.customersService.stateWithPropertyChanges;
21 | }
22 |
23 | addCustomer() {
24 | const customer: Customer = {
25 | id: Date.now(),
26 | firstName: 'John',
27 | lastName: 'Doe'
28 | };
29 | this.customersService.addCustomer(customer);
30 | }
31 |
32 | updateCustomer(customer: Customer) {
33 | this.customersService.updateCustomer(customer);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 |
4 | import { AppRoutingModule } from './app-routing.module';
5 | import { AppComponent } from './app.component';
6 | import { ChildComponent } from './child/child.component';
7 |
8 | @NgModule({
9 | declarations: [
10 | AppComponent,
11 | ChildComponent
12 | ],
13 | imports: [
14 | BrowserModule,
15 | AppRoutingModule
16 | ],
17 | providers: [],
18 | bootstrap: [AppComponent]
19 | })
20 | export class AppModule { }
21 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/app/child/child.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/angular-stateChanged/src/app/child/child.component.css
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/app/child/child.component.html:
--------------------------------------------------------------------------------
1 | Customers (in Child Component)
2 |
3 | -
4 | {{ customer.firstName }} {{ customer.lastName }}
5 |
6 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/app/child/child.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ChildComponent } from './child.component';
4 |
5 | describe('ChildComponent', () => {
6 | let component: ChildComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ ChildComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ChildComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/app/child/child.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { CustomersService, StoreState } from '../core/customers.service';
4 |
5 | @Component({
6 | selector: 'app-child',
7 | templateUrl: './child.component.html',
8 | styleUrls: ['./child.component.css']
9 | })
10 | export class ChildComponent implements OnInit {
11 | storeState$: Observable = new Observable();
12 |
13 | constructor(private customersService: CustomersService) {}
14 |
15 | ngOnInit() {
16 | this.storeState$ = this.customersService.stateChanged;
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/app/core/customers.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { CustomersService } from './customers.service';
4 |
5 | describe('CustomersService', () => {
6 | beforeEach(() => TestBed.configureTestingModule({}));
7 |
8 | it('should be created', () => {
9 | const service: CustomersService = TestBed.get(CustomersService);
10 | expect(service).toBeTruthy();
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/app/core/customers.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ObservableStore } from '@codewithdan/observable-store';
3 | import { of } from 'rxjs';
4 |
5 | export interface StoreState {
6 | customers: Customer[];
7 | selectedCustomer: Customer;
8 | countries: Country[];
9 | }
10 |
11 | export interface Customer {
12 | id: number;
13 | firstName: string;
14 | lastName: string;
15 | }
16 |
17 | export interface Country {
18 | id: number;
19 | name: string;
20 | }
21 |
22 | @Injectable({
23 | providedIn: 'root'
24 | })
25 | export class CustomersService extends ObservableStore {
26 |
27 | constructor() {
28 | super({ logStateChanges: true, trackStateHistory: true });
29 |
30 | const initialState = {
31 | customers: [{
32 | id: 1,
33 | firstName: 'Jane',
34 | lastName: 'Doe'
35 | }],
36 | countries: [
37 | { id: 1, name: 'Arizona' },
38 | { id: 2, name: 'California' }
39 | ]
40 | }
41 | this.setState(initialState, 'INITIALIZE_STORE');
42 | }
43 |
44 | getCustomers() {
45 | return of(this.getState().customers);
46 | }
47 |
48 | addCustomer(customer: Customer) {
49 | let customers = this.getState().customers;
50 | customers.push(customer);
51 | this.setState({ customers }, 'ADD_CUSTOMER');
52 | }
53 |
54 | updateCustomer(customer: Customer) {
55 | let customers = this.getState().customers;
56 | customer.firstName = customer.firstName + ' Updated!!!';
57 | customers = customers.map(c => {
58 | if (c.id === customer.id) {
59 | return customer;
60 | }
61 | return c;
62 | });
63 | this.setState({ customers }, 'UPDATE_CUSTOMER');
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/angular-stateChanged/src/assets/.gitkeep
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/angular-stateChanged/src/favicon.ico
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AngularStateChanged
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 | import { ObservableStore } from '@codewithdan/observable-store';
7 | import { ReduxDevToolsExtension } from '@codewithdan/observable-store-extensions';
8 |
9 | if (environment.production) {
10 | enableProdMode();
11 | }
12 |
13 | // Set ObservableStore globalSettings here since
14 | // it'll be called before the rest of the app loads
15 | ObservableStore.globalSettings = { isProduction: environment.production };
16 | ObservableStore.addExtension(new ReduxDevToolsExtension());
17 |
18 | platformBrowserDynamic().bootstrapModule(AppModule)
19 | .catch(err => console.error(err));
20 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/src/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/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting()
16 | );
17 | // Then we find all the tests.
18 | const context = require.context('./', true, /\.spec\.ts$/);
19 | // And load the modules.
20 | context.keys().map(context);
21 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ]
26 | },
27 | "angularCompilerOptions": {
28 | "enableI18nLegacyMessageIdFormat": false,
29 | "strictInjectionParameters": true,
30 | "strictInputAccessModifiers": true,
31 | "strictTemplates": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/samples/angular-stateChanged/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/.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 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /dist-server
6 | /tmp
7 | /out-tsc
8 |
9 | # dependencies
10 | /node_modules
11 |
12 | # IDEs and editors
13 | /.idea
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 | *.sublime-workspace
20 |
21 | # IDE - VSCode
22 | .vscode/*
23 | !.vscode/settings.json
24 | !.vscode/tasks.json
25 | !.vscode/launch.json
26 | !.vscode/extensions.json
27 |
28 | # misc
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | yarn-error.log
35 | testem.log
36 | /typings
37 |
38 | # e2e
39 | /e2e/*.js
40 | /e2e/*.map
41 |
42 | # System Files
43 | .DS_Store
44 | Thumbs.db
45 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/README.md:
--------------------------------------------------------------------------------
1 | ## Observable Store for Angular
2 |
3 | Open the `src/app/core/stores` folder for examples.
4 |
5 | ## Running the Project
6 |
7 | 1. Install the Angular CLI
8 |
9 | `npm install -g @angular/cli`
10 |
11 | 1. Run `npm install` within this folder.
12 |
13 | 1. Run `ng serve -o`
--------------------------------------------------------------------------------
/samples/angular-store-edits/browserslist:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | > 0.5%
9 | last 2 versions
10 | Firefox ESR
11 | not dead
12 | not IE 9-11 # For IE 9-11 support, remove 'not'.
--------------------------------------------------------------------------------
/samples/angular-store-edits/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('angular-jumpstart App', () => {
4 | let page: AppPage;
5 |
6 | beforeEach(() => {
7 | page = new AppPage();
8 | });
9 |
10 | it('should display welcome message', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('Welcome to cm!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('cm-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": [
9 | "jasmine",
10 | "jasminewd2",
11 | "node"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "observable-store-demo",
3 | "version": "0.0.1",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve -o",
8 | "build": "ng build --prod",
9 | "test": "ng test",
10 | "lint": "ng lint",
11 | "e2e": "ng e2e"
12 | },
13 | "dependencies": {
14 | "@angular/animations": "^14.1.0",
15 | "@angular/common": "^14.1.0",
16 | "@angular/compiler": "^14.1.0",
17 | "@angular/core": "^14.1.0",
18 | "@angular/forms": "^14.1.0",
19 | "@angular/platform-browser": "^14.1.0",
20 | "@angular/platform-browser-dynamic": "^14.1.0",
21 | "@angular/router": "^14.1.0",
22 | "@codewithdan/observable-store": "^2.2.15",
23 | "@codewithdan/observable-store-extensions": "^2.2.9",
24 | "rxjs": "~7.5.0",
25 | "tslib": "^2.3.0",
26 | "zone.js": "~0.11.4",
27 | "angular-in-memory-web-api": "^0.14.0",
28 | "primeicons": "^6.0.1",
29 | "primeng": "^14.2.2",
30 | "subsink": "^1.0.2"
31 | },
32 | "devDependencies": {
33 | "@angular-devkit/build-angular": "^14.1.1",
34 | "@angular/cli": "~14.1.1",
35 | "@angular/compiler-cli": "^14.1.0",
36 | "@types/node": "^18.11.9",
37 | "typescript": "~4.7.2"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/app-dev.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { AppModule } from './app.module';
4 | import { AppComponent } from './app.component';
5 |
6 | import { HttpClientInMemoryWebApiModule, InMemoryDbService, InMemoryBackendConfigArgs} from 'angular-in-memory-web-api';
7 | import { InMemoryDataService } from './core/in-memory-data.service';
8 |
9 | @NgModule({
10 | imports: [
11 | AppModule,
12 | HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, {
13 | dataEncapsulation: false,
14 | delay: 100,
15 | passThruUnknownUrl: true,
16 | post204: false,
17 | put204: false
18 | })
19 | ],
20 | providers: [ { provide: InMemoryDataService, useExisting: InMemoryDbService } ],
21 | bootstrap: [ AppComponent ]
22 | })
23 | export class AppDevModule {}
24 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { UserSettingsComponent } from './user-settings/user-settings.component';
4 |
5 | const routes: Routes = [
6 | { path: '', pathMatch: 'full', redirectTo: 'customers' },
7 | { path: 'customers', loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule) },
8 | { path: 'orders/:id', loadChildren: () => import('./orders/orders.module').then(m => m.OrdersModule) },
9 | { path: 'settings', component: UserSettingsComponent }
10 | ];
11 |
12 | @NgModule({
13 | imports: [RouterModule.forRoot(routes)],
14 | exports: [RouterModule]
15 | })
16 | export class AppRoutingModule {}
17 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | .title {
2 | margin-right: 1em;
3 | }
4 |
5 | .preferred-name {
6 | color: #fff;
7 | place-self: center;
8 | margin-right: 30px;
9 | font-size: 15pt;
10 | }
11 |
12 | .circle {
13 | place-self: center;
14 | height:25px;
15 | width:25px;
16 | border-radius: 50px;
17 | background: red;
18 | color: white;
19 | margin-right: 10px;
20 | font-size: 13pt;
21 | text-align: center;
22 | cursor: pointer;
23 | }
24 |
25 | .settings {
26 | color: #fff;
27 | font-size: 15pt;
28 | margin-left: 15px;
29 | }
30 |
31 | header {
32 | background-color: #007bff;
33 | }
34 |
35 | :host-context(.light-theme) header{
36 | background-color: #007bff;
37 | }
38 |
39 | :host-context(.dark-theme) header{
40 | background-color: #000;
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
2 | import { DOCUMENT } from '@angular/common';
3 |
4 | import { CustomersService } from './customers/customers.service';
5 | import { UserSettingsService } from './core/user-settings.service';
6 | import { Theme } from './shared/enums';
7 | import { UserSettings } from './shared/interfaces';
8 | import { Observable, merge } from 'rxjs';
9 | import { map, tap } from 'rxjs/operators';
10 |
11 | @Component({
12 | selector: 'app-root',
13 | templateUrl: './app.component.html',
14 | styleUrls: ['./app.component.scss']
15 | })
16 | export class AppComponent implements OnInit {
17 | customersLength$: Observable = new Observable();
18 | userSettings$: Observable = new Observable();
19 |
20 | constructor(@Inject(DOCUMENT) private document: HTMLDocument,
21 | private customersService: CustomersService,
22 | private userSettingsService: UserSettingsService) { }
23 |
24 | ngOnInit() {
25 | this.userSettings$ = merge(
26 | this.userSettingsService.getUserSettings(), // Get initial data
27 | this.userSettingsService.userSettingsChanged() // Handle any changes
28 | .pipe(
29 | // tap(userSettings => console.log('userSettingsChanged: ', userSettings)),
30 | map(userSettings => this.updateTheme(userSettings as UserSettings))
31 | )
32 | );
33 |
34 | this.customersLength$ = this.customersService.stateChanged
35 | .pipe(
36 | map(state => {
37 | if (state && state.customers) {
38 | return state.customers.length;
39 | }
40 | return 0;
41 | })
42 | );
43 | }
44 |
45 | updateTheme(userSettings: UserSettings) {
46 | this.document.documentElement.className = (userSettings && userSettings.theme === Theme.Dark) ? 'dark-theme' : 'light-theme';
47 | return userSettings;
48 | }
49 |
50 | }
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
4 | import { HttpClientModule } from '@angular/common/http';
5 | import { FormsModule } from '@angular/forms';
6 |
7 | import { AppRoutingModule } from './app-routing.module';
8 | import { AppComponent } from './app.component';
9 | import { SelectButtonModule } from 'primeng/selectbutton';
10 | import { UserSettingsComponent } from './user-settings/user-settings.component';
11 |
12 |
13 | @NgModule({
14 | imports: [
15 | BrowserModule,
16 | BrowserAnimationsModule,
17 | HttpClientModule,
18 | FormsModule,
19 | AppRoutingModule,
20 | SelectButtonModule
21 | ],
22 | declarations: [AppComponent, UserSettingsComponent],
23 | bootstrap: [AppComponent]
24 | })
25 | export class AppModule {}
26 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/core/model/customer.ts:
--------------------------------------------------------------------------------
1 | export interface Customer {
2 | id: number;
3 | name: string;
4 | city: string;
5 | orderTotal: number;
6 | }
7 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/core/model/index.ts:
--------------------------------------------------------------------------------
1 | export * from './customer';
2 | export * from './order';
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/core/model/order.ts:
--------------------------------------------------------------------------------
1 | export interface Order {
2 | id: number;
3 | customerId: number;
4 | orderItems: OrderItem[];
5 | }
6 |
7 | export interface OrderItem {
8 | id: number;
9 | productName: string;
10 | itemCost: number;
11 | }
12 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/core/sorter.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable({ providedIn: 'root' })
4 | export class SorterService {
5 |
6 | property = '';
7 | direction = 1;
8 |
9 | sort(collection: any[], prop: any) {
10 | this.property = prop;
11 | this.direction = (this.property === prop) ? this.direction * -1 : 1;
12 |
13 | collection.sort((a: any, b: any) => {
14 | let aVal: any;
15 | let bVal: any;
16 |
17 | // Handle resolving complex properties such as 'state.name' for prop value
18 | if (prop && prop.indexOf('.') > -1) {
19 | aVal = this.resolveProperty(prop, a);
20 | bVal = this.resolveProperty(prop, b);
21 | } else {
22 | aVal = a[prop];
23 | bVal = b[prop];
24 | }
25 |
26 | // Fix issues that spaces before/after string value can cause such as ' San Francisco'
27 | if (this.isString(aVal)) { aVal = aVal.trim().toUpperCase(); }
28 | if (this.isString(bVal)) { bVal = bVal.trim().toUpperCase(); }
29 |
30 | if (aVal === bVal) {
31 | return 0;
32 | } else if (aVal > bVal) {
33 | return this.direction * -1;
34 | } else {
35 | return this.direction * 1;
36 | }
37 | });
38 | }
39 |
40 | isString(val: any): boolean {
41 | return (val && (typeof val === 'string' || val instanceof String));
42 | }
43 |
44 | resolveProperty(path: string, obj: any) {
45 | return path.split('.').reduce(function(prev, curr) {
46 | return (prev ? prev[curr] : undefined);
47 | }, obj || self);
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/customers/customers-edit/customers-edit.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/customers/customers-edit/customers-edit.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/angular-store-edits/src/app/customers/customers-edit/customers-edit.component.scss
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/customers/customers-list/customers-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Name |
8 | City |
9 | Order Total |
10 | |
11 |
12 |
13 |
14 | {{ cust.name | capitalize }} |
15 | {{ cust.city }} |
16 | {{ cust.orderTotal | currency:currencyCode:'symbol' }} |
17 | Orders |
18 | Edit |
19 |
20 |
21 | |
22 |
23 | {{ customersOrderTotal | currency:currencyCode:'symbol' }}
24 | |
25 | |
26 |
27 |
28 | No customers found |
29 |
30 |
31 | Number of Customers: {{ filteredCustomers.length }}
32 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/customers/customers-list/customers-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnInit } from '@angular/core';
2 |
3 | import { SorterService } from '../../core/sorter.service';
4 | import { Customer } from '../../core/model/customer';
5 |
6 | @Component({
7 | selector: 'app-customers-list',
8 | templateUrl: './customers-list.component.html'
9 | })
10 | export class CustomersListComponent implements OnInit {
11 | private _customers: Customer[] = [];
12 | @Input() get customers(): Customer[] {
13 | return this._customers;
14 | }
15 | set customers(value: Customer[]) {
16 | if (value) {
17 | this.filteredCustomers = this._customers = value;
18 | this.calculateOrders();
19 | }
20 | }
21 | filteredCustomers: Customer[] = [];
22 | customersOrderTotal = 0;
23 | currencyCode = 'USD';
24 |
25 | constructor(private sorterService: SorterService) { }
26 |
27 | ngOnInit() {
28 |
29 | }
30 |
31 | calculateOrders() {
32 | this.customersOrderTotal = 0;
33 | this.filteredCustomers.forEach((cust: Customer) => {
34 | this.customersOrderTotal += cust.orderTotal;
35 | });
36 | }
37 |
38 | filter(data: string) {
39 | if (data) {
40 | this.filteredCustomers = this.customers.filter((cust: Customer) => {
41 | return cust.name.toLowerCase().indexOf(data.toLowerCase()) > -1 ||
42 | cust.city.toLowerCase().indexOf(data.toLowerCase()) > -1 ||
43 | cust.orderTotal.toString().indexOf(data) > -1;
44 | });
45 | } else {
46 | this.filteredCustomers = this.customers;
47 | }
48 | this.calculateOrders();
49 | }
50 |
51 | sort(prop: string) {
52 | this.sorterService.sort(this.filteredCustomers, prop);
53 | }
54 |
55 | customerTrackBy(index: number, customer: Customer) {
56 | return customer.id;
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/customers/customers-list/filter-textbox.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-filter-textbox',
5 | template: `
6 | Filter:
7 | `
8 | })
9 | export class FilterTextboxComponent implements OnInit {
10 |
11 | private _filter = '';
12 |
13 | @Input() get filter() {
14 | return this._filter;
15 | }
16 |
17 | set filter(val: string) {
18 | this._filter = val;
19 | this.changed.emit(this.filter); // Raise changed event
20 | }
21 |
22 | getInputValue(event: Event) {
23 | return (event.target as HTMLInputElement).value;
24 | }
25 |
26 | @Output() changed: EventEmitter = new EventEmitter();
27 |
28 | constructor() { }
29 |
30 | ngOnInit() {
31 |
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/customers/customers-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 |
4 | import { CustomersComponent } from './customers.component';
5 | import { CustomersEditComponent } from './customers-edit/customers-edit.component';
6 |
7 | const routes: Routes = [
8 | { path: '', component: CustomersComponent },
9 | { path: ':id', component: CustomersEditComponent }
10 | ];
11 |
12 | @NgModule({
13 | imports: [ RouterModule.forChild(routes) ],
14 | exports: [ RouterModule ]
15 | })
16 | export class CustomersRoutingModule {}
17 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/customers/customers.component.html:
--------------------------------------------------------------------------------
1 | {{ title }}
2 |
3 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/customers/customers.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | import { Customer } from '../core/model/customer';
4 | import { Observable, of, merge } from 'rxjs';
5 | import { map } from 'rxjs/operators';
6 | import { CustomersService } from './customers.service';
7 |
8 | @Component({
9 | selector: 'app-customers',
10 | templateUrl: './customers.component.html'
11 | })
12 | export class CustomersComponent implements OnInit {
13 | title = 'Customers';
14 | customers$: Observable = new Observable();
15 |
16 | constructor(private customersService: CustomersService) {}
17 |
18 | ngOnInit() {
19 | this.customers$ = this.customersService.getAll();
20 |
21 | // Could do this to get initial customers plus
22 | // listen for any changes
23 | // this.customers$ = merge(
24 | // // Get initial
25 | // this.customersService.getAll(),
26 | // // Capture any changes to the store
27 | // this.customersService.stateChanged.pipe(
28 | // map(state => {
29 | // if (state) {
30 | // return state.customers;
31 | // }
32 | // })
33 | // ));
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/customers/customers.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 |
4 | import { SharedModule } from '../shared/shared.module';
5 | import { CustomersListComponent } from './customers-list/customers-list.component';
6 | import { FilterTextboxComponent } from './customers-list/filter-textbox.component';
7 | import { CustomersComponent } from './customers.component';
8 | import { CustomersRoutingModule } from './customers-routing.module';
9 | import { CustomersEditComponent } from './customers-edit/customers-edit.component';
10 |
11 | @NgModule({
12 | imports: [ CommonModule, SharedModule, CustomersRoutingModule ],
13 | declarations: [ CustomersListComponent, FilterTextboxComponent, CustomersComponent, CustomersEditComponent]
14 | })
15 | export class CustomersModule { }
16 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/orders/orders-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 |
4 | import { OrdersComponent } from '../orders/orders.component';
5 |
6 | const routes: Routes = [
7 | { path: '', component: OrdersComponent }
8 | ];
9 |
10 | @NgModule({
11 | imports: [ RouterModule.forChild(routes) ],
12 | exports: [ RouterModule ]
13 | })
14 | export class OrdersRoutingModule {
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/orders/orders.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Orders
3 |
4 |
6 |
7 | {{ orderItem.productName }} |
8 | {{ orderItem.itemCost | currency:'USD':'symbol' }} |
9 |
10 |
11 |
12 |
13 | View All Customers
14 |
15 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/orders/orders.component.scss:
--------------------------------------------------------------------------------
1 | .orders-table {
2 | margin-top: 20px;
3 | }
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/orders/orders.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ActivatedRoute } from '@angular/router';
3 | import { Order } from '../core/model/order';
4 | import { Observable } from 'rxjs';
5 | import { OrdersService } from './orders.service';
6 |
7 | @Component({
8 | selector: 'app-orders',
9 | templateUrl: './orders.component.html',
10 | styleUrls: [ './orders.component.scss' ]
11 | })
12 | export class OrdersComponent implements OnInit {
13 |
14 | orders$: Observable = new Observable();
15 |
16 | constructor(private ordersService: OrdersService,
17 | private route: ActivatedRoute) {
18 |
19 | }
20 |
21 | ngOnInit() {
22 | const id = Number(this.route.snapshot.paramMap.get('id'));
23 | this.orders$ = this.ordersService.get(id);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/orders/orders.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 | import { FormsModule } from '@angular/forms';
4 | import { RouterModule } from '@angular/router';
5 |
6 | import { SharedModule } from '../shared/shared.module';
7 | import { OrdersComponent } from './orders.component';
8 | import { OrdersRoutingModule } from './orders-routing.module';
9 |
10 | @NgModule({
11 | imports: [ CommonModule, FormsModule, SharedModule, OrdersRoutingModule ],
12 | declarations: [ OrdersComponent ]
13 | })
14 | export class OrdersModule { }
15 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/shared/capitalize.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({ name: 'capitalize' })
4 | export class CapitalizePipe implements PipeTransform {
5 | transform(value: any) {
6 | if (value) {
7 | return value.charAt(0).toUpperCase() + value.slice(1);
8 | }
9 | return value;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/shared/enums.ts:
--------------------------------------------------------------------------------
1 | export enum Theme {
2 | Light,
3 | Dark
4 | }
5 |
6 | export enum Actions {
7 | SetUserSettings = 'SET_USER_SETTINGS',
8 | UpdateUserSettingsTheme = 'UPDATE_USERSETTINGS_THEME'
9 | }
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/shared/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { Customer, Order } from '../core/model';
2 | import { Theme } from './enums';
3 |
4 | export interface UserSettings {
5 | id: number;
6 | preferredName: string;
7 | email: string;
8 | theme: Theme;
9 | }
10 |
11 | export interface StoreState {
12 | customers: Customer[];
13 | customer: Customer | null;
14 | orders: Order[];
15 | userSettings: UserSettings;
16 | }
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/shared/shared.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 | import { CapitalizePipe } from './capitalize.pipe';
5 |
6 | @NgModule({
7 | imports: [ CommonModule, ReactiveFormsModule ],
8 | exports: [ ReactiveFormsModule, CapitalizePipe ],
9 | declarations: [ CapitalizePipe ]
10 | })
11 | export class SharedModule {}
12 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/user-settings/user-settings.component.html:
--------------------------------------------------------------------------------
1 | User Settings
2 |
8 |
9 |
15 |
16 |
22 |
23 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/user-settings/user-settings.component.scss:
--------------------------------------------------------------------------------
1 | .form-group > label {
2 | font-weight: 500;
3 | }
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/app/user-settings/user-settings.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy } from '@angular/core';
2 | import { Theme } from '../shared/enums';
3 | import { UserSettingsService } from '../core/user-settings.service';
4 | import { UserSettings } from '../shared/interfaces';
5 | import { SubSink } from 'subsink';
6 |
7 | @Component({
8 | selector: 'app-user-settings',
9 | templateUrl: './user-settings.component.html',
10 | styleUrls: ['./user-settings.component.scss']
11 | })
12 | export class UserSettingsComponent implements OnInit, OnDestroy {
13 |
14 | themes = [{label: 'Light', value: 0}, {label: 'Dark', value: 1}];
15 | selectedTheme = Theme.Light;
16 | userSettings: UserSettings = { id: 1, preferredName: '', email: '', theme: this.selectedTheme };
17 | subsink = new SubSink();
18 |
19 | constructor(private userSettingsService: UserSettingsService) { }
20 |
21 | ngOnInit() {
22 | this.subsink.sink = this.userSettingsService.getUserSettings().subscribe(settings => {
23 | this.userSettings = settings;
24 | if (settings) {
25 | this.selectedTheme = settings.theme;
26 | }
27 | });
28 | }
29 |
30 | updateUserSettings() {
31 | this.userSettingsService.updateUserSettings(this.userSettings)
32 | .subscribe((userSettings: any) => this.userSettings = userSettings);
33 | }
34 |
35 | ngOnDestroy() {
36 | this.subsink.unsubscribe();
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/assets/styles.css:
--------------------------------------------------------------------------------
1 | html {
2 | overflow-y: scroll;
3 | overflow-x: hidden;
4 | }
5 |
6 | main {
7 | position: relative;
8 | padding-top: 70px;
9 | }
10 |
11 | /* Ensure display:flex and others don't override a [hidden] */
12 | [hidden] { display: none !important; }
13 |
14 | footer {
15 | margin-top: 15px;
16 | }
17 |
18 | th {
19 | cursor: pointer;
20 | }
21 |
22 | td {
23 | width:33%;
24 | }
25 |
26 | .app-title {
27 | line-height:50px;
28 | font-size:20px;
29 | color: white;
30 | }
31 |
32 | .light-theme body {
33 | background-color: #fff;
34 | color: #000;
35 | }
36 |
37 | .light-theme thead {
38 | background-color: #efefef;
39 | }
40 |
41 | .dark-theme body {
42 | background-color: #3f3f3f;
43 | color: #fff;
44 | }
45 |
46 | .dark-theme thead {
47 | background-color: #000;
48 | }
49 |
50 |
51 | .white {
52 | color: white;
53 | }
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | apiUrlBase: '/api'
4 | };
5 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/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 | apiUrlBase: '/api'
9 | };
10 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/angular-store-edits/src/favicon.ico
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Observable Store Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Loading...
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowser } from '@angular/platform-browser';
3 | import { NgZone } from '@angular/core';
4 | import { Router } from '@angular/router';
5 |
6 | import { environment } from './environments/environment';
7 | import { AppDevModule } from './app/app-dev.module';
8 | import { ObservableStore } from '@codewithdan/observable-store';
9 | import { ReduxDevToolsExtension } from '@codewithdan/observable-store-extensions';
10 |
11 | if (environment.production) {
12 | enableProdMode();
13 | }
14 |
15 | // Set ObservableStore globalSettings here since
16 | // it'll be called before the rest of the app loads
17 | ObservableStore.globalSettings = {
18 | isProduction: environment.production,
19 | trackStateHistory: !environment.production,
20 | logStateChanges: !environment.production
21 | };
22 |
23 | // Optional: Initialize store state
24 | ObservableStore.initializeState({});
25 |
26 | // Add Redux DevTools extensions support
27 | if (!environment.production) {
28 | ObservableStore.addExtension(new ReduxDevToolsExtension({ router: Router, ngZone: NgZone }));
29 | }
30 |
31 | // platformBrowserDynamic().bootstrapModule(AppModule)
32 | // Bootstrap dev module that uses HttpClientInMemoryWebApiModule
33 | platformBrowser()
34 | .bootstrapModule(AppDevModule)
35 | .catch(err => console.log(err));
36 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/src/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/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting()
16 | );
17 | // Then we find all the tests.
18 | const context = require.context('./', true, /\.spec\.ts$/);
19 | // And load the modules.
20 | context.keys().map(context);
21 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts",
10 | "src/polyfills.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/samples/angular-store-edits/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "es2020",
20 | "module": "es2020",
21 | "lib": [
22 | "es2020",
23 | "dom"
24 | ]
25 | },
26 | "angularCompilerOptions": {
27 | "enableI18nLegacyMessageIdFormat": false,
28 | "strictInjectionParameters": true,
29 | "strictInputAccessModifiers": true,
30 | "strictTemplates": true
31 | }
32 | }
--------------------------------------------------------------------------------
/samples/angular-store/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://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 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/samples/angular-store/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/samples/angular-store/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/samples/angular-store/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "pwa-chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/samples/angular-store/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/samples/angular-store/README.md:
--------------------------------------------------------------------------------
1 | ## Observable Store for Angular
2 |
3 | Open the `src/app/core/stores` folder for examples.
4 |
5 | ## Running the Project
6 |
7 | 1. Install the Angular CLI
8 |
9 | `npm install -g @angular/cli`
10 |
11 | 1. Run `npm install` within this folder.
12 |
13 | 1. Run `ng serve -o`
--------------------------------------------------------------------------------
/samples/angular-store/config/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 0.0.0.0:80;
3 | listen [::]:80;
4 | default_type application/octet-stream;
5 |
6 | gzip on;
7 | gzip_comp_level 6;
8 | gzip_vary on;
9 | gzip_min_length 1000;
10 | gzip_proxied any;
11 | gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
12 | gzip_buffers 16 8k;
13 | client_max_body_size 256M;
14 |
15 | root /usr/share/nginx/html;
16 |
17 | location / {
18 | try_files $uri $uri/ /index.html =404;
19 | }
20 | }
--------------------------------------------------------------------------------
/samples/angular-store/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-store",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve -o",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^15.0.0",
14 | "@angular/common": "^15.0.0",
15 | "@angular/compiler": "^15.0.0",
16 | "@angular/core": "^15.0.0",
17 | "@angular/forms": "^15.0.0",
18 | "@angular/platform-browser": "^15.0.0",
19 | "@angular/platform-browser-dynamic": "^15.0.0",
20 | "@angular/router": "^15.0.0",
21 | "@codewithdan/observable-store": "^2.2.15",
22 | "@codewithdan/observable-store-extensions": "^2.2.9",
23 | "subsink": "^1.0.2",
24 | "rxjs": "~7.5.0",
25 | "tslib": "^2.3.0",
26 | "zone.js": "~0.12.0"
27 | },
28 | "devDependencies": {
29 | "@angular-devkit/build-angular": "^15.0.0",
30 | "@angular/cli": "~15.0.0",
31 | "@angular/compiler-cli": "^15.0.0",
32 | "@types/jasmine": "~4.3.0",
33 | "jasmine-core": "~4.5.0",
34 | "karma": "~6.4.0",
35 | "karma-chrome-launcher": "~3.1.0",
36 | "karma-coverage": "~2.2.0",
37 | "karma-jasmine": "~5.1.0",
38 | "karma-jasmine-html-reporter": "~2.0.0",
39 | "typescript": "~4.8.2"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/samples/angular-store/server/data/customers.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1,
4 | "name": "ted James",
5 | "city": " Phoenix ",
6 | "orderTotal": 40.99
7 | },
8 | {
9 | "id": 2,
10 | "name": "Michelle Thompson",
11 | "city": "Los Angeles ",
12 | "orderTotal": 89.99
13 | },
14 | {
15 | "id": 3,
16 | "name": "James Thomas",
17 | "city": " Las Vegas ",
18 | "orderTotal": 29.99
19 | },
20 | {
21 | "id": 4,
22 | "name": "Tina Adams",
23 | "city": "Seattle",
24 | "orderTotal": 15.99
25 | }
26 | ]
27 |
--------------------------------------------------------------------------------
/samples/angular-store/server/node.dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:alpine
2 |
3 | LABEL author="Dan Wahlin"
4 |
5 | WORKDIR /var/www/angular-node-service
6 |
7 | COPY package.json package.json
8 | RUN npm install
9 |
10 | COPY . .
11 |
12 | EXPOSE 3000
13 |
14 | ENTRYPOINT ["node", "server.js"]
--------------------------------------------------------------------------------
/samples/angular-store/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-core-concepts-server",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "start": "node server.js"
7 | },
8 | "dependencies": {
9 | "express": "4.17.3",
10 | "body-parser": "1.20.1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/samples/angular-store/server/server.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var express = require('express'),
3 | bodyParser = require('body-parser'),
4 | app = express(),
5 | customers = require('./data/customers'),
6 | orders = require('./data/orders');
7 |
8 | app.use(bodyParser.urlencoded({ extended: true }));
9 | app.use(bodyParser.json());
10 |
11 | //CORS
12 | app.use(function(req, res, next) {
13 | res.header("Access-Control-Allow-Origin", "*");
14 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, X-XSRF-TOKEN, Content-Type, Accept");
15 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH');
16 | next();
17 | });
18 |
19 | app.get('/api/customers', (req, res) => {
20 | res.json(customers);
21 | });
22 |
23 | app.get('/api/orders', (req, res) => {
24 | res.json(orders);
25 | });
26 |
27 | app.listen(3000);
28 |
29 | console.log('Express listening on port 3000.');
30 |
31 |
32 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 |
4 | const routes: Routes = [
5 | { path: '', pathMatch: 'full', redirectTo: '/customers'},
6 | { path: '**', pathMatch: 'full', redirectTo: '/customers' }
7 | ];
8 |
9 | @NgModule({
10 | imports: [ RouterModule.forRoot(routes) ],
11 | exports: [ RouterModule ]
12 | })
13 | export class AppRoutingModule {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-root',
5 | template: `
6 |
7 | `
8 | })
9 | export class AppComponent implements OnInit {
10 |
11 | constructor() { }
12 |
13 | ngOnInit() {
14 |
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 |
4 | import { CoreModule } from './core/core.module';
5 | import { CustomersModule } from './customers/customers.module';
6 | import { AppComponent } from './app.component';
7 | import { AppRoutingModule } from './app-routing.module';
8 | import { OrdersModule } from './orders/orders.module';
9 |
10 | @NgModule({
11 | imports: [ BrowserModule, CoreModule, CustomersModule, OrdersModule, AppRoutingModule ],
12 | declarations: [ AppComponent ],
13 | bootstrap: [ AppComponent ]
14 | })
15 | export class AppModule { }
16 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/core/core.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { HttpClientModule } from '@angular/common/http';
3 |
4 | @NgModule({
5 | imports: [ HttpClientModule ]
6 | })
7 | export class CoreModule { }
8 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/core/sorter.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable({
4 | providedIn: 'root'
5 | })
6 | export class SorterService {
7 |
8 | property = null;
9 | direction = 1;
10 |
11 | sort(collection: any[], prop: any) {
12 | this.property = prop;
13 | this.direction = (this.property === prop) ? this.direction * -1 : 1;
14 |
15 | collection.sort((a: any, b: any) => {
16 | let aVal: any;
17 | let bVal: any;
18 |
19 | // Handle resolving complex properties such as 'state.name' for prop value
20 | if (prop && prop.indexOf('.') > -1) {
21 | aVal = this.resolveProperty(prop, a);
22 | bVal = this.resolveProperty(prop, b);
23 | }
24 | else {
25 | aVal = a[prop];
26 | bVal = b[prop];
27 | }
28 |
29 | // Fix issues that spaces before/after string value can cause such as ' San Francisco'
30 | if (this.isString(aVal)) { aVal = aVal.trim().toUpperCase(); }
31 | if (this.isString(bVal)) { bVal = bVal.trim().toUpperCase(); }
32 |
33 | if (aVal === bVal) {
34 | return 0;
35 | }
36 | else if (aVal > bVal) {
37 | return this.direction * -1;
38 | }
39 | else {
40 | return this.direction * 1;
41 | }
42 | });
43 | }
44 |
45 | isString(val: any): boolean {
46 | return (val && (typeof val === 'string' || val instanceof String));
47 | }
48 |
49 | resolveProperty(path: string, obj: any) {
50 | return path.split('.').reduce(function(prev, curr) {
51 | return (prev ? prev[curr] : undefined);
52 | }, obj || self);
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/core/store/store-state.ts:
--------------------------------------------------------------------------------
1 | import { Customer, Order } from '../../shared/interfaces';
2 |
3 | export interface StoreState {
4 | customers: Customer[];
5 | customer: Customer;
6 | orders: Order[];
7 | }
8 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/customers/customers-list/customers-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Name |
8 | City |
9 | Order Total |
10 |
11 |
12 |
13 |
14 |
15 | {{ cust.name | capitalize }}
16 |
17 | |
18 | {{ cust.city }} |
19 | {{ cust.orderTotal | currency:currencyCode:'symbol' }}
20 | |
21 |
22 | |
23 |
24 | {{ customersOrderTotal | currency:currencyCode:'symbol' }}
25 | |
26 |
27 |
28 | No customers found |
29 |
30 |
31 | Number of Customers: {{ filteredCustomers.length }}
32 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/customers/customers-list/customers-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnInit } from '@angular/core';
2 |
3 | import { Customer } from '../../shared/interfaces';
4 | import { SorterService } from '../../core/sorter.service';
5 |
6 | @Component({
7 | selector: 'app-customers-list',
8 | templateUrl: './customers-list.component.html'
9 | })
10 | export class CustomersListComponent implements OnInit {
11 | private _customers: Customer[] = [];
12 | @Input() get customers(): Customer[] {
13 | return this._customers;
14 | }
15 | set customers(value: Customer[]) {
16 | if (value) {
17 | this.filteredCustomers = this._customers = value;
18 | this.calculateOrders();
19 | }
20 | }
21 | filteredCustomers: Customer[] = [];
22 | customersOrderTotal = 0;
23 | currencyCode = 'USD';
24 |
25 | constructor(private sorterService: SorterService) { }
26 |
27 | ngOnInit() {
28 |
29 | }
30 |
31 | calculateOrders() {
32 | this.customersOrderTotal = 0;
33 | this.filteredCustomers.forEach((cust: Customer) => {
34 | if (cust && cust.orderTotal) {
35 | this.customersOrderTotal += cust.orderTotal;
36 | }
37 | });
38 | }
39 |
40 | filter(data: string) {
41 | if (data) {
42 | this.filteredCustomers = this.customers.filter((cust: Customer) => {
43 | const orderTotal = cust.orderTotal || '';
44 | return cust.name.toLowerCase().indexOf(data.toLowerCase()) > -1 ||
45 | cust.city.toLowerCase().indexOf(data.toLowerCase()) > -1 ||
46 | orderTotal.toString().indexOf(data) > -1;
47 | });
48 | } else {
49 | this.filteredCustomers = this.customers;
50 | }
51 | this.calculateOrders();
52 | }
53 |
54 | sort(prop: string) {
55 | this.sorterService.sort(this.filteredCustomers, prop);
56 | }
57 |
58 | customerTrackBy(index: number, customer: Customer) {
59 | return customer.id;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/customers/customers-list/filter-textbox.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-filter-textbox',
5 | template: `
6 | Filter:
7 | `
8 | })
9 | export class FilterTextboxComponent implements OnInit {
10 |
11 | private _filter = '';
12 | @Input() get filter() {
13 | return this._filter;
14 | }
15 |
16 | set filter(val: string) {
17 | this._filter = val;
18 | this.changed.emit(this.filter); // Raise changed event
19 | }
20 |
21 | @Output() changed: EventEmitter = new EventEmitter();
22 |
23 | constructor() { }
24 |
25 | ngOnInit() {
26 |
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/customers/customers-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 |
4 | import { CustomersComponent } from './customers.component';
5 |
6 | const routes: Routes = [
7 | { path: 'customers', component: CustomersComponent }
8 | ];
9 |
10 | @NgModule({
11 | imports: [ RouterModule.forChild(routes) ],
12 | exports: [ RouterModule ]
13 | })
14 | export class CustomersRoutingModule {}
15 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/customers/customers.component.html:
--------------------------------------------------------------------------------
1 | {{ title }}
2 |
3 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/customers/customers.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy } from '@angular/core';
2 |
3 | import { SubSink } from 'subsink';
4 | import { Customer } from '../shared/interfaces';
5 | import { CustomersService } from '../core/services/customers.service';
6 |
7 | @Component({
8 | selector: 'app-customers',
9 | templateUrl: './customers.component.html'
10 | })
11 | export class CustomersComponent implements OnInit, OnDestroy {
12 | title = '';
13 | customers: Customer[] = [];
14 | subsink = new SubSink();
15 |
16 | constructor(private customersService: CustomersService) { }
17 |
18 | ngOnInit() {
19 | this.title = 'Customers';
20 |
21 | // Option 1: Subscribe to store stateChanged
22 | // Useful when a component needs to be notified of changes but won't always
23 | // call store directly.
24 | this.subsink.sink = this.customersService.stateChanged.subscribe(state => {
25 | if (state) {
26 | console.log(this.customersService.stateHistory);
27 | this.customers = state.customers;
28 | }
29 | });
30 | this.subsink.sink = this.customersService.getCustomers().subscribe();
31 |
32 | // Option 2: Retrieve data directly from store
33 | // this.subsink.sink = this.customersStore.getCustomers()
34 | // .subscribe((customers: ICustomer[]) => this.customers = customers);
35 | }
36 |
37 | ngOnDestroy() {
38 | this.subsink.unsubscribe();
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/customers/customers.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 | import { FormsModule } from '@angular/forms';
4 | import { RouterModule } from '@angular/router';
5 |
6 | import { SharedModule } from '../shared/shared.module';
7 | import { CustomersListComponent } from './customers-list/customers-list.component';
8 | import { FilterTextboxComponent } from './customers-list/filter-textbox.component';
9 | import { CustomersComponent } from './customers.component';
10 | import { CustomersRoutingModule } from './customers-routing.module';
11 |
12 | @NgModule({
13 | imports: [ CommonModule, FormsModule, SharedModule, CustomersRoutingModule ],
14 | declarations: [ CustomersListComponent, FilterTextboxComponent,
15 | CustomersComponent]
16 | })
17 | export class CustomersModule { }
18 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/orders/orders-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 |
4 | import { OrdersComponent } from '../orders/orders.component';
5 |
6 | const routes: Routes = [
7 | { path: 'orders/:id', component: OrdersComponent }
8 | ];
9 |
10 | @NgModule({
11 | imports: [ RouterModule.forChild(routes) ],
12 | exports: [ RouterModule ]
13 | })
14 | export class OrdersRoutingModule {
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/orders/orders.component.css:
--------------------------------------------------------------------------------
1 | .orders-table {
2 | margin-top: 20px;
3 | }
--------------------------------------------------------------------------------
/samples/angular-store/src/app/orders/orders.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Orders for {{ customer.name | capitalize }}
3 |
4 |
5 |
6 |
7 | {{ orderItem.productName }} |
8 | {{ orderItem.itemCost | currency:'USD':'symbol' }} |
9 |
10 |
11 |
12 |
13 |
14 |
15 | No customer found
16 |
17 |
18 |
19 | View All Customers
20 |
21 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/orders/orders.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy } from '@angular/core';
2 | import { ActivatedRoute } from '@angular/router';
3 |
4 | import { Customer, Order } from '../shared/interfaces';
5 | import { CustomersService } from '../core/services/customers.service';
6 | import { OrdersService } from '../core/services/orders.service';
7 | import { Observable } from 'rxjs';
8 |
9 | @Component({
10 | selector: 'app-orders',
11 | templateUrl: './orders.component.html',
12 | styleUrls: [ './orders.component.css' ]
13 | })
14 | export class OrdersComponent implements OnInit, OnDestroy {
15 |
16 | customer$: Observable = new Observable();
17 | orders$: Observable = new Observable();
18 |
19 | constructor(private customersService: CustomersService,
20 | private ordersService: OrdersService,
21 | private route: ActivatedRoute) { }
22 |
23 | ngOnInit() {
24 | const id = Number(this.route.snapshot.paramMap.get('id'));
25 |
26 | // Option 1: Access data directly from store
27 | this.customer$ = this.customersService.getCustomer(id);
28 | this.orders$ = this.ordersService.getOrders(id);
29 |
30 | // Option 2: Could subscribe to store stateChanged (see customers/customers.component.ts for an example)
31 | }
32 |
33 | ngOnDestroy() {
34 |
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/orders/orders.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 | import { FormsModule } from '@angular/forms';
4 | import { RouterModule } from '@angular/router';
5 |
6 | import { SharedModule } from '../shared/shared.module';
7 | import { OrdersComponent } from './orders.component';
8 | import { OrdersRoutingModule } from './orders-routing.module';
9 |
10 | @NgModule({
11 | imports: [ CommonModule, FormsModule, SharedModule, OrdersRoutingModule ],
12 | declarations: [ OrdersComponent ]
13 | })
14 | export class OrdersModule { }
15 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/shared/auto-unsubscribe.decorator.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/NetanelBasal/ngx-auto-unsubscribe/blob/master/src/auto-unsubscribe.ts
2 | const isFunction = (fn) => typeof fn === 'function';
3 |
4 | const doUnsubscribe = (subscription) => {
5 | subscription && isFunction(subscription.unsubscribe) && subscription.unsubscribe();
6 | }
7 |
8 | const doUnsubscribeIfArray = (subscriptionsArray) => {
9 | Array.isArray(subscriptionsArray) && subscriptionsArray.forEach(doUnsubscribe);
10 | }
11 |
12 | export function AutoUnsubscribe({ blackList = [], includeArrays = false, arrayName = '', event = 'ngOnDestroy'} = {}) {
13 |
14 | return function (constructor: Function) {
15 | const original = constructor.prototype[event];
16 |
17 | if (!isFunction(original) && !disableAutoUnsubscribeAot()) {
18 | console.warn(`${constructor.name} is using @AutoUnsubscribe but does not implement OnDestroy`);
19 | }
20 |
21 | constructor.prototype[event] = function () {
22 | if (arrayName) {
23 | return doUnsubscribeIfArray(this[arrayName]);
24 | }
25 |
26 | for (let propName in this) {
27 | if (blackList.includes(propName)) continue;
28 |
29 | const property = this[propName];
30 | doUnsubscribe(property);
31 | doUnsubscribeIfArray(property);
32 | }
33 |
34 | isFunction(original) && original.apply(this, arguments);
35 | };
36 | }
37 |
38 |
39 | function disableAutoUnsubscribeAot() {
40 | return window && window['disableAutoUnsubscribeAot'] || window['disableAuthUnsubscribeAot'];
41 | }
42 | }
--------------------------------------------------------------------------------
/samples/angular-store/src/app/shared/capitalize.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({ name: 'capitalize' })
4 | export class CapitalizePipe implements PipeTransform {
5 | transform(value: any) {
6 | if (value) {
7 | return value.charAt(0).toUpperCase() + value.slice(1);
8 | }
9 | return value;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/shared/interfaces.ts:
--------------------------------------------------------------------------------
1 | export interface Customer {
2 | id: number;
3 | name: string;
4 | city: string;
5 | orderTotal?: number;
6 | customerSince: any;
7 | }
8 |
9 | export interface Order {
10 | customerId: number;
11 | orderItems: OrderItem[];
12 | }
13 |
14 | export interface OrderItem {
15 | id: number;
16 | productName: string;
17 | itemCost: number;
18 | }
19 |
--------------------------------------------------------------------------------
/samples/angular-store/src/app/shared/shared.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { CapitalizePipe } from './capitalize.pipe';
4 |
5 |
6 | @NgModule({
7 | declarations: [ CapitalizePipe ],
8 | exports: [ CapitalizePipe ]
9 | })
10 | export class SharedModule { }
11 |
--------------------------------------------------------------------------------
/samples/angular-store/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/angular-store/src/assets/.gitkeep
--------------------------------------------------------------------------------
/samples/angular-store/src/assets/customers.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1,
4 | "name": "ted James",
5 | "city": " Phoenix ",
6 | "orderTotal": 40.99
7 | },
8 | {
9 | "id": 2,
10 | "name": "Michelle Thompson",
11 | "city": "Los Angeles ",
12 | "orderTotal": 89.99
13 | },
14 | {
15 | "id": 3,
16 | "name": "James Thomas",
17 | "city": " Las Vegas ",
18 | "orderTotal": 29.99
19 | },
20 | {
21 | "id": 4,
22 | "name": "Tina Adams",
23 | "city": "Seattle",
24 | "orderTotal": 15.99
25 | }
26 | ]
27 |
--------------------------------------------------------------------------------
/samples/angular-store/src/assets/styles.css:
--------------------------------------------------------------------------------
1 | html {
2 | overflow-y: scroll;
3 | overflow-x: hidden;
4 | }
5 |
6 | main {
7 | position: relative;
8 | padding-top: 70px;
9 | }
10 |
11 | /* Ensure display:flex and others don't override a [hidden] */
12 | [hidden] { display: none !important; }
13 |
14 | footer {
15 | margin-top: 15px;
16 | }
17 |
18 | th {
19 | cursor: pointer;
20 | }
21 |
22 | td {
23 | width:33%;
24 | }
25 |
26 | .app-title {
27 | line-height:50px;
28 | font-size:20px;
29 | color: white;
30 | }
31 |
32 | thead {
33 | background-color: #efefef;
34 | }
--------------------------------------------------------------------------------
/samples/angular-store/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/samples/angular-store/src/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 |
--------------------------------------------------------------------------------
/samples/angular-store/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/angular-store/src/favicon.ico
--------------------------------------------------------------------------------
/samples/angular-store/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Core Concepts
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
24 |
25 |
26 |
27 | Loading...
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/samples/angular-store/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 | import { ObservableStore } from '@codewithdan/observable-store';
7 | import { ReduxDevToolsExtension } from '@codewithdan/observable-store-extensions';
8 |
9 | if (environment.production) {
10 | enableProdMode();
11 | }
12 |
13 | // Set ObservableStore globalSettings here since
14 | // it'll be called before the rest of the app loads
15 | ObservableStore.globalSettings = { isProduction: environment.production };
16 | ObservableStore.addExtension(new ReduxDevToolsExtension());
17 |
18 | platformBrowserDynamic().bootstrapModule(AppModule)
19 | .catch(err => console.log(err));
20 |
--------------------------------------------------------------------------------
/samples/angular-store/src/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 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare const __karma__: any;
17 | declare const require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | // First, initialize the Angular testing environment.
23 | getTestBed().initTestEnvironment(
24 | BrowserDynamicTestingModule,
25 | platformBrowserDynamicTesting()
26 | );
27 | // Then we find all the tests.
28 | const context = require.context('./', true, /\.spec\.ts$/);
29 | // And load the modules.
30 | context.keys().map(context);
31 | // Finally, start Karma to run the tests.
32 | __karma__.start();
33 |
--------------------------------------------------------------------------------
/samples/angular-store/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "baseUrl": "./",
6 | "types": []
7 | },
8 | "exclude": [
9 | "test.ts",
10 | "**/*.spec.ts"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/samples/angular-store/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "baseUrl": "./",
6 | "types": [
7 | "jasmine",
8 | "node"
9 | ]
10 | },
11 | "files": [
12 | "test.ts",
13 | "polyfills.ts"
14 | ],
15 | "include": [
16 | "**/*.spec.ts",
17 | "**/*.d.ts"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/samples/angular-store/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/samples/angular-store/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/samples/angular-store/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ]
26 | },
27 | "angularCompilerOptions": {
28 | "enableI18nLegacyMessageIdFormat": false,
29 | "strictInjectionParameters": true,
30 | "strictInputAccessModifiers": true,
31 | "strictTemplates": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/samples/angular-store/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/samples/javascript-demo/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
23 | .idea
--------------------------------------------------------------------------------
/samples/javascript-demo/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Demo
2 |
3 | This shows how to use Observable Store with vanilla JavaScript (simple example).
4 |
5 | 1. Run `npm install`
6 |
7 | 1. Run `npm run webpack`
8 |
9 | 1. Run `npm start`
10 |
11 | NOTE:
12 |
13 | To develop against the local Observable Store packages, replace the following packages in package.json:
14 |
15 | ```
16 | "@codewithdan/observable-store": "file:../../modules/observable-store/dist",
17 | "@codewithdan/observable-store-extensions": "file:../../modules/observable-store/dist",
18 | ```
--------------------------------------------------------------------------------
/samples/javascript-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-store",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts":{
6 | "webpack": "npx webpack --mode development",
7 | "start": "http-server"
8 | },
9 | "dependencies": {
10 | "@codewithdan/observable-store": "^2.2.15",
11 | "@codewithdan/observable-store-extensions": "2.2.9",
12 | "rxjs": "7.5.7"
13 | },
14 | "devDependencies": {
15 | "http-server": "^14.1.1",
16 | "webpack": "^5.76.0",
17 | "webpack-cli": "^5.0.0"
18 | }
19 | }
--------------------------------------------------------------------------------
/samples/javascript-demo/public/customers.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1,
4 | "name": "ted James",
5 | "city": "Phoenix",
6 | "orderTotal": 40.99
7 | },
8 | {
9 | "id": 2,
10 | "name": "Michelle Thompson",
11 | "city": "Los Angeles",
12 | "orderTotal": 89.99
13 | },
14 | {
15 | "id": 3,
16 | "name": "James Thomas",
17 | "city": "Las Vegas",
18 | "orderTotal": 29.99
19 | },
20 | {
21 | "id": 4,
22 | "name": "Tina Adams",
23 | "city": "Seattle",
24 | "orderTotal": 15.99
25 | }
26 | ]
27 |
--------------------------------------------------------------------------------
/samples/javascript-demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/javascript-demo/public/favicon.ico
--------------------------------------------------------------------------------
/samples/javascript-demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/samples/javascript-demo/src/main.js:
--------------------------------------------------------------------------------
1 | import CustomersStore from './customers-store';
2 |
3 | let customersStore = new CustomersStore();
4 | customersStore.getCustomers().then((customers) => {
5 | // Get customers via fetch
6 | alert('Fetch returned ' + customers.length + ' customers');
7 | // Get customers from store
8 | customersStore.getCustomers().then((customers) => {
9 | alert('Store returned ' + customers.length + ' customers');
10 | });
11 | });
--------------------------------------------------------------------------------
/samples/javascript-demo/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: './src/main.js',
5 | devtool: 'inline-source-map',
6 | resolve: {
7 | extensions: [ '.js' ],
8 | },
9 | output: {
10 | filename: 'bundle.js',
11 | path: path.resolve(__dirname, 'public'),
12 | },
13 | };
--------------------------------------------------------------------------------
/samples/react-store/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 | indent_style = space
12 | indent_size = 2
--------------------------------------------------------------------------------
/samples/react-store/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
23 | .idea
--------------------------------------------------------------------------------
/samples/react-store/README.md:
--------------------------------------------------------------------------------
1 | # React Core Concepts
2 |
3 | This shows how to use Observable Stores to maintain state in a React application.
4 |
5 | 1. Run `npm install`
6 |
7 | 1. Run `npm start`
8 |
9 | ## To use the local version of Observable Store (for trying out the local version of the packages)
10 |
11 | 1. Remove the `@codewithdan` packages from `package.json`.
12 |
13 | 1. Run `npm install`
14 |
15 | 1. Uncomment the 3 `@codewithdan` lines in `config-overrides.js` to define the alias.
16 |
17 | 1. Run the following command in `module/observable-store` to generate a `dist/observable-store` bundle:
18 |
19 | `npm run build`
20 |
21 | 1. Run the following command in `module/observable-store-extensions` to generate a `dist/observable-store-extensions` bundle:
22 |
23 | `npm run build`
24 |
25 | 1. Run the application:
26 |
27 | `npm start`
28 |
29 |
30 |
--------------------------------------------------------------------------------
/samples/react-store/config-overrides.js:
--------------------------------------------------------------------------------
1 | const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
2 | const rewireAliases = require('react-app-rewire-aliases');
3 | const { paths } = require('react-app-rewired');
4 | const path = require('path');
5 |
6 | // https://stackoverflow.com/questions/44114436/the-create-react-app-imports-restriction-outside-of-src-directory
7 | // https://github.com/aze3ma/react-app-rewire-aliases
8 | // https://github.com/timarney/react-app-rewired#how-to-rewire-your-create-react-app-project
9 |
10 | module.exports = (config, env) => {
11 | config.resolve.plugins = config.resolve.plugins.filter(plugin => !(plugin instanceof ModuleScopePlugin));
12 | // config = rewireAliases.aliasesOptions({
13 | // '@codewithdan/observable-store': path.resolve('../../modules/observable-store/dist') // path.resolve(__dirname, `${paths.appSrc}/components/`)
14 | // '@codewithdan/observable-store-extensions': path.resolve('../../modules/observable-store-extensions/dist')
15 | // })(config, env);
16 | return config;
17 | };
--------------------------------------------------------------------------------
/samples/react-store/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-store",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "react-app-rewired start",
7 | "build": "react-app-rewired build",
8 | "test": "react-app-rewired test",
9 | "eject": "react-scripts eject"
10 | },
11 | "dependencies": {
12 | "@codewithdan/observable-store": "^2.2.15",
13 | "@codewithdan/observable-store-extensions": "^2.2.9",
14 | "lodash.isequal": "^4.5.0",
15 | "lodash.orderby": "^4.6.0",
16 | "prop-types": "^15.7.2",
17 | "react": "^16.13.0",
18 | "react-currency-formatter": "^1.1.0",
19 | "react-dom": "^16.13.0",
20 | "react-router-dom": "^5.1.2",
21 | "react-scripts": "^5.0.1",
22 | "rxjs": "6.5.4"
23 | },
24 | "devDependencies": {
25 | "prettier": "^1.19.1",
26 | "react-app-rewire-aliases": "^0.2.0",
27 | "react-app-rewired": "^2.1.5"
28 | },
29 | "eslintConfig": {
30 | "extends": "react-app"
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | }
44 | }
--------------------------------------------------------------------------------
/samples/react-store/public/customers.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1,
4 | "name": "ted James",
5 | "city": "Phoenix",
6 | "orderTotal": 40.99
7 | },
8 | {
9 | "id": 2,
10 | "name": "Michelle Thompson",
11 | "city": "Los Angeles",
12 | "orderTotal": 89.99
13 | },
14 | {
15 | "id": 3,
16 | "name": "James Thomas",
17 | "city": "Las Vegas",
18 | "orderTotal": 29.99
19 | },
20 | {
21 | "id": 4,
22 | "name": "Tina Adams",
23 | "city": "Seattle",
24 | "orderTotal": 15.99
25 | }
26 | ]
27 |
--------------------------------------------------------------------------------
/samples/react-store/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/react-store/public/favicon.ico
--------------------------------------------------------------------------------
/samples/react-store/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 |
24 |
25 | React Observable Store
26 |
27 |
28 |
31 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/samples/react-store/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/samples/react-store/solvedIssues.md:
--------------------------------------------------------------------------------
1 | ## Types error for Node when running `npm start`
2 |
3 | When doing the observable store build ensure that all .d.ts files copied into the react project are deleted. Otherwise it makes a .tsconfig file
4 | and types for Node and other items are missing which causes the error.
5 |
6 | ## Browserlist Error: BrowserslistError: Unknown browser query `android all`
7 |
8 | https://github.com/facebook/create-react-app/issues/7239
9 |
10 | ## Overriding the ModuleScopePlugin (to go outside of src) and adding Aliases
11 |
12 | https://github.com/timarney/react-app-rewired#how-to-rewire-your-create-react-app-project
13 | https://medium.com/front-end-weekly/creating-alias-for-package-imports-in-react-99d455284029
14 | https://stackoverflow.com/questions/44114436/the-create-react-app-imports-restriction-outside-of-src-directory
15 |
16 | 1. `npm install react-app-rewired react-app-rewire-aliases --save-dev`
17 | 1. Add a `config-overrides.js` file into the root of the project
18 | 1. Add the following into the `config-overrides.js` file:
19 |
20 | ```javascript
21 | const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
22 | const { paths } = require('react-app-rewired');
23 | const path = require('path');
24 |
25 | module.exports = function override(config, env) {
26 | config.resolve.plugins = config.resolve.plugins.filter(plugin => !(plugin instanceof ModuleScopePlugin));
27 | config = rewireAliases.aliasesOptions({
28 | '@codewithdan': path.resolve('../../dist-extensions') // path.resolve(__dirname, `${paths.appSrc}/components/`)
29 | })(config, env);
30 | return config;
31 | };
32 | ```
33 |
34 | 1. Change:
35 |
36 | ```javascript
37 | "scripts": {
38 | "start": "react-scripts start",
39 | "build": "react-scripts build",
40 | "test": "react-scripts test",
41 | "eject": "react-scripts eject"
42 | }
43 | ```
44 |
45 | To:
46 |
47 | ```javascript
48 | "scripts": {
49 | "start": "react-app-rewired start",
50 | "build": "react-app-rewired build",
51 | "test": "react-app-rewired test",
52 | "eject": "react-scripts eject"
53 | },
54 | ```
55 |
56 |
57 |
--------------------------------------------------------------------------------
/samples/react-store/src/Routes.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Router, Route, Redirect } from 'react-router-dom';
3 | import { createBrowserHistory } from 'history';
4 |
5 | import CustomersContainer from './customers/CustomersContainer';
6 | import CustomerEdit from './customers/CustomerEdit';
7 | import OrdersContainer from './orders/OrdersContainer';
8 |
9 | export const history = createBrowserHistory();
10 |
11 | const Routes = () => (
12 |
13 |
14 | }
16 | />
17 |
18 |
19 |
20 |
21 |
22 | );
23 |
24 | export default Routes;
--------------------------------------------------------------------------------
/samples/react-store/src/customers/CustomerRow.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import Currency from 'react-currency-formatter';
4 |
5 | import { capitalize } from '../utils';
6 |
7 | const CustomerRow = ({ customer }) => {
8 | return (
9 |
10 | {capitalize(customer.name)} |
11 | {customer.city} |
12 | |
13 | Orders |
14 | Edit |
15 |
16 | );
17 | };
18 |
19 | export default CustomerRow;
20 |
--------------------------------------------------------------------------------
/samples/react-store/src/customers/CustomersContainer.jsx:
--------------------------------------------------------------------------------
1 | // React
2 | import React, { Component } from 'react';
3 |
4 | // Import store
5 | import CustomersStore from '../stores/CustomersStore';
6 |
7 | // Components
8 | import CustomersList from './CustomersList';
9 |
10 | class CustomersContainer extends Component {
11 | state = {
12 | customers: []
13 | };
14 | storeSub = null;
15 |
16 | componentDidMount() {
17 | // ###### CustomersStore ########
18 | // Option 1: Subscribe to store changes
19 | // Useful when a component needs to be notified of changes but won't always
20 | // call store directly.
21 | this.storeSub = CustomersStore.stateChanged.subscribe(state => {
22 | if (state) {
23 | this.setState({customers: state.customers});
24 | }
25 | });
26 |
27 | CustomersStore.getCustomers();
28 |
29 | // Option 2: Get data directly from store
30 | // CustomersStore.getCustomers()
31 | // .then(customers => {
32 | // this.setState({customers: customers});
33 | // });
34 | }
35 |
36 | componentWillUnmount() {
37 | if (this.storeSub) {
38 | this.storeSub.unsubscribe();
39 | }
40 | }
41 |
42 | render() {
43 | return (
44 |
45 |
Customers
46 |
47 |
48 |
49 | );
50 | }
51 | }
52 |
53 | export default CustomersContainer;
54 |
--------------------------------------------------------------------------------
/samples/react-store/src/index.css:
--------------------------------------------------------------------------------
1 | html {
2 | overflow-y: scroll;
3 | overflow-x: hidden;
4 | }
5 |
6 | main {
7 | position: relative;
8 | padding-top: 70px;
9 | }
10 |
11 | /* Ensure display:flex and others don't override a [hidden] */
12 | [hidden] { display: none !important; }
13 |
14 | footer {
15 | margin-top: 15px;
16 | }
17 |
18 | th {
19 | cursor: pointer;
20 | }
21 |
22 | td {
23 | width:33%;
24 | }
25 |
26 | .app-title {
27 | line-height:50px;
28 | font-size:20px;
29 | color: white;
30 | }
31 |
32 | thead {
33 | background-color: #efefef;
34 | }
--------------------------------------------------------------------------------
/samples/react-store/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import Routes, { history } from './Routes';
5 | import * as serviceWorker from './serviceWorker';
6 | import { ObservableStore } from '@codewithdan/observable-store';
7 | import { ReduxDevToolsExtension } from '@codewithdan/observable-store-extensions';
8 |
9 | const production = process.env.NODE_ENV === 'production';
10 |
11 | ObservableStore.globalSettings = { isProduction: production };
12 | if (!production) {
13 | ObservableStore.addExtension(new ReduxDevToolsExtension({ reactRouterHistory: history }));
14 | }
15 |
16 | ReactDOM.render(, document.getElementById('root'));
17 |
18 | serviceWorker.unregister();
19 |
--------------------------------------------------------------------------------
/samples/react-store/src/orders/OrdersList.jsx:
--------------------------------------------------------------------------------
1 | // React
2 | import React, { Component } from 'react';
3 | import PropTypes from 'prop-types';
4 | import Currency from 'react-currency-formatter';
5 |
6 | class OrdersList extends Component {
7 | static propTypes = {
8 | orders: PropTypes.array.isRequired
9 | };
10 |
11 | render() {
12 | return (
13 |
14 | {this.props.orders.map(order => {
15 | return
16 |
17 |
18 | {order.orderItems.map((orderItem) => {
19 | return
20 | {orderItem.productName} |
21 |
22 |
23 | |
24 |
25 | })}
26 |
27 |
28 |
29 |
30 | })}
31 |
32 | );
33 | }
34 |
35 | }
36 |
37 | export default OrdersList;
38 |
--------------------------------------------------------------------------------
/samples/react-store/src/stores/OrdersStore.js:
--------------------------------------------------------------------------------
1 | import { ObservableStore } from '@codewithdan/observable-store';
2 |
3 | class OrdersStore extends ObservableStore {
4 |
5 | constructor() {
6 | super({ trackStateHistory: true, logStateChanges: true });
7 | }
8 |
9 | fetchOrders() {
10 | return fetch('/orders.json')
11 | .then(response => response.json())
12 | .then(orders => {
13 | this.setState({ orders }, 'SET_ORDERS');
14 | return orders;
15 | });
16 | }
17 |
18 | getOrders(id) {
19 | let state = this.getState();
20 | // pull from store cache
21 | if (state && state.orders) {
22 | return this.createPromise(null, this.filterOrders(id, state.orders));
23 | }
24 | // doesn't exist in store so fetch from server
25 | else {
26 | return this.fetchOrders()
27 | .then(orders => {
28 | return this.filterOrders(id, orders);
29 | });
30 | }
31 | }
32 |
33 | filterOrders(id, orders) {
34 | return orders.filter(order => order.customerId === id);
35 | }
36 |
37 | createPromise(err, result) {
38 | return new Promise((resolve, reject) => {
39 | return err ? reject(err) : resolve(result);
40 | });
41 | }
42 | }
43 |
44 | export default new OrdersStore();
--------------------------------------------------------------------------------
/samples/react-store/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export const capitalize = value => value.charAt(0).toUpperCase() + value.slice(1);
2 |
--------------------------------------------------------------------------------
/samples/vue-store/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 8
4 |
--------------------------------------------------------------------------------
/samples/vue-store/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | 'extends': [
7 | 'plugin:vue/essential',
8 | '@vue/standard'
9 | ],
10 | rules: {
11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
13 | },
14 | parserOptions: {
15 | parser: 'babel-eslint'
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/samples/vue-store/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
--------------------------------------------------------------------------------
/samples/vue-store/README.md:
--------------------------------------------------------------------------------
1 | # vue-multiple-stores
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | npm run lint
21 | ```
22 |
--------------------------------------------------------------------------------
/samples/vue-store/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/samples/vue-store/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-multiple-stores",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "@codewithdan/observable-store": "^2.2.13",
12 | "vue": "^2.6.10",
13 | "vue-router": "^3.1.3"
14 | },
15 | "devDependencies": {
16 | "@vue/cli-plugin-babel": "^5.0.8",
17 | "@vue/cli-plugin-eslint": "^5.0.8",
18 | "@vue/cli-service": "^5.0.8",
19 | "@vue/eslint-config-standard": "^4.0.0",
20 | "vue-template-compiler": "^2.6.10"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/samples/vue-store/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/samples/vue-store/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/vue-store/public/favicon.ico
--------------------------------------------------------------------------------
/samples/vue-store/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-multiple-stores
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/samples/vue-store/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Home |
5 | About
6 |
7 |
8 |
9 |
10 |
11 |
32 |
--------------------------------------------------------------------------------
/samples/vue-store/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanWahlin/Observable-Store/b1448e1c7359c048d5df1205d387e3a007618f92/samples/vue-store/src/assets/logo.png
--------------------------------------------------------------------------------
/samples/vue-store/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation.
8 |
9 |
Installed CLI Plugins
10 |
14 |
Essential Links
15 |
22 |
Ecosystem
23 |
30 |
31 |
32 |
33 |
41 |
42 |
43 |
59 |
--------------------------------------------------------------------------------
/samples/vue-store/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 |
5 | Vue.config.productionTip = false
6 |
7 | new Vue({
8 | router,
9 | render: h => h(App)
10 | }).$mount('#app')
11 |
--------------------------------------------------------------------------------
/samples/vue-store/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Home from './views/Home.vue'
4 |
5 | Vue.use(Router)
6 |
7 | export default new Router({
8 | mode: 'history',
9 | base: process.env.BASE_URL,
10 | routes: [
11 | {
12 | path: '/',
13 | name: 'home',
14 | component: Home
15 | },
16 | {
17 | path: '/about',
18 | name: 'about',
19 | // route level code-splitting
20 | // this generates a separate chunk (about.[hash].js) for this route
21 | // which is lazy-loaded when the route is visited.
22 | component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
23 | }
24 | ]
25 | })
26 |
--------------------------------------------------------------------------------
/samples/vue-store/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/samples/vue-store/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
19 |
--------------------------------------------------------------------------------