├── tsconfig.json ├── .travis.yml ├── lib ├── index.ts ├── model.ts └── model.test.ts ├── LICENSE ├── .gitignore ├── package.json ├── tslint.json ├── CHANGELOG.md └── README.md /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "strict": true 5 | } 6 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "9" 4 | dist: trusty 5 | install: 6 | - npm i -g npm@6.0.1 7 | - npm ci 8 | script: 9 | - npm test -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { MODEL_PROVIDER } from './model'; 3 | 4 | export * from './model'; 5 | 6 | @NgModule({ 7 | providers: [MODEL_PROVIDER] 8 | }) 9 | export class NgxModelModule {} 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tomas Trajan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | .idea 8 | dist 9 | .ng_pkg_build 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | -------------------------------------------------------------------------------- /lib/model.ts: -------------------------------------------------------------------------------- 1 | import { Observable, BehaviorSubject } from 'rxjs'; 2 | import { map, shareReplay } from 'rxjs/operators'; 3 | 4 | export class Model { 5 | private _data: BehaviorSubject; 6 | 7 | data$: Observable; 8 | 9 | constructor( 10 | initialData: any, 11 | immutable: boolean, 12 | sharedSubscription: boolean, 13 | clone?: (data: T) => T 14 | ) { 15 | this._data = new BehaviorSubject(initialData); 16 | this.data$ = this._data 17 | .asObservable() 18 | .pipe( 19 | map( 20 | (data: T) => 21 | immutable 22 | ? clone 23 | ? clone(data) 24 | : JSON.parse(JSON.stringify(data)) 25 | : data 26 | ), 27 | sharedSubscription ? shareReplay(1) : map((data: T) => data) 28 | ); 29 | } 30 | 31 | get(): T { 32 | return this._data.getValue(); 33 | } 34 | 35 | set(data: T) { 36 | this._data.next(data); 37 | } 38 | } 39 | 40 | export class ModelFactory { 41 | create(initialData: T): Model { 42 | return new Model(initialData, true, false); 43 | } 44 | 45 | createMutable(initialData: T): Model { 46 | return new Model(initialData, false, false); 47 | } 48 | 49 | createMutableWithSharedSubscription(initialData: T): Model { 50 | return new Model(initialData, false, true); 51 | } 52 | 53 | createWithCustomClone(initialData: T, clone: (data: T) => T) { 54 | return new Model(initialData, true, false, clone); 55 | } 56 | } 57 | 58 | export function useModelFactory() { 59 | return new ModelFactory(); 60 | } 61 | 62 | export const MODEL_PROVIDER = { 63 | provide: ModelFactory, 64 | useFactory: useModelFactory 65 | }; 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-model", 3 | "version": "6.0.2", 4 | "description": "Angular Model. Simple state management with minimalistic API, one way data flow, multiple model support and immutable data exposed as RxJS Observable.", 5 | "main": "lib/index.ts", 6 | "scripts": { 7 | "clean": "rm -rf ./dist && rm -rf ./.ng_pkg_build && rm -f ./dist.tgz", 8 | "test": "npm run clean && tslint *.ts && npm run format:ci && mocha ./lib/model.test.ts --require ts-node/register", 9 | "watch": "mocha ./lib/model.test.ts --require ts-node/register --watch-extensions ts --watch ", 10 | "build": "npm run clean && ng-packagr -p package.json", 11 | "release": "npm run test && standard-version && git push --follow-tags origin master && npm run build && npm publish ./dist", 12 | "format": "prettier --single-quote --write \"./**/*.ts\"", 13 | "format:ci": "prettier --single-quote --list-different \"./**/*.ts\"" 14 | }, 15 | "ngPackage": { 16 | "lib": { 17 | "entryFile": "lib/index.ts", 18 | "comments": "none" 19 | } 20 | }, 21 | "homepage": "https://github.com/tomastrajan/ngx-model#readme", 22 | "peerDependencies": { 23 | "@angular/core": ">=6.0.0", 24 | "rxjs": "^6.1.0" 25 | }, 26 | "devDependencies": { 27 | "@angular/compiler": "^6.0.0", 28 | "@angular/compiler-cli": "^6.0.0", 29 | "@angular/core": "^6.0.0", 30 | "@types/mocha": "^5.2.0", 31 | "@types/node": "^9.6.6", 32 | "@types/sinon": "^4.3.1", 33 | "mocha": "^5.1.1", 34 | "ng-packagr": "^3.0.0-rc.2", 35 | "prettier": "^1.12.1", 36 | "rxjs": "^6.1.0", 37 | "sinon": "^4.5.0", 38 | "standard-version": "^4.3.0", 39 | "ts-node": "^6.0.0", 40 | "tsickle": "~0.27.0", 41 | "tslint": "^5.9.1", 42 | "typescript": "~2.7.0", 43 | "zone.js": "^0.8.26" 44 | }, 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/tomastrajan/ngx-model.git" 48 | }, 49 | "keywords": [ 50 | "angular", 51 | "model", 52 | "state management", 53 | "rxjs", 54 | "observable", 55 | "immutable", 56 | "one way data flow", 57 | "typescript" 58 | ], 59 | "author": "Tomas Trajan ", 60 | "license": "MIT", 61 | "bugs": { 62 | "url": "https://github.com/tomastrajan/ngx-model/issues" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "arrow-return-shorthand": true, 4 | "callable-types": true, 5 | "class-name": true, 6 | "comment-format": [ 7 | true, 8 | "check-space" 9 | ], 10 | "curly": true, 11 | "eofline": true, 12 | "forin": true, 13 | "import-blacklist": [ 14 | true, 15 | "rxjs/add/*" 16 | ], 17 | "import-spacing": true, 18 | "indent": [ 19 | true, 20 | "spaces" 21 | ], 22 | "interface-over-type-literal": true, 23 | "label-position": true, 24 | "max-line-length": [ 25 | true, 26 | 80 27 | ], 28 | "member-access": false, 29 | "member-ordering": [ 30 | true, 31 | "variables-before-functions" 32 | ], 33 | "no-arg": true, 34 | "no-bitwise": true, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-construct": true, 44 | "no-debugger": true, 45 | "no-duplicate-super": true, 46 | "no-empty": false, 47 | "no-empty-interface": true, 48 | "no-eval": true, 49 | "no-inferrable-types": [ 50 | true, 51 | "ignore-params" 52 | ], 53 | "no-misused-new": true, 54 | "no-non-null-assertion": true, 55 | "no-shadowed-variable": true, 56 | "no-string-literal": false, 57 | "no-string-throw": true, 58 | "no-switch-case-fall-through": true, 59 | "no-trailing-whitespace": true, 60 | "no-unnecessary-initializer": true, 61 | "no-unused-expression": true, 62 | "no-var-keyword": true, 63 | "object-literal-sort-keys": false, 64 | "one-line": [ 65 | true, 66 | "check-open-brace", 67 | "check-catch", 68 | "check-else", 69 | "check-whitespace" 70 | ], 71 | "prefer-const": true, 72 | "quotemark": [ 73 | true, 74 | "single" 75 | ], 76 | "radix": true, 77 | "semicolon": [ 78 | "always" 79 | ], 80 | "triple-equals": [ 81 | true, 82 | "allow-null-check" 83 | ], 84 | "typedef-whitespace": [ 85 | true, 86 | { 87 | "call-signature": "nospace", 88 | "index-signature": "nospace", 89 | "parameter": "nospace", 90 | "property-declaration": "nospace", 91 | "variable-declaration": "nospace" 92 | } 93 | ], 94 | "unified-signatures": true, 95 | "variable-name": false, 96 | "whitespace": [ 97 | true, 98 | "check-branch", 99 | "check-decl", 100 | "check-operator", 101 | "check-separator", 102 | "check-type" 103 | ] 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/model.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as sinon from 'sinon'; 3 | 4 | import { ModelFactory } from './model'; 5 | 6 | const modelFactory = new ModelFactory(); 7 | 8 | describe('Model', () => { 9 | it('should expose model data in observable', () => { 10 | const model = modelFactory.create({ value: 'test' }); 11 | 12 | model.data$.subscribe(data => assert.deepEqual(data, { value: 'test' })); 13 | }); 14 | 15 | it('should expose raw data getter', () => { 16 | const model = modelFactory.create({ value: 'test' }); 17 | 18 | assert.deepEqual(model.get(), { value: 'test' }); 19 | }); 20 | 21 | it('should expose raw data setter', () => { 22 | const model = modelFactory.create({ value: 'test' }); 23 | 24 | model.set({ value: 'changed' }); 25 | 26 | assert.deepEqual(model.get(), { value: 'changed' }); 27 | }); 28 | 29 | it('should use immutable data in exposed observable by default', () => { 30 | const model = modelFactory.create({ value: 'test' }); 31 | 32 | model.data$.subscribe(data => { 33 | data.value = 'changed'; 34 | assert.deepEqual(model.get(), { value: 'test' }); 35 | }); 36 | }); 37 | 38 | it('should use mutable data in exposed observable when configured', () => { 39 | const model = modelFactory.createMutable({ value: 'test' }); 40 | 41 | model.data$.subscribe(data => { 42 | data.value = 'changed'; 43 | assert.deepEqual(model.get(), { value: 'changed' }); 44 | }); 45 | }); 46 | 47 | it('should use custom clone function when configured', () => { 48 | const cloneSpy = sinon.spy(); 49 | const model = modelFactory.createWithCustomClone( 50 | { value: 'test' }, 51 | cloneSpy 52 | ); 53 | 54 | model.data$.subscribe(() => { 55 | sinon.assert.calledOnce(cloneSpy); 56 | sinon.assert.calledWith(cloneSpy, { value: 'test' }); 57 | }); 58 | }); 59 | 60 | it('should create multiple independent instances', () => { 61 | const model1 = modelFactory.create({ value: 'test1' }); 62 | const model2 = modelFactory.create({ value: 'test2' }); 63 | 64 | model2.set({ value: 'changed' }); 65 | 66 | model1.data$.subscribe(data => assert.deepEqual(data, { value: 'test1' })); 67 | model2.data$.subscribe(data => 68 | assert.deepEqual(data, { value: 'changed' }) 69 | ); 70 | }); 71 | 72 | it('should not share subscription by default', () => { 73 | const model = modelFactory.create({ value: 'test' }); 74 | 75 | model.data$.subscribe(data => { 76 | data.value = 'changed'; 77 | assert.deepEqual(data, { value: 'changed' }); 78 | }); 79 | model.data$.subscribe(data => { 80 | assert.deepEqual(data, { value: 'test' }); 81 | }); 82 | }); 83 | 84 | it('should share subscription when configured', () => { 85 | const model = modelFactory.createMutableWithSharedSubscription({ 86 | value: 'test' 87 | }); 88 | 89 | model.data$.subscribe(data => { 90 | data.value = 'changed'; 91 | assert.deepEqual(data, { value: 'changed' }); 92 | }); 93 | model.data$.subscribe(data => { 94 | assert.deepEqual(data, { value: 'changed' }); 95 | }); 96 | }); 97 | }); 98 | 99 | interface TestModel { 100 | value: string; 101 | } 102 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | ## [6.0.2](https://github.com/tomastrajan/ngx-model/compare/v6.0.1...v6.0.2) (2018-09-24) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * **readme:** update schematics usage section (to be used with schematics 6.0.0+) ([ab4e838](https://github.com/tomastrajan/ngx-model/commit/ab4e838)) 12 | 13 | 14 | 15 | 16 | ## [6.0.1](https://github.com/tomastrajan/ngx-model/compare/v6.0.0...v6.0.1) (2018-05-05) 17 | 18 | 19 | ### Bug Fixes 20 | 21 | * **docs:** stackblitz url ([601737f](https://github.com/tomastrajan/ngx-model/commit/601737f)) 22 | 23 | 24 | 25 | 26 | # [6.0.0](https://github.com/tomastrajan/ngx-model/compare/v5.2.1...v6.0.0) (2018-05-05) 27 | 28 | 29 | ### Features 30 | 31 | * **dependencies:** use version 6 of rxjs and angular, adjust imports ([6fbc50c](https://github.com/tomastrajan/ngx-model/commit/6fbc50c)) 32 | 33 | 34 | ### BREAKING CHANGES 35 | 36 | * **dependencies:** Update ngx-model to work with angular v6.0.0, by bumping peerDependencies of angular and rxjs and adjusting rxjs related imports 37 | 38 | - adjust readme code examples 39 | - adjust tslint import blacklist rule 40 | 41 | 42 | 43 | 44 | ## [5.2.1](https://github.com/tomastrajan/ngx-model/compare/v5.2.0...v5.2.1) (2018-04-22) 45 | 46 | 47 | ### Bug Fixes 48 | 49 | * **build:** use date of release as last modified for generated files,fixes [#7](https://github.com/tomastrajan/ngx-model/issues/7) ([cae1837](https://github.com/tomastrajan/ngx-model/commit/cae1837)) 50 | 51 | 52 | 53 | 54 | # [5.2.0](https://github.com/tomastrajan/ngx-model/compare/v5.1.0...v5.2.0) (2018-03-06) 55 | 56 | 57 | ### Features 58 | 59 | * **build:** add watch script ([d0a4996](https://github.com/tomastrajan/ngx-model/commit/d0a4996)) 60 | * **docs:** pretty highlighting text for code ([11c6b59](https://github.com/tomastrajan/ngx-model/commit/11c6b59)) 61 | * **model:** add support for shared subscription with new factory method "createMutableWithSharedSubscription" ([640ae48](https://github.com/tomastrajan/ngx-model/commit/640ae48)) 62 | 63 | 64 | 65 | 66 | # [5.1.0](https://github.com/tomastrajan/ngx-model/compare/v5.0.0...v5.1.0) (2018-02-10) 67 | 68 | 69 | ### Features 70 | 71 | * **docs:** add getting started with schematics ([945238c](https://github.com/tomastrajan/ngx-model/commit/945238c)) 72 | 73 | 74 | 75 | 76 | # [5.0.0](https://github.com/tomastrajan/ngx-model/compare/v4.0.0...v5.0.0) (2017-11-06) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * **build:** distribute as compiled javascript instead of typescript sources ([12627a4](https://github.com/tomastrajan/ngx-model/commit/12627a4)), closes [#3](https://github.com/tomastrajan/ngx-model/issues/3) 82 | 83 | 84 | ### BREAKING CHANGES 85 | 86 | * **build:** Use ng-packagr to generate UMD, FESM5 and FESM2015 bundles and distribute them instead of original typescript sources. This is necessary since Angular CLI 1.5 correctly doesn't compile 3rd party libraries anymore. 87 | 88 | 89 | 90 | 91 | # [4.0.0](https://github.com/tomastrajan/ngx-model/compare/v3.1.0...v4.0.0) (2017-10-28) 92 | 93 | 94 | ### Features 95 | 96 | * **rxjs:** use lettable operators instead of monkey patching of observable prototype ([2989a91](https://github.com/tomastrajan/ngx-model/commit/2989a91)) 97 | 98 | 99 | ### BREAKING CHANGES 100 | 101 | * **rxjs:** can break applications (very low probability) which use map operator without importing it by themselves thus depending on the ngx-model map operator import, require rxjs 5.5.0 or higher 102 | 103 | 104 | 105 | 106 | # [3.1.0](https://github.com/tomastrajan/ngx-model/compare/v3.0.1...v3.1.0) (2017-09-30) 107 | 108 | 109 | ### Features 110 | 111 | * **dependency:** support wider peerDependency version ranges (angular, rxjs) ([dbeb223](https://github.com/tomastrajan/ngx-model/commit/dbeb223)) 112 | 113 | 114 | 115 | 116 | ## [3.0.1](https://github.com/tomastrajan/ngx-model/compare/v3.0.0...v3.0.1) (2017-09-26) 117 | 118 | ### Docs 119 | 120 | * **readme:** update documentation 121 | 122 | 123 | # [3.0.0](https://github.com/tomastrajan/ngx-model/compare/v2.0.0...v3.0.0) (2017-09-25) 124 | 125 | 126 | ### Bug Fixes 127 | 128 | * **types:** add missing type for function parameter (to work with noImplicitAny) ([a811708](https://github.com/tomastrajan/ngx-model/commit/a811708)), closes [#2](https://github.com/tomastrajan/ngx-model/issues/2) 129 | 130 | 131 | ### BREAKING CHANGES 132 | 133 | * **types:** Should be frictionless update, will result in problems only in case it was used with custom `clone` function with different than expected type signature (highly unlikely) 134 | 135 | 136 | 137 | 138 | # [2.0.0](https://github.com/tomastrajan/ngx-model/compare/v1.0.1...v2.0.0) (2017-08-26) 139 | 140 | 141 | ### Bug Fixes 142 | 143 | * **dependency:** multiple versions of rxjs in single project (TS90010) ([46a83cb](https://github.com/tomastrajan/ngx-model/commit/46a83cb)), closes [#1](https://github.com/tomastrajan/ngx-model/issues/1) 144 | 145 | 146 | ### BREAKING CHANGES 147 | 148 | * **dependency:** rxjs is now peerDependency instead of dependency 149 | 150 | 151 | 152 | 153 | ## [1.0.1](https://github.com/tomastrajan/ngx-model/compare/v1.0.0...v1.0.1) (2017-07-20) 154 | 155 | 156 | ### Bug Fixes 157 | 158 | * lint rules ([eefa8ea](https://github.com/tomastrajan/ngx-model/commit/eefa8ea)) 159 | 160 | 161 | 162 | 163 | # 1.0.0 (2017-07-20) 164 | 165 | 166 | ### Features 167 | 168 | * model ([dd2b952](https://github.com/tomastrajan/ngx-model/commit/dd2b952)) 169 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️ IMPORTANT 2 | ## THIS REPO / PACKAGE HAS BEEN DEPRECATED 3 | 4 | Please use new `@angular-extensions/model`[package](https://www.npmjs.com/package/@angular-extensions/model) / [repo](https://github.com/angular-extensions/model) which is a combination of both the model library and related schematics which renders this package uselsess. On the other hand, feel free to keep using `ngx-model` if it suits your needs, it will not be deleted, but there will be no further development. Please, have a look into [migration](https://github.com/angular-extensions/model#migration-from-ngx-model) section in the new documentation. 5 | 6 | # The Angular Model - ngx-model 7 | by [@tomastrajan](https://twitter.com/tomastrajan) 8 | 9 | [![npm](https://img.shields.io/npm/v/ngx-model.svg)](https://www.npmjs.com/package/ngx-model) [![npm](https://img.shields.io/npm/l/ngx-model.svg)](https://github.com/tomastrajan/ngx-model/blob/master/LICENSE) [![npm](https://img.shields.io/npm/dm/ngx-model.svg)](https://www.npmjs.com/package/ngx-model) [![Build Status](https://travis-ci.org/tomastrajan/ngx-model.svg?branch=master)](https://travis-ci.org/tomastrajan/ngx-model) [![Twitter Follow](https://img.shields.io/twitter/follow/tomastrajan.svg?style=social&label=Follow)](https://twitter.com/tomastrajan) 10 | 11 | Simple state management with minimalistic API, one way data flow, 12 | multiple model support and immutable data exposed as RxJS Observable. 13 | 14 | 15 | ## Documentation 16 | 17 | * [StackBlitz Demo](https://stackblitz.com/github/tomastrajan/ngx-model-example) 18 | * [Demo & Documentation](http://tomastrajan.github.io/angular-model-pattern-example/) 19 | * [Blog Post](https://medium.com/@tomastrajan/model-pattern-for-angular-state-management-6cb4f0bfed87) 20 | * [Changelog](https://github.com/tomastrajan/ngx-model/blob/master/CHANGELOG.md) 21 | * [Schematics](https://www.npmjs.com/package/@angular-extensions/schematics) - generate `ngx-model` services using Angular CLI schematics! 22 | 23 | ![ngx-model dataflow diagram](https://raw.githubusercontent.com/tomastrajan/angular-model-pattern-example/master/src/assets/model_graph.png "ngx-model dataflow diagram") 24 | 25 | ## Getting started 26 | 27 | 1. Install `ngx-model` 28 | 29 | ``` 30 | npm install --save ngx-model 31 | ``` 32 | 33 | or 34 | 35 | ``` 36 | yarn add ngx-model 37 | ``` 38 | 39 | 2. Import and use `NgxModelModule` in you `AppModule` (or `CoreModule`) 40 | 41 | ```ts 42 | import { NgxModelModule } from 'ngx-model'; 43 | 44 | @NgModule({ 45 | imports: [ 46 | NgxModelModule 47 | ] 48 | }) 49 | export class CoreModule {} 50 | 51 | ``` 52 | 53 | 3. Import and use `Model` and `ModelFactory` in your own services. 54 | 55 | ```ts 56 | import { Injectable } from '@angular/core'; 57 | import { Observable } from 'rxjs'; 58 | import { ModelFactory, Model } from 'ngx-model'; 59 | 60 | @Injectable() 61 | export class TodosService { 62 | 63 | private model: Model; 64 | 65 | todos$: Observable; 66 | 67 | constructor(private modelFactory: ModelFactory) { 68 | this.model = this.modelFactory.create([]); // create model and pass initial data 69 | this.todos$ = this.model.data$; // expose model data as named public property 70 | } 71 | 72 | toggleTodo(id: string) { 73 | // retrieve raw model data 74 | const todos = this.model.get(); 75 | 76 | // mutate model data 77 | todos.forEach(t => { 78 | if (t.id === id) { 79 | t.done = !t.done; 80 | } 81 | }); 82 | 83 | // set new model data (after mutation) 84 | this.model.set(todos); 85 | } 86 | 87 | } 88 | ``` 89 | 90 | 4. Use service in your component. Import and inject service into components constructor. 91 | Subscribe to services data in template `todosService.todos$ | async` 92 | or explicitly `this.todosService.todos$.subscribe(todos => { /* ... */ })` 93 | 94 | ```ts 95 | import { Component, OnInit, OnDestroy } from '@angular/core'; 96 | import { Subject } from 'rxjs'; 97 | 98 | import { TodosService, Todo } from './todos.service'; 99 | 100 | @Component({ 101 | selector: 'ngx-model-todos', 102 | templateUrl: ` 103 | /* ... */ 104 |

