├── .editorconfig ├── .github └── workflows │ ├── ci.yml │ └── update_dependencies.yml ├── .gitignore ├── .mergify.yml ├── .npmignore ├── .prettierignore ├── .prettierrc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __test__ ├── plugin.spec.ts └── rx-async-policy.spec.ts ├── artifacts.json ├── package.json ├── rollup.config.js ├── src ├── core.augment.ts ├── core.augment.typedoc.ts ├── index.ts ├── index.typedoc.ts ├── rx-async-policy.ts └── ui-router-rx.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: 'CI' 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | ci: 11 | needs: [test] 12 | runs-on: ubuntu-latest 13 | steps: 14 | - run: true 15 | 16 | test: 17 | name: yarn ${{ matrix.yarncmd }} 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | yarncmd: ['test'] 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Configure 25 | run: | 26 | npm config set scripts-prepend-node-path auto 27 | git config --global user.email uirouter@github.actions 28 | git config --global user.name uirouter_github_actions 29 | - name: Install Dependencies 30 | run: yarn install --pure-lockfile 31 | - name: Check Peer Dependencies 32 | run: npx check-peer-dependencies 33 | - name: Run Tests 34 | run: yarn ${{ matrix.yarncmd }} 35 | -------------------------------------------------------------------------------- /.github/workflows/update_dependencies.yml: -------------------------------------------------------------------------------- 1 | # This workflow requires a personal access token for uirouterbot 2 | name: Weekly Dependency Bumps 3 | on: 4 | repository_dispatch: 5 | types: [update_dependencies] 6 | schedule: 7 | - cron: '0 21 * * 0' 8 | 9 | jobs: 10 | upgrade: 11 | runs-on: ubuntu-latest 12 | name: 'Update ${{ matrix.deptype }} (latest: ${{ matrix.latest }})' 13 | strategy: 14 | matrix: 15 | excludes: [''] 16 | deptype: ['dependencies', 'devDependencies'] 17 | latest: [false] 18 | steps: 19 | - uses: actions/checkout@v2 20 | - run: | 21 | git config user.name uirouterbot 22 | git config user.password ${{ secrets.UIROUTERBOT_PAT }} 23 | git remote set-url origin $(git remote get-url origin | sed -e 's/ui-router/uirouterbot/') 24 | git fetch --unshallow -p origin 25 | - name: Update dependencies 26 | id: upgrade 27 | uses: ui-router/publish-scripts/actions/upgrade@actions-upgrade-v1.0.3 28 | with: 29 | excludes: ${{ matrix.excludes }} 30 | deptype: ${{ matrix.deptype }} 31 | latest: ${{ matrix.latest }} 32 | - name: Create Pull Request 33 | id: cpr 34 | if: ${{ steps.upgrade.outputs.upgrades != '' }} 35 | # the following hash is from https://github.com/peter-evans/create-pull-request/releases/tag/v2.7.0 36 | uses: peter-evans/create-pull-request@340e629d2f63059fb3e3f15437e92cfbc7acd85b 37 | with: 38 | token: ${{ secrets.UIROUTERBOT_PAT }} 39 | request-to-parent: true 40 | branch-suffix: 'random' 41 | commit-message: 'chore(package): Update ${{ steps.upgrade.outputs.upgradecount }} ${{ matrix.deptype }} to ${{ steps.upgrade.outputs.upgradestrategy }}' 42 | title: 'chore(package): Update ${{ steps.upgrade.outputs.upgradecount }} ${{ matrix.deptype }} to ${{ steps.upgrade.outputs.upgradestrategy }}' 43 | body: | 44 | chore(package): Update ${{ steps.upgrade.outputs.upgradecount }} ${{ matrix.deptype }} to ${{ steps.upgrade.outputs.upgradestrategy }} 45 | 46 | ``` 47 | ${{ steps.upgrade.outputs.upgrades }} 48 | ``` 49 | 50 | Auto-generated by [create-pull-request][1] 51 | 52 | [1]: https://github.com/peter-evans/create-pull-request 53 | - name: Apply Merge Label 54 | if: ${{ steps.cpr.outputs.pr_number != '' }} 55 | uses: actions/github-script@0.9.0 56 | with: 57 | github-token: ${{ secrets.UIROUTERBOT_PAT }} 58 | script: | 59 | await github.issues.addLabels({ 60 | owner: context.repo.owner, 61 | repo: context.repo.repo, 62 | issue_number: ${{ steps.cpr.outputs.pr_number }}, 63 | labels: ['ready to squash and merge'] 64 | }); 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # common 2 | node_modules 3 | .DS_Store 4 | *~ 5 | **/.* 6 | .* 7 | stats.html 8 | 9 | # webstorm files 10 | .idea 11 | idea-out 12 | *.iml 13 | *.ipr 14 | *.iws 15 | 16 | _doc 17 | _bundles 18 | lib 19 | lib-esm 20 | yarn-error.log 21 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: Auto Squash and Merge 3 | conditions: 4 | - base=master 5 | - status-success=ci 6 | - 'label=ready to squash and merge' 7 | actions: 8 | delete_head_branch: {} 9 | merge: 10 | method: squash 11 | strict: smart 12 | - name: Auto Rebase and Merge 13 | conditions: 14 | - base=master 15 | - status-success=ci 16 | - 'label=ready to rebase and merge' 17 | actions: 18 | delete_head_branch: {} 19 | merge: 20 | method: rebase 21 | strict: smart 22 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Any hidden files 2 | **/.* 3 | .* 4 | 5 | src 6 | test 7 | scripts 8 | node_modules 9 | 10 | karma.**.js 11 | tslint.json 12 | tsconfig.json 13 | tsconfig.*.json 14 | yarn.lock 15 | stats.html 16 | 17 | *.iml 18 | *.ipr 19 | *.iws 20 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | CHANGELOG.md 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 (2021-11-30) 2 | [Compare `@uirouter/rx` versions 0.6.5 and 1.0.0](https://github.com/ui-router/rx/compare/0.6.5...1.0.0) 3 | 4 | ### Features 5 | 6 | * **rxjs:** Add support for RxJS 7 (in addition to 6) 7 | 8 | ### BREAKING CHANGE 9 | 10 | - rxjs semver range changed to ^6.5.3 || ^7.4.0 11 | 12 | 13 | 14 | ## 0.6.5 (2020-01-13) 15 | [Compare `@uirouter/rx` versions 0.6.4 and 0.6.5](https://github.com/ui-router/rx/compare/0.6.4...0.6.5) 16 | 17 | 18 | 19 | ## 0.6.4 (2019-11-19) 20 | [Compare `@uirouter/rx` versions 0.6.0 and 0.6.4](https://github.com/ui-router/rx/compare/0.6.0...0.6.4) 21 | 22 | ### Bug Fixes 23 | 24 | * make RXWAIT custom async policy AOT compatible ([a091c48](https://github.com/ui-router/rx/commit/a091c48)) 25 | 26 | 27 | 28 | 29 | # 0.6.0 (2019-10-01) 30 | [Compare `@uirouter/rx` versions 0.5.0 and 0.6.0](https://github.com/ui-router/rx/compare/0.5.0...0.6.0) 31 | 32 | ### Bug Fixes 33 | 34 | * **travis:** use service: xvfb instead of launching it manually. install libgconf debian package ([eace3a9](https://github.com/ui-router/rx/commit/eace3a9)) 35 | 36 | 37 | ### Features 38 | 39 | * add rxwait custom async policy ([dca4929](https://github.com/ui-router/rx/commit/dca4929)) 40 | * add rxwait custom async policy ([ab1aaa4](https://github.com/ui-router/rx/commit/ab1aaa4)) 41 | * **package:** require uirouter/core >=6.0.0 via peerDependency ([9bacfa4](https://github.com/ui-router/rx/commit/9bacfa4)) 42 | 43 | 44 | ### BREAKING CHANGES 45 | 46 | * **package:** this version of uirouter/rx depends on uirouter/core version 6 and greater 47 | Because this package now provides an async resolve policy for Observables, this package now has a peerDependency on uirouter/core version >=6.0.0 48 | 49 | 50 | 51 | 52 | # 0.5.0 (2018-05-08) 53 | [Compare `@uirouter/rx` versions 0.4.5 and 0.5.0](https://github.com/ui-router/rx/compare/0.4.5...0.5.0) 54 | 55 | ### Bug Fixes 56 | 57 | * **onError:** Handle transition error so rxjs doesn't log rejections ([84e6210](https://github.com/ui-router/rx/commit/84e6210)) 58 | 59 | 60 | ### Features 61 | 62 | * upgrade to rxjs 6 ([46b8158](https://github.com/ui-router/rx/commit/46b8158)) 63 | 64 | 65 | ### BREAKING CHANGES 66 | 67 | * rxjs 6.0.0 or higher is now required to use this module 68 | 69 | 70 | 71 | 72 | # 0.5.0-alpha.1 (2018-05-07) 73 | [Compare `@uirouter/rx` versions 0.4.5 and 0.5.0-alpha.1](https://github.com/ui-router/rx/compare/0.4.5...0.5.0-alpha.1) 74 | 75 | ### Features 76 | 77 | * upgrade to rxjs 6 ([46b8158](https://github.com/ui-router/rx/commit/46b8158)) 78 | 79 | 80 | ### BREAKING CHANGES 81 | 82 | * rxjs 6.0.0 or higher is now required to use this module 83 | 84 | 85 | 86 | 87 | ## 0.4.5 (2017-10-17) 88 | [Compare `@uirouter/rx` versions 0.4.4 and 0.4.5](https://github.com/ui-router/rx/compare/0.4.4...0.4.5) 89 | 90 | 91 | 92 | ## 0.4.4 (2017-10-12) 93 | [Compare `@uirouter/rx` versions 0.4.2 and 0.4.4](https://github.com/ui-router/rx/compare/0.4.2...0.4.4) 94 | 95 | 96 | ## 0.4.2 (2017-10-12) 97 | [Compare `@uirouter/rx` versions 0.4.1 and 0.4.2](https://github.com/ui-router/rx/compare/0.4.1...0.4.2) 98 | 99 | 100 | 101 | # 0.4.0 (2017-05-08) 102 | 103 | * feat(build): Publish UMD bundles ([fd97a1d](https://github.com/ui-router/rx/commit/fd97a1d)) 104 | * feat(plugin): Re-export UIRouterRx as UIRouterRxPlugin ([51a31e2](https://github.com/ui-router/rx/commit/51a31e2)) 105 | 106 | 107 | ### BREAKING CHANGE 108 | 109 | * rename plugin.name from `ui-router-rx` to `@uirouter/rx` 110 | 111 | 112 | 113 | ## 0.3.2 (2017-05-06) 114 | 115 | * chore(*): added .editorconfig file ([8101b6b](https://github.com/ui-router/rx/commit/8101b6b)) 116 | * chore(build): widen dependency on @uirouter/core ([ef3ac74](https://github.com/ui-router/rx/commit/ef3ac74)) 117 | * chore(gitignore): re-ignore hidden files ([e30ae6e](https://github.com/ui-router/rx/commit/e30ae6e)) 118 | 119 | 120 | 121 | 122 | ## 0.3.1 (2017-04-22) 123 | 124 | * chore(*): Fix botched npm release 125 | 126 | 127 | ## 0.3.0 (2017-04-22) 128 | 129 | * chore(*): Rename npm package from `ui-router-rx` to `@uirouter/rx` 130 | * chore(*): Switch dependency from `ui-router-core` to `@uirouter/core` 131 | 132 | 133 | ## 0.2.1 (2017-01-22) 134 | 135 | * fix(*): fix peer dependency for ui-router-core (from `^3.1.1` to `>=3.1.1`) ([4a29191](https://github.com/ui-router/rx/commit/4a29191)) 136 | 137 | 138 | 139 | 140 | # 0.2.0 (2017-01-20) 141 | 142 | * feat(*): Replace with (more up to date) code from https://github.com/ui-router/ng2/blob/45c73 ([f98574e](https://github.com/ui-router/rx/commit/f98574e)) 143 | 144 | 145 | 146 | # 0.1.0 147 | 148 | * Add package.json ([e4bd097](https://github.com/ui-router/rx/commit/e4bd097)) 149 | * Create README.md ([c5305d6](https://github.com/ui-router/rx/commit/c5305d6)) 150 | * Create ui-router-react.ts ([a1c5346](https://github.com/ui-router/rx/commit/a1c5346)) 151 | * Initial commit ([b5e9dd2](https://github.com/ui-router/rx/commit/b5e9dd2)) 152 | * Update readme ([5ec502f](https://github.com/ui-router/rx/commit/5ec502f)) 153 | * Update README.md ([2b616a4](https://github.com/ui-router/rx/commit/2b616a4)) 154 | 155 | 156 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 UI-Router 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @uirouter/rx 2 | Reactive Extensions (RxJS) for UI-Router 3 | 4 | ### What 5 | 6 | This UI-Router plugin exposes various events in UI-Router 7 | as [RxJS](https://github.com/ReactiveX/rxjs) Observables. 8 | 9 | - Transitions (successfull, or any) 10 | - Parameter values 11 | - State registration/deregistrations 12 | 13 | This helps you to use UI-Router in a reactive mode. 14 | 15 | This plugin works with UI-Router Core 2.0 and above (angular-ui-router 1.0.0-rc.1+, ui-router-ng2 1.0.0-beta.4+, ui-router-react 0.4.0+). 16 | 17 | 18 | ### Getting 19 | 20 | ``` 21 | npm install @uirouter/rx 22 | ``` 23 | 24 | ### Enabling 25 | 26 | This is a UI-Router Plugin. 27 | Add the `UIRouterRx` plugin to your app's `UIRouter` instance. 28 | 29 | ```js 30 | import { UIRouterRx } from "@uirouter/rx"; 31 | 32 | // ... after UI-Router bootstrap, get a reference to the `UIRouter` instance 33 | // ... call `.plugin()` to register the ui-router-rx plugin 34 | uiRouter.plugin(UIRouterRx); 35 | ``` 36 | 37 | ### Using 38 | 39 | In a state definition, 40 | 41 | ```js 42 | const foo$ = (uiRouter) => 43 | uiRouter.globals.params$.map(params => params.fooId) 44 | .distinctUntilChanged() 45 | .map(fooId => fetch('/foo/' + fooId).then(resp => resp.json())) 46 | 47 | var fooState = { 48 | name: 'foo', 49 | url: '/foo/{fooId}', 50 | component: FooComponent, 51 | resolve: [ 52 | { token: 'foo$', deps: [ UIRouter ], resolveFn: foo$ } 53 | ] 54 | }) 55 | ``` 56 | 57 | In the component, access the `foo$` resolve value (it will be an Observable). Subscribe to it and do something with it when it emits a new value. 58 | 59 | ```js 60 | var subscription = foo$.subscribe(foo => this.foo = foo); 61 | ``` 62 | 63 | Don't forget to unsubscribe when the component is destroyed. 64 | 65 | ```js 66 | subscription.unsubscribe(); 67 | ``` 68 | 69 | 70 | -------------------------------------------------------------------------------- /__test__/plugin.spec.ts: -------------------------------------------------------------------------------- 1 | import { UIRouter, servicesPlugin, memoryLocationPlugin } from '@uirouter/core'; 2 | import { UIRouterRxPlugin } from '../src'; 3 | 4 | describe('Globals Augmentation', () => { 5 | let router; 6 | beforeEach(() => { 7 | router = new UIRouter(); 8 | router.plugin(servicesPlugin); 9 | router.plugin(memoryLocationPlugin); 10 | }); 11 | 12 | it('should augument router.globals', () => { 13 | expect(router.globals.success$).not.toBeDefined(); 14 | expect(router.globals.params$).not.toBeDefined(); 15 | 16 | router.plugin(UIRouterRxPlugin); 17 | expect(router.globals.success$).toBeDefined(); 18 | expect(router.globals.params$).toBeDefined(); 19 | }); 20 | }); 21 | 22 | const tick = () => new Promise(resolve => setTimeout(resolve)); 23 | 24 | describe('State Changes', () => { 25 | let router, successes; 26 | 27 | beforeEach(() => { 28 | successes = []; 29 | router = new UIRouter(); 30 | router.plugin(servicesPlugin); 31 | router.plugin(memoryLocationPlugin); 32 | router.plugin(UIRouterRxPlugin); 33 | 34 | router.stateRegistry.register({ name: 'foo' }); 35 | router.stateRegistry.register({ name: 'bar' }); 36 | 37 | router.globals.success$.subscribe(trans => { 38 | successes.push(trans.to().name); 39 | }); 40 | }); 41 | 42 | it('(successful) should emit transitions from router.globals.success$', async function(done) { 43 | await router.stateService.go('foo').then(tick, tick); 44 | expect(router.globals.current.name).toEqual('foo'); 45 | expect(successes).toEqual(['foo']); 46 | 47 | await router.stateService.go('bar').then(tick, tick); 48 | expect(router.globals.current.name).toEqual('bar'); 49 | expect(successes).toEqual(['foo', 'bar']); 50 | 51 | done(); 52 | }); 53 | 54 | it('(unsuccessful) should not emit transitions from router.globals.success$', async function(done) { 55 | const failresolve = () => Promise.reject('the transition should fail'); 56 | router.stateRegistry.register({ name: 'fail', resolve: { failresolve } }); 57 | router.stateService.defaultErrorHandler(() => null); 58 | 59 | await router.stateService.go('foo').then(tick, tick); 60 | expect(successes).toEqual(['foo']); 61 | 62 | try { 63 | await router.stateService.go('fail'); 64 | } catch (ignored) {} 65 | 66 | await tick(); 67 | 68 | expect(router.globals.current.name).toBe('foo'); 69 | expect(successes).toEqual(['foo']); 70 | 71 | done(); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /__test__/rx-async-policy.spec.ts: -------------------------------------------------------------------------------- 1 | import { RXWAIT } from '../src'; 2 | import { Observable, of, Subject } from 'rxjs'; 3 | 4 | describe('RXWAIT', () => { 5 | let resolveSuccessSpy; 6 | 7 | beforeEach(() => { 8 | resolveSuccessSpy = jasmine.createSpy('resolveSuccess'); 9 | }); 10 | 11 | describe('when provided with something other than an observable', () => { 12 | let resolve; 13 | 14 | beforeEach(() => { 15 | resolve = RXWAIT('5'); 16 | 17 | return resolve.then(resolveSuccessSpy); 18 | }); 19 | 20 | it('should resolve with an observable', () => { 21 | return resolve.then(observable => { 22 | observable.subscribe(value => { 23 | expect(value).toBe('5'); 24 | }); 25 | }); 26 | }); 27 | }); 28 | 29 | describe('when provided with an observable that already emitted', () => { 30 | let resolve; 31 | 32 | beforeEach(() => { 33 | resolve = RXWAIT(of('5')); 34 | }); 35 | 36 | it('should resolve straight away with an observable', () => { 37 | return resolve.then(observable => { 38 | observable.subscribe(value => { 39 | expect(value).toBe('5'); 40 | }); 41 | }); 42 | }); 43 | }); 44 | 45 | describe('when provided with an observable that has not emitted yet', () => { 46 | let resolve, resolveRejectSpy, resolveSubject; 47 | 48 | beforeEach(() => { 49 | resolveSubject = new Subject(); 50 | 51 | resolveRejectSpy = jasmine.createSpy('resolveReject'); 52 | 53 | resolve = RXWAIT(resolveSubject.asObservable()); 54 | 55 | resolve.then(resolveSuccessSpy, resolveRejectSpy); 56 | }); 57 | 58 | it('should not resolve the promise', () => { 59 | expect(resolveSuccessSpy).not.toHaveBeenCalled(); 60 | }); 61 | 62 | describe('when the observable emits one value', () => { 63 | beforeEach(() => { 64 | resolveSubject.next('5'); 65 | 66 | return resolve; 67 | }); 68 | 69 | it('should resolve the promise with an observable', () => { 70 | expect(resolveSuccessSpy).toHaveBeenCalledWith(jasmine.any(Observable)); 71 | }); 72 | 73 | describe('when the observable emits again', () => { 74 | let value; 75 | 76 | beforeEach(() => { 77 | value = null; 78 | 79 | resolveSuccessSpy.calls.mostRecent().args[0].subscribe(result => { 80 | value = result; 81 | }); 82 | 83 | resolveSubject.next('6'); 84 | }); 85 | 86 | it('should emite the new value', () => { 87 | expect(value).toBe('6'); 88 | }); 89 | }); 90 | }); 91 | 92 | describe('when the observable error', () => { 93 | beforeEach(() => { 94 | resolveSubject.error('Something went wrong'); 95 | 96 | return resolve.catch(() => null); 97 | }); 98 | 99 | it('should reject the promise with the error', () => { 100 | expect(resolveRejectSpy).toHaveBeenCalledWith('Something went wrong'); 101 | }); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /artifacts.json: -------------------------------------------------------------------------------- 1 | { 2 | "ARTIFACTS": [ 3 | "lib", 4 | "lib-esm", 5 | "_bundles", 6 | "package.json" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uirouter/rx", 3 | "version": "1.0.0", 4 | "description": "Reactive extensions for UI-Router", 5 | "scripts": { 6 | "clean": "shx rm -rf lib lib-esm _bundles yarn-error.log stats.html", 7 | "compile": "npm run clean && tsc && tsc -outDir lib-esm -m es6", 8 | "bundle": "rollup -c", 9 | "build": "run-s compile bundle fixmaps:*", 10 | "test": "jest", 11 | "test:debug": "node --inspect ./node_modules/.bin/jest --runInBand --watch", 12 | "fixmaps:lib": "tweak_sourcemap_paths -a --include 'lib/**/*.js.map' 'lib-esm/**/*.js.map'", 13 | "fixmaps:bundle": "tweak_sourcemap_paths -p ../src --include '_bundles/**/*.js.map'", 14 | "release": "release", 15 | "prepublishOnly": "npm run build", 16 | "precommit": "pretty-quick --staged" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/ui-router/rx.git" 21 | }, 22 | "keywords": [ 23 | "ui-router", 24 | "reactive", 25 | "rxjs" 26 | ], 27 | "main": "lib/index.js", 28 | "module": "lib-esm/index.js", 29 | "typings": "lib/index.d.ts", 30 | "author": "Chris Thielen", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/ui-router/rx/issues" 34 | }, 35 | "homepage": "https://github.com/ui-router/rx#readme", 36 | "peerDependencies": { 37 | "@uirouter/core": ">=6.0.1", 38 | "rxjs": "^6.5.3 || ^7.4.0" 39 | }, 40 | "devDependencies": { 41 | "@rollup/plugin-commonjs": "15.0.0", 42 | "@rollup/plugin-node-resolve": "9.0.0", 43 | "@types/jest": "26.0.13", 44 | "@uirouter/core": "^6.0.1", 45 | "@uirouter/publish-scripts": "2.5.5", 46 | "husky": "^4.2.5", 47 | "jasmine": "^3.1.0", 48 | "jest": "^26.0.1", 49 | "prettier": "^2.0.5", 50 | "pretty-quick": "^3.0.2", 51 | "rollup": "^2.10.9", 52 | "rollup-plugin-sourcemaps": "^0.6.2", 53 | "rollup-plugin-terser": "^7.0.2", 54 | "rollup-plugin-visualizer": "^4.0.4", 55 | "rxjs": "^6.5.3 || ^7.4.0", 56 | "ts-jest": "^26.3.0", 57 | "typescript": "^4.0.2" 58 | }, 59 | "jest": { 60 | "transform": { 61 | ".(ts|tsx)": "ts-jest" 62 | }, 63 | "preset": "ts-jest", 64 | "restoreMocks": true 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | 4 | let pkg = require('./package.json'); 5 | let banner = `/** 6 | * ${pkg.description} 7 | * @version v${pkg.version} 8 | * @link ${pkg.homepage} 9 | * @license MIT License, http://www.opensource.org/licenses/MIT 10 | */`; 11 | 12 | let plugins = [nodeResolve(), commonjs()]; 13 | 14 | // Suppress this error message... there are hundreds of them. Angular team says to ignore it. 15 | // https://github.com/rollup/rollup/wiki/Troubleshooting#this-is-undefined 16 | function onwarn(warning) { 17 | if (warning.code === 'THIS_IS_UNDEFINED') return; 18 | console.error(warning.message); 19 | } 20 | 21 | function isExternal(id) { 22 | // @uirouter/core and rxjs should be external 23 | let externals = [/^rxjs\/?/, /\/rxjs\//, /^@uirouter\/.*/]; 24 | let isExternal = externals.map(regex => regex.exec(id)).reduce((acc, val) => acc || !!val, false); 25 | // console.log(id, isExternal); 26 | return isExternal; 27 | } 28 | 29 | const CONFIG = { 30 | input: 'lib-esm/index.js', 31 | output: { 32 | file: '_bundles/ui-router-rx.js', 33 | name: '@uirouter/rx', 34 | globals: { 35 | '@uirouter/core': '@uirouter/core', 36 | '@uirouter/rx': '@uirouter/rx', 37 | }, 38 | sourcemap: true, 39 | banner: banner, 40 | format: 'umd', 41 | exports: 'named', 42 | }, 43 | 44 | plugins: plugins, 45 | onwarn: onwarn, 46 | external: isExternal, 47 | }; 48 | 49 | export default CONFIG; 50 | -------------------------------------------------------------------------------- /src/core.augment.ts: -------------------------------------------------------------------------------- 1 | /** @packageDocumentation @module core */ 2 | import { StatesChangedEvent } from './ui-router-rx'; 3 | import { Transition } from '@uirouter/core'; 4 | import { Observable } from 'rxjs'; 5 | 6 | declare module '@uirouter/core/lib/globals' { 7 | interface UIRouterGlobals { 8 | states$?: Observable; 9 | start$?: Observable; 10 | success$?: Observable; 11 | params$?: Observable<{ [paramName: string]: any }>; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/core.augment.typedoc.ts: -------------------------------------------------------------------------------- 1 | /** @packageDocumentation @coreapi @module rx */ 2 | import { StatesChangedEvent } from './ui-router-rx'; 3 | import { Transition } from '@uirouter/core'; 4 | import { Observable } from 'rxjs'; 5 | 6 | /** 7 | * Augments the core [[core.UIRouterGlobals]] interface 8 | * @coreapi @module rx.augment 9 | */ 10 | // @ts-ignore 11 | declare module '../../core/src/globals' { 12 | /** 13 | * The following observables are added to the core `UIRouterGlobals` object via the [[rx]] plugin. 14 | * 15 | * Access these observables from the router's globals object: 16 | * 17 | * ```js 18 | * router.globals.* 19 | * ``` 20 | */ 21 | interface UIRouterGlobals { 22 | /** 23 | * This observable emits whenever the list of registered states change. 24 | * 25 | * example: 26 | * ```js 27 | * router.globals.states$.subscribe(event => { 28 | * event.deregistered.forEach(state => console.log(`${state.name} has been deregistered`)); 29 | * event.registered.forEach(state => console.log(`${state.name} has been registered`)); 30 | * console.log(`A total of ${event.currentStates.length} states are now registered`); 31 | * }); 32 | * ``` 33 | */ 34 | states$?: Observable; 35 | /** 36 | * This observable emits each Transition when it starts. 37 | * 38 | * example: 39 | * ```js 40 | * router.globals.start$.subscribe(transition => { 41 | * console.log(`About to transition to ${transition.to()}`); 42 | * }); 43 | * ``` 44 | */ 45 | start$?: Observable; 46 | /** 47 | * This observable emits each successful Transition. 48 | * 49 | * example: 50 | * ```js 51 | * router.globals.success$.subscribe(transition => { 52 | * console.log(`Current state is now ${transition.to()}); 53 | * console.log(`Current params are now ${JSON.stringify(transition.params())}); 54 | * }); 55 | * ``` 56 | */ 57 | success$?: Observable; 58 | /** 59 | * This observable emits the current parameter values whenever a transition succeeds. 60 | * 61 | * example: 62 | * ```js 63 | * router.globals.params$.subscribe(transition => { 64 | * console.log(`Current params are now ${JSON.stringify(transition.params())}); 65 | * }); 66 | * ``` 67 | */ 68 | params$?: Observable<{ [paramName: string]: any }>; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import './core.augment'; 2 | export * from './ui-router-rx'; 3 | export * from './rx-async-policy'; 4 | -------------------------------------------------------------------------------- /src/index.typedoc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Reactive ([RxJS](https://rxjs.dev/)) extensions for UI-Router. 3 | * 4 | * From: https://github.com/ui-router/rx 5 | * 6 | * This plugin adds two major features to UI-Router: 7 | * 8 | * - A new [[ResolvePolicy]] called [[RXWAIT]] 9 | * - Observables for state change events 10 | * [[globals]] 11 | * [[UIRouterGlobals]] 12 | * [[core/UIRouterGlobals]] 13 | * [[core.UIRouterGlobals]] 14 | * [[core.uirouterglobals]] 15 | * [[../../core/src/globals]] 16 | * [[_______core_src_globals_]] 17 | * [[core._______core_src_globals_.uirouterglobals]] 18 | * [[_______core_src_globals_.uirouterglobals]] 19 | * 20 | * @packageDocumentation @preferred @coreapi @module rx 21 | */ 22 | 23 | import './core.augment.typedoc'; 24 | export * from './ui-router-rx'; 25 | export * from './rx-async-policy'; 26 | -------------------------------------------------------------------------------- /src/rx-async-policy.ts: -------------------------------------------------------------------------------- 1 | /** @packageDocumentation @publicapi @module rx */ 2 | 3 | import { Observable, of } from 'rxjs'; 4 | import { first, shareReplay } from 'rxjs/operators'; 5 | 6 | /** 7 | * Determines the unwrapping behavior of asynchronous resolve values. 8 | * 9 | * - When an Observable is returned from the resolveFn, wait until the Observable emits at least one item. 10 | * If any other value will be converted to an Observable that emits such value. 11 | * - The Observable item will not be unwrapped. 12 | * - The Observable stream itself will be provided when the resolve is injected or bound elsewhere. 13 | * 14 | * #### Example: 15 | * 16 | * The `Transition` will wait for the `main.home` resolve observables to emit their first value. 17 | * Promises will be unwrapped and returned as observables before being provided to components. 18 | * ```js 19 | * var mainState = { 20 | * name: 'main', 21 | * resolve: mainResolves, // defined elsewhere 22 | * resolvePolicy: { async: RXWAIT }, 23 | * } 24 | * ``` 25 | */ 26 | export function RXWAIT(resolveFnValue: Observable | any): Promise> { 27 | if (!(resolveFnValue instanceof Observable)) { 28 | resolveFnValue = of(resolveFnValue); 29 | } 30 | 31 | const data$: Observable = resolveFnValue.pipe(shareReplay(1)); 32 | 33 | return data$ 34 | .pipe(first()) 35 | .toPromise() 36 | .then(() => { 37 | return data$; 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/ui-router-rx.ts: -------------------------------------------------------------------------------- 1 | /** @packageDocumentation @publicapi @module rx */ 2 | 3 | import { StateDeclaration, Transition, UIRouter, UIRouterPlugin } from '@uirouter/core'; 4 | import { ReplaySubject } from 'rxjs'; 5 | import { filter, map, mergeMap } from 'rxjs/operators'; 6 | 7 | export interface StatesChangedEvent { 8 | currentStates: StateDeclaration[]; 9 | registered: StateDeclaration[]; 10 | deregistered: StateDeclaration[]; 11 | } 12 | 13 | /** Augments UIRouterGlobals with observables for transition starts, successful transitions, and state parameters */ 14 | export class UIRouterRx implements UIRouterPlugin { 15 | name = '@uirouter/rx'; 16 | private deregisterFns: Function[] = []; 17 | 18 | constructor(router: UIRouter) { 19 | let start$ = new ReplaySubject(1); 20 | let success$ = start$.pipe( 21 | mergeMap(t => 22 | t.promise.then( 23 | () => t, 24 | () => null 25 | ) 26 | ), 27 | filter(t => !!t) 28 | ); 29 | let params$ = success$.pipe(map((transition: Transition) => transition.params())); 30 | 31 | let states$ = new ReplaySubject(1); 32 | 33 | function onStatesChangedEvent(event: string, states: StateDeclaration[]) { 34 | let changeEvent = { 35 | currentStates: router.stateRegistry.get(), 36 | registered: [], 37 | deregistered: [], 38 | }; 39 | 40 | if (event) changeEvent[event] = states; 41 | states$.next(changeEvent); 42 | } 43 | 44 | this.deregisterFns.push(router.transitionService.onStart({}, transition => start$.next(transition))); 45 | this.deregisterFns.push(router.stateRegistry.onStatesChanged(onStatesChangedEvent)); 46 | onStatesChangedEvent(null, null); 47 | Object.assign(router.globals, { start$, success$, params$, states$ }); 48 | } 49 | 50 | dispose() { 51 | this.deregisterFns.forEach(deregisterFn => deregisterFn()); 52 | this.deregisterFns = []; 53 | } 54 | } 55 | 56 | export const UIRouterRxPlugin = UIRouterRx; 57 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "inlineSources": true, 8 | "lib": ["es6", "dom"], 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "outDir": "lib", 12 | "rootDir": "src", 13 | "skipLibCheck": true, 14 | "sourceMap": true, 15 | "target": "es5" 16 | }, 17 | "files": ["src/index.ts"] 18 | } 19 | --------------------------------------------------------------------------------