Todos ({{count}})

105 |
    106 | 107 |
  • 108 | {{todo.name}} 109 |
  • 110 |
111 | `, 112 | }) 113 | export class TodosComponent implements OnInit, OnDestroy { 114 | 115 | private unsubscribe$: Subject = new Subject(); 116 | 117 | count: number; 118 | 119 | constructor(public todosService: TodosService) {} 120 | 121 | ngOnInit() { 122 | // explicit subscription to todos to get count 123 | this.todosService.todos 124 | .pipe( 125 | takeUntil(this.unsubscribe$) // declarative unsubscription 126 | ) 127 | .subscribe(todos => this.count = todos.length); 128 | } 129 | 130 | ngOnDestroy(): void { 131 | // for declarative unsubscription 132 | this.unsubscribe$.next(); 133 | this.unsubscribe$.complete(); 134 | } 135 | 136 | onTodoClick(todo: Todo) { 137 | this.todosService.toggleTodo(todo.id); 138 | } 139 | 140 | } 141 | 142 | ``` 143 | 144 | ## Available Model Factories 145 | 146 | Models are created using model factory as shown in above example `this.model = this.modelFactory.create([]);`. 147 | Multiple model factories are provided out of the box to support different use cases: 148 | 149 | 150 | * `create(initialData: T): Model` - create basic model which is immutable by default (`JSON` cloning) 151 | * `createMutable(initialData: T): Model` - create model with no immutability guarantees (you have to make sure that model consumers don't mutate and corrupt model state) but much more performance because whole cloning step is skipped 152 | * `createMutableWithSharedSubscription(initialData: T): Model` - gain even more performance by skipping both immutability and sharing subscription between all consumers (eg situation in which many components are subscribed to single model) 153 | * `createWithCustomClone(initialData: T, clone: (data: T) => T)` - create immutable model by passing your custom clone function (`JSON` cloning doesn't support properties containing function or regex so custom cloning functionality might be needed) 154 | 155 | 156 | ## Relationship to Angular Model Pattern 157 | 158 | This is a library version of [Angular Model Pattern](https://tomastrajan.github.io/angular-model-pattern-example). 159 | All the original examples and documentation are still valid. The only difference is that 160 | you can install `ngx-model` from npm instead of having to copy model pattern 161 | implementation to your project manually. 162 | 163 | Check out the [Blog Post](https://medium.com/@tomastrajan/model-pattern-for-angular-state-management-6cb4f0bfed87) and 164 | [Advanced Usage Patterns](https://tomastrajan.github.io/angular-model-pattern-example#/advanced) 165 | for more how-tos and examples. 166 | 167 | 168 | ## Getting started with Schematics 169 | 170 | 1. make sure you're using this in project generated with Angular CLI. 171 | 2. install dependency with `npm i -D @angular-extensions/schematics` 172 | 3. generate model services with `ng g @angular-extensions/schematics:model --name path/my-model` 173 | 4. or with `ng g @angular-extensions/schematics:model --name path/my-model-collection --items` form model of collection of items 174 | 5. add your own model service methods and tests 175 | 176 | ![Generating model using schematics](https://raw.githubusercontent.com/angular-extensions/schematics/master/assets/model-schematics.gif) 177 | --------------------------------------------------------------------------------