├── .eslintrc.js
├── .github
├── stale.yml
└── workflows
│ ├── ci.yml
│ └── update_dependencies.yml
├── .gitignore
├── .mergify.yml
├── .npmignore
├── .npmrc
├── .prettierignore
├── .prettierrc.json
├── .yarnclean
├── CHANGELOG.md
├── CONTRIBUTING.md
├── DOCS.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── artifacts.json
├── bower.json
├── downstream_projects.json
├── jest.config.js
├── karma.conf.js
├── package.json
├── rollup.config.js
├── scripts
├── bower_release.js
└── npm_angular_ui_router_release.js
├── src
├── angular.ts
├── directives
│ ├── stateDirectives.ts
│ └── viewDirective.ts
├── index.ts
├── injectables.ts
├── interface.ts
├── legacy
│ ├── core-adapter.js
│ ├── resolveService.ts
│ └── stateEvents.ts
├── locationServices.ts
├── services.ts
├── stateFilters.ts
├── stateProvider.ts
├── statebuilders
│ ├── onEnterExitRetain.ts
│ └── views.ts
├── templateFactory.ts
├── urlRouterProvider.ts
└── viewScroll.ts
├── test
├── angular
│ ├── 1.2
│ │ ├── angular-animate.js
│ │ ├── angular-mocks.js
│ │ └── angular.js
│ ├── 1.3
│ │ ├── angular-animate.js
│ │ ├── angular-mocks.js
│ │ └── angular.js
│ ├── 1.4
│ │ ├── angular-animate.js
│ │ ├── angular-mocks.js
│ │ └── angular.js
│ ├── 1.5
│ │ ├── angular-animate.js
│ │ ├── angular-mocks.js
│ │ └── angular.js
│ ├── 1.6
│ │ ├── angular-animate.js
│ │ ├── angular-mocks.js
│ │ └── angular.js
│ ├── 1.7
│ │ ├── angular-animate.js
│ │ ├── angular-mocks.js
│ │ └── angular.js
│ ├── jest-angular.js
│ └── update_all.sh
├── jest.init.ts
├── ng1StateBuilderSpec.ts
├── resolveSpec.ts
├── servicesSpec.ts
├── stateDirectivesSpec.ts
├── stateEventsSpec.ts
├── stateFiltersSpec.ts
├── stateSpec.ts
├── templateFactorySpec.ts
├── tsconfig.json
├── typescript
│ ├── 3.9
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── mktest.sh
│ └── template
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
├── urlMatcherFactorySpec.ts
├── urlRouterSpec.ts
├── util
│ └── testUtilsNg1.ts
├── viewDirectiveSpec.ts
├── viewHookSpec.ts
├── viewScrollSpec.ts
└── viewSpec.ts
├── tsconfig.docgen.json
├── tsconfig.json
├── typedoc.json
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-undef
2 | module.exports = {
3 | env: { browser: true },
4 | parser: '@typescript-eslint/parser',
5 | plugins: ['@typescript-eslint'],
6 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier', 'prettier/@typescript-eslint'],
7 | rules: {
8 | '@typescript-eslint/no-explicit-any': 'off',
9 | '@typescript-eslint/explicit-module-boundary-types': 'off',
10 |
11 | '@typescript-eslint/ban-types': [
12 | 'error',
13 | {
14 | types: {
15 | Function: false,
16 | },
17 | },
18 | ],
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 180
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 14
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - notstale
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel: stale
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: |
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs.
15 |
16 | This does not mean that the issue is invalid. Valid issues
17 | may be reopened.
18 |
19 | Thank you for your contributions.
20 | # Comment to post when closing a stale issue. Set to `false` to disable
21 | closeComment: false
22 |
--------------------------------------------------------------------------------
/.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', 'test:downstream', 'docs']
22 | steps:
23 | - uses: actions/checkout@v2
24 | - name: Configure Environment
25 | run: |
26 | git config --global user.email uirouter@github.actions
27 | git config --global user.name uirouter_github_actions
28 | - name: Install Dependencies
29 | run: yarn install --pure-lockfile
30 | - name: Check Peer Dependencies
31 | run: npx check-peer-dependencies
32 | - name: Run yarn ${{ matrix.yarncmd }}
33 | run: yarn ${{ matrix.yarncmd }}
34 |
--------------------------------------------------------------------------------
/.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 20 * * 0'
8 |
9 | jobs:
10 | upgrade-dependencies:
11 | runs-on: ubuntu-latest
12 | name: Update dependencies
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 | bower_components
4 | yarn-error.log
5 |
6 | .DS_Store
7 | *~
8 | **/.*
9 |
10 | # webstorm files
11 | idea-out
12 | *.iml
13 | *.ipr
14 | *.iws
15 |
16 | # generated files
17 | _doc
18 | lib
19 | lib-esm
20 | release
21 |
22 | # do not ignore github
23 | !.github
24 | !.npmrc
25 | !.npmignore
26 | !.prettier*
27 |
--------------------------------------------------------------------------------
/.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 | node_modules
5 | scripts
6 | src
7 | test
8 | _docs
9 |
10 | bower.json
11 | karma.conf.js
12 | tsconfig.json
13 | tsconfig.**.json
14 | webpack.config.js
15 |
16 | *.iml
17 | *.ipr
18 | *.iws
19 | idea-out
20 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | scripts-prepend-node-path=auto
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | package.json
2 | bower.json
3 | CHANGELOG.md
4 | test/**/angular.js
5 | test/**/angular-mocks.js
6 | test/**/angular-animate.js
7 | release/**
8 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "es5",
4 | "printWidth": 120
5 | }
6 |
--------------------------------------------------------------------------------
/.yarnclean:
--------------------------------------------------------------------------------
1 | # test directories
2 | #__tests__
3 | #test
4 | #tests
5 | #powered-test
6 |
7 | # asset directories
8 | #docs
9 | #doc
10 | #website
11 | #images
12 | #assets
13 |
14 | # examples
15 | #example
16 | #examples
17 |
18 | # code coverage directories
19 | #coverage
20 | #.nyc_output
21 |
22 | # build scripts
23 | #Makefile
24 | #Gulpfile.js
25 | #Gruntfile.js
26 |
27 | # configs
28 | #.tern-project
29 | #.gitattributes
30 | #.editorconfig
31 | #.*ignore
32 | #.eslintrc
33 | #.jshintrc
34 | #.flowconfig
35 | #.documentup.json
36 | #.yarn-metadata.json
37 | #.*.yml
38 | #*.yml
39 |
40 | # misc
41 | #*.gz
42 | #*.md
43 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 | # Report an Issue
3 |
4 | Help us make UI-Router better! If you think you might have found a bug, or some other weirdness, start by making sure
5 | it hasn't already been reported. You can [search through existing issues](https://github.com/angular-ui/ui-router/search?q=wat%3F&type=Issues)
6 | to see if someone's reported one similar to yours.
7 |
8 | If not, then [create a plunkr](http://bit.ly/UIR-Plunk) that demonstrates the problem (try to use as little code
9 | as possible: the more minimalist, the faster we can debug it).
10 |
11 | Next, [create a new issue](https://github.com/angular-ui/ui-router/issues/new) that briefly explains the problem,
12 | and provides a bit of background as to the circumstances that triggered it. Don't forget to include the link to
13 | that plunkr you created!
14 |
15 | **Note**: If you're unsure how a feature is used, or are encountering some unexpected behavior that you aren't sure
16 | is a bug, it's best to talk it out on
17 | [StackOverflow](http://stackoverflow.com/questions/ask?tags=angularjs,angular-ui-router) before reporting it. This
18 | keeps development streamlined, and helps us focus on building great software.
19 |
20 |
21 | Issues only! |
22 | -------------|
23 | Please keep in mind that the issue tracker is for *issues*. Please do *not* post an issue if you need help or support. Instead, see one of the above-mentioned forums or [IRC](irc://irc.freenode.net/#angularjs). |
24 |
25 | #### Purple Labels
26 | A purple label means that **you** need to take some further action.
27 | - : Your issue is not specific enough, or there is no clear action that we can take. Please clarify and refine your issue.
28 | - : Please [create a plunkr](http://bit.ly/UIR-Plunk)
29 | - : We suspect your issue is really a help request, or could be answered by the community. Please ask your question on [StackOverflow](http://stackoverflow.com/questions/ask?tags=angularjs,angular-ui-router). If you determine that is an actual issue, please explain why.
30 |
31 | If your issue gets labeled with purple label, no further action will be taken until you respond to the label appropriately.
32 |
33 | # Contribute
34 |
35 | **(1)** See the **[Developing](#developing)** section below, to get the development version of UI-Router up and running on your local machine.
36 |
37 | **(2)** Check out the [roadmap](https://github.com/angular-ui/ui-router/milestones) to see where the project is headed, and if your feature idea fits with where we're headed.
38 |
39 | **(3)** If you're not sure, [open an RFC](https://github.com/angular-ui/ui-router/issues/new?title=RFC:%20My%20idea) to get some feedback on your idea.
40 |
41 | **(4)** Finally, commit some code and open a pull request. Code & commits should abide by the following rules:
42 |
43 | - *Always* have test coverage for new features (or regression tests for bug fixes), and *never* break existing tests
44 | - Commits should represent one logical change each; if a feature goes through multiple iterations, squash your commits down to one
45 | - Make sure to follow the [Angular commit message format](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format) so your change will appear in the changelog of the next release.
46 | - Changes should always respect the coding style of the project
47 |
48 |
49 |
50 | # Developing
51 |
52 | UI-Router uses npm
and Rollup
.
53 |
54 | ## Fetch the source code
55 |
56 | The code for Angular UI-Router is split into two source repositories:
57 |
58 | * [UI-Router Core](https://github.com/ui-router/core) (`@uirouter/core` on npm)
59 | * [UI-Router for Angular 1](https://github.com/angular-ui/ui-router) (`@ui-router/angularjs` on npm)
60 |
61 | Clone both repositories into directories next to each other.
62 |
63 | ```
64 | mkdir uirouter
65 | cd uirouter
66 | git clone https://github.com/angular-ui/ui-router.git angularjs
67 | git clone https://github.com/ui-router/core.git core
68 | ```
69 |
70 | ## Install dependencies
71 |
72 | Use `npm` to install the development dependencies for each repository.
73 |
74 | ```
75 | cd core
76 | npm install
77 | cd ../angularjs
78 | npm install
79 | cd ..
80 | ```
81 |
82 | ## Link the directories
83 |
84 | This step is necessary if you need to modify any code in `@uirouter/core`.
85 | Using `npm`, link `@uirouter/core` into `@uirouter/angularjs`
86 |
87 | ```
88 | cd core
89 | npm link
90 | cd ../angularjs
91 | npm link '@uirouter/core'
92 | ```
93 |
94 | After executing these steps, `@uirouter/angularjs` will be depend on your local copy of `@uirouter/core` instead of the version listed in `package.json`.
95 |
96 | ## Develop
97 |
98 | These scripts may be run in the `angularjs` directory:
99 |
100 | * `npm run build`: Compiles TypeScript source
101 | * `npm run package`: Compiles TypeScript source and creates the Rollup bundles.
102 | * `npm test`: Runs the test suite (against Angular 1.2 through 1.5).
103 | * `npm run watch`: Continuously compiles the source and runs the test suite (when either source or tests change).
104 |
105 | Scripts of the same name (in the `core` directory) can be used.
106 |
107 | * `npm run build`: Compiles `@uirouter/core` TypeScript source
108 | * `npm test`: Runs the `@uirouter/core` test suite
109 | * `npm run watch`: Continuously compiles the source and runs the `@uirouter/core` test suite (when core source or tests change).
110 |
111 | If you've followed the [linking instructions](#link-the-directories), it's useful to run both
112 | `npm run watch` tasks (each task from `@uirouter/core` *and* `@uirouter/angularjs`).
113 | This ensures that changes to either `@uirouter/core` and `@uirouter/angularjs` compile successfully and are run against their test suites.
114 |
--------------------------------------------------------------------------------
/DOCS.md:
--------------------------------------------------------------------------------
1 | # [UI Router for Angular 1](https://ui-router.github.io/ng1/docs/latest)
2 |
3 | #### The de-facto solution to flexible routing in angular 1
4 |
5 |
6 |
7 |
8 |
9 | [](https://travis-ci.org/angular-ui/ui-router)
10 |
11 |
12 |
13 |
14 | Angular UI-Router is a client-side [Single Page Application](https://en.wikipedia.org/wiki/Single-page_application)
15 | routing framework for [AngularJS](http://angularjs.org).
16 |
17 | **[View on Github](http://github.com/angular-ui/ui-router) |**
18 | **[Tutorials](https://ui-router.github.io/ng1/tutorials/)** |
19 | **[Guides](https://ui-router.github.io/guide) |**
20 | **[Sample App](http://ui-router.github.io/resources/sampleapp/) |**
21 | **[Wiki](https://github.com/angular-ui/ui-router/wiki) |**
22 | **[FAQ](https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions)**
23 |
24 | #### API Documentation Organization
25 |
26 | The documentation is arranged according to API concern, such as `url`, `resolve`, and `core`.
27 | For a list of services and objects that can be injectable, see the [`injectables` section](./injectables.html).
28 |
29 | By default, only the public UI-Router API is shown.
30 | To view both public API and the internal APIs, check the "Internal UI-Router API" checkbox.
31 |
32 | #### Typescript
33 |
34 | UI-Router is written in Typescript.
35 | The API documentation is generated using [TypeDoc](https://github.com/TypeStrong/typedoc).
36 | The documentation reflects the Typescript classes, interfaces, and parameter types information embedded in the source code.
37 |
38 | #### Contributing
39 |
40 | Angular UI-Router depends on the framework agnostic `@uirouter/core`.
41 | To contribute to the documentation, please submit a PR to either
42 | [@uirouter/angularjs](http://github.com/angular-ui/ui-router)
43 | or
44 | [@uirouter/core](http://github.com/ui-router/core).
45 | To find where a specific piece of documentation is written, follow the links that say:
46 | > _Defined in ui-router/somedir/somefile.ts_
47 |
48 |
49 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | This issue tracker is for Bug Reports and Feature Requests only.
2 |
3 | Please direct requests for help to StackOverflow.
4 | See http://bit.ly/UIR-SOF for details.
5 |
6 | This is a (check one box):
7 |
8 | - [ ] Bug Report
9 | - [ ] Feature Request
10 | - [ ] General Query
11 |
12 | My version of UI-Router is: (type version)
13 |
14 |
15 |
16 | # Bug Report
17 |
18 | #### Current Behavior:
19 |
20 | (type current behavior here)
21 |
22 | #### Expected Behavior:
23 |
24 | (type expected behavior here)
25 |
26 | #### Link to Plunker or stackblitz that reproduces the issue:
27 |
28 | ( if you want a response to your issue, provide a way to reproduce it )
29 | ( http://bit.ly/UIR-Plunk1 )
30 | ( https://stackblitz.com/edit/ui-router-angularjs )
31 |
32 |
33 |
34 |
35 | # Feature Request
36 |
37 | (type feature request here)
38 |
39 |
40 |
41 |
42 | # General Query
43 |
44 | Please direct general implementation questions to StackOverflow:
45 | http://stackoverflow.com/questions/ask?tags=angularjs,angular-ui-router
46 |
47 | Please review the Sample Application which highlights common approaches:
48 | https://github.com/ui-router/sample-app-angularjs
49 |
50 | - [ ] I have already asked my question on StackOverflow and nobody could answer the question
51 |
52 | - [ ] I have already reviewed the sample application for examples of common approaches
53 |
54 | - [ ] I believe my question can only be answered by the UI-Router maintainers
55 |
56 |
57 | (type general query here)
58 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2013-2018 The AngularUI Team, Karsten Sperling
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AngularUI Router [](https://github.com/angular-ui/ui-router/actions?query=workflow%3A%22CI%3A+UIRouter+for+AngularJS%22)
2 |
3 | **Note: this is the Angular 1.x source for UI-Router version 1.x. If you are looking for the source for UI-Router
4 | version 0.x, it can be found [here](https://github.com/angular-ui/ui-router/tree/legacy)**
5 |
6 | ---
7 |
8 |
9 | #### The de-facto solution to flexible routing in angular
10 | ---
11 | **[Tutorials](https://ui-router.github.io/tutorials/)** |
12 | **[API Docs](https://ui-router.github.io/docs/latest/)** |
13 | **[Download stable](http://unpkg.com/@uirouter/angularjs@latest/release/angular-ui-router.js)** (or **[Minified](http://unpkg.com/@uirouter/angularjs@latest/release/angular-ui-router.min.js)**) **|**
14 | **[Guide](https://ui-router.github.io/guide/) |**
15 | **[Sample App](http://ui-router.github.io/resources/sampleapp/) |**
16 | **[FAQ](https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions) |**
17 | **[Report an Issue](https://github.com/angular-ui/ui-router/blob/master/CONTRIBUTING.md#report-an-issue) |**
18 | **[Contribute](https://github.com/angular-ui/ui-router/blob/master/CONTRIBUTING.md#contribute) |**
19 | **[Help!](http://stackoverflow.com/questions/ask?tags=angularjs,angular-ui-router) |**
20 |
21 | ---
22 |
23 | Angular UI-Router is a client-side [Single Page Application](https://en.wikipedia.org/wiki/Single-page_application)
24 | routing framework for [AngularJS](http://angularjs.org).
25 |
26 | Routing frameworks for SPAs update the browser's URL as the user navigates through the app. Conversely, this allows
27 | changes to the browser's URL to drive navigation through the app, thus allowing the user to create a bookmark to a
28 | location deep within the SPA.
29 |
30 | UI-Router applications are modeled as a hierarchical tree of states. UI-Router provides a
31 | [*state machine*](https://en.wikipedia.org/wiki/Finite-state_machine) to manage the transitions between those
32 | application states in a transaction-like manner.
33 |
34 | ## Get Started
35 |
36 |
37 | - [UI-Router for Angular 1](https://ui-router.github.io/ng1)
38 | - [UI-Router for Angular 2](https://ui-router.github.io/ng2)
39 | - [UI-Router for React](https://ui-router.github.io/react)
40 |
41 | ## Resources
42 |
43 | * [In-Depth Guide](https://github.com/angular-ui/ui-router/wiki)
44 | * [Slides comparing ngRoute to ui-router](http://slid.es/timkindberg/ui-router#/)
45 | * [UI-Router Extras / Addons for legacy (0.x)](http://christopherthielen.github.io/ui-router-extras/#/home) (@christopherthielen)
46 |
47 | ### Videos
48 |
49 | * [Introduction Video](https://egghead.io/lessons/angularjs-introduction-ui-router) (egghead.io)
50 | * [Tim Kindberg on Angular UI-Router](https://www.youtube.com/watch?v=lBqiZSemrqg)
51 | * [Activating States](https://egghead.io/lessons/angularjs-ui-router-activating-states) (egghead.io)
52 | * [Learn Angular.js using UI-Router](http://youtu.be/QETUuZ27N0w) (LearnCode.academy)
53 |
54 | ## Reporting issues and Contributing
55 |
56 | Please read our [Contributor guidelines](CONTRIBUTING.md) before reporting an issue or creating a pull request.
57 |
--------------------------------------------------------------------------------
/artifacts.json:
--------------------------------------------------------------------------------
1 | {
2 | "ARTIFACTS": ["lib", "lib-esm", "release", "package.json"]
3 | }
4 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-ui-router",
3 | "description": "State-based routing for AngularJS",
4 | "license": "MIT",
5 | "main": "./release/angular-ui-router.js",
6 | "dependencies": {
7 | "angular": ">= 1.2.0"
8 | },
9 | "ignore": [
10 | "**/.*",
11 | "**/tsconfig.json",
12 | "**/tsconfig.typedoc.json",
13 | "**/webpack.config.js",
14 | "**/node_modules",
15 | "package.json",
16 | "scripts",
17 | "test",
18 | "src"
19 | ],
20 | "version": "1.0.30"
21 | }
--------------------------------------------------------------------------------
/downstream_projects.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript": {
3 | "typescript3.9": "./test/typescript/3.9"
4 | },
5 | "sample-app-angularjs": "https://github.com/ui-router/sample-app-angularjs.git"
6 | }
7 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | const NG = process.env.NG || '1.7';
2 |
3 | console.log(`Testing with AngularJS ${NG}`);
4 |
5 | module.exports = {
6 | preset: 'ts-jest',
7 | testEnvironment: 'jsdom',
8 | roots: ['src', 'test'],
9 | testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', '**/?*Spec.[jt]s'],
10 | setupFilesAfterEnv: ['./test/jest.init.ts'],
11 | moduleNameMapper: {
12 | '^angular$': '/test/angular/jest-angular.js',
13 | '^jest-angular-import$': `/test/angular/${NG}/angular.js`,
14 | '^angular-animate$': `/test/angular/${NG}/angular-animate.js`,
15 | '^angular-mocks$': `/test/angular/${NG}/angular-mocks.js`,
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file
2 | var karma = require('karma');
3 | var ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
4 | var DEFAULT_NG_VERSION = '1.6';
5 |
6 | /**
7 | * This returns a Karma 'files configuration'.
8 | * http://karma-runner.github.io/0.8/config/files.html
9 | *
10 | * Specifies which files can be served by the Karma web server
11 | *
12 | * included: true -- files that are always served to the browser (like
13 | * ```
14 | * and also make sure you depend on the `ui.router.state.events` angular module, e.g.,
15 | * ```
16 | * angular.module("myApplication", ['ui.router', 'ui.router.state.events']
17 | * ```
18 | *
19 | * @publicapi @module ng1_state_events
20 | */ /** */
21 | import { ng as angular } from '../angular';
22 | import { IScope, IAngularEvent, IServiceProviderFactory } from 'angular';
23 | import {
24 | Obj,
25 | TargetState,
26 | StateService,
27 | Transition,
28 | TransitionService,
29 | UrlRouter,
30 | HookResult,
31 | UIInjector,
32 | } from '@uirouter/core';
33 | import { StateProvider } from '../stateProvider';
34 |
35 | /**
36 | * An event broadcast on `$rootScope` when the state transition **begins**.
37 | *
38 | * ### Deprecation warning: use [[TransitionService.onStart]] instead
39 | *
40 | * You can use `event.preventDefault()`
41 | * to prevent the transition from happening and then the transition promise will be
42 | * rejected with a `'transition prevented'` value.
43 | *
44 | * Additional arguments to the event handler are provided:
45 | * - `toState`: the Transition Target state
46 | * - `toParams`: the Transition Target Params
47 | * - `fromState`: the state the transition is coming from
48 | * - `fromParams`: the parameters from the state the transition is coming from
49 | * - `options`: any Transition Options
50 | * - `$transition$`: the [[Transition]]
51 | *
52 | * #### Example:
53 | * ```js
54 | * $rootScope.$on('$stateChangeStart', function(event, transition) {
55 | * event.preventDefault();
56 | * // transitionTo() promise will be rejected with
57 | * // a 'transition prevented' error
58 | * })
59 | * ```
60 | *
61 | * @event $stateChangeStart
62 | * @deprecated
63 | */
64 | export let $stateChangeStart: IAngularEvent;
65 |
66 | /**
67 | * An event broadcast on `$rootScope` if a transition is **cancelled**.
68 | *
69 | * ### Deprecation warning: use [[TransitionService.onStart]] instead
70 | *
71 | * Additional arguments to the event handler are provided:
72 | * - `toState`: the Transition Target state
73 | * - `toParams`: the Transition Target Params
74 | * - `fromState`: the state the transition is coming from
75 | * - `fromParams`: the parameters from the state the transition is coming from
76 | * - `options`: any Transition Options
77 | * - `$transition$`: the [[Transition]] that was cancelled
78 | *
79 | * @event $stateChangeCancel
80 | * @deprecated
81 | */
82 | export let $stateChangeCancel: IAngularEvent;
83 |
84 | /**
85 | * An event broadcast on `$rootScope` once the state transition is **complete**.
86 | *
87 | * ### Deprecation warning: use [[TransitionService.onStart]] and [[Transition.promise]], or [[Transition.onSuccess]]
88 | *
89 | * Additional arguments to the event handler are provided:
90 | * - `toState`: the Transition Target state
91 | * - `toParams`: the Transition Target Params
92 | * - `fromState`: the state the transition is coming from
93 | * - `fromParams`: the parameters from the state the transition is coming from
94 | * - `options`: any Transition Options
95 | * - `$transition$`: the [[Transition]] that just succeeded
96 | *
97 | * @event $stateChangeSuccess
98 | * @deprecated
99 | */
100 | export let $stateChangeSuccess: IAngularEvent;
101 |
102 | /**
103 | * An event broadcast on `$rootScope` when an **error occurs** during transition.
104 | *
105 | * ### Deprecation warning: use [[TransitionService.onStart]] and [[Transition.promise]], or [[Transition.onError]]
106 | *
107 | * It's important to note that if you
108 | * have any errors in your resolve functions (javascript errors, non-existent services, etc)
109 | * they will not throw traditionally. You must listen for this $stateChangeError event to
110 | * catch **ALL** errors.
111 | *
112 | * Additional arguments to the event handler are provided:
113 | * - `toState`: the Transition Target state
114 | * - `toParams`: the Transition Target Params
115 | * - `fromState`: the state the transition is coming from
116 | * - `fromParams`: the parameters from the state the transition is coming from
117 | * - `error`: The reason the transition errored.
118 | * - `options`: any Transition Options
119 | * - `$transition$`: the [[Transition]] that errored
120 | *
121 | * @event $stateChangeError
122 | * @deprecated
123 | */
124 | export let $stateChangeError: IAngularEvent;
125 |
126 | /**
127 | * An event broadcast on `$rootScope` when a requested state **cannot be found** using the provided state name.
128 | *
129 | * ### Deprecation warning: use [[StateService.onInvalid]] instead
130 | *
131 | * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
132 | * lazy-loading the unfound state). A `TargetState` object is passed to the listener handler,
133 | * you can see its properties in the example. You can use `event.preventDefault()` to abort the
134 | * transition and the promise returned from `transitionTo()` will be rejected with a
135 | * `'transition aborted'` error.
136 | *
137 | * Additional arguments to the event handler are provided:
138 | * - `unfoundState` Unfound State information. Contains: `to, toParams, options` properties.
139 | * - `fromState`: the state the transition is coming from
140 | * - `fromParams`: the parameters from the state the transition is coming from
141 | * - `options`: any Transition Options
142 | *
143 | * #### Example:
144 | * ```js
145 | * // somewhere, assume lazy.state has not been defined
146 | * $state.go("lazy.state", { a: 1, b: 2 }, { inherit: false });
147 | *
148 | * // somewhere else
149 | * $scope.$on('$stateNotFound', function(event, transition) {
150 | * function(event, unfoundState, fromState, fromParams){
151 | * console.log(unfoundState.to); // "lazy.state"
152 | * console.log(unfoundState.toParams); // {a:1, b:2}
153 | * console.log(unfoundState.options); // {inherit:false} + default options
154 | * });
155 | * ```
156 | *
157 | * @event $stateNotFound
158 | * @deprecated
159 | */
160 | export let $stateNotFound: IAngularEvent;
161 |
162 | (function () {
163 | const { isFunction, isString } = angular;
164 |
165 | function applyPairs(memo: Obj, keyValTuple: any[]) {
166 | let key: string, value: any;
167 | if (Array.isArray(keyValTuple)) [key, value] = keyValTuple;
168 | if (!isString(key)) throw new Error('invalid parameters to applyPairs');
169 | memo[key] = value;
170 | return memo;
171 | }
172 |
173 | function stateChangeStartHandler($transition$: Transition) {
174 | if (!$transition$.options().notify || !$transition$.valid() || $transition$.ignored()) return;
175 |
176 | const $injector = $transition$.injector();
177 | const $stateEvents = $injector.get('$stateEvents');
178 | const $rootScope = $injector.get('$rootScope');
179 | const $state = $injector.get('$state');
180 | const $urlRouter = $injector.get('$urlRouter');
181 |
182 | const enabledEvents = $stateEvents.provider.enabled();
183 |
184 | const toParams = $transition$.params('to');
185 | const fromParams = $transition$.params('from');
186 |
187 | if (enabledEvents.$stateChangeSuccess) {
188 | const startEvent = $rootScope.$broadcast(
189 | '$stateChangeStart',
190 | $transition$.to(),
191 | toParams,
192 | $transition$.from(),
193 | fromParams,
194 | $transition$.options(),
195 | $transition$
196 | );
197 |
198 | if (startEvent.defaultPrevented) {
199 | if (enabledEvents.$stateChangeCancel) {
200 | $rootScope.$broadcast(
201 | '$stateChangeCancel',
202 | $transition$.to(),
203 | toParams,
204 | $transition$.from(),
205 | fromParams,
206 | $transition$.options(),
207 | $transition$
208 | );
209 | }
210 | // Don't update and resync url if there's been a new transition started. see issue #2238, #600
211 | if ($state.transition == null) $urlRouter.update();
212 | return false;
213 | }
214 |
215 | // right after global state is updated
216 | const successOpts = { priority: 9999 };
217 | $transition$.onSuccess(
218 | {},
219 | function () {
220 | $rootScope.$broadcast(
221 | '$stateChangeSuccess',
222 | $transition$.to(),
223 | toParams,
224 | $transition$.from(),
225 | fromParams,
226 | $transition$.options(),
227 | $transition$
228 | );
229 | },
230 | successOpts
231 | );
232 | }
233 |
234 | if (enabledEvents.$stateChangeError) {
235 | $transition$.promise['catch'](function (error) {
236 | if (error && (error.type === 2 /* RejectType.SUPERSEDED */ || error.type === 3) /* RejectType.ABORTED */)
237 | return;
238 |
239 | const evt = $rootScope.$broadcast(
240 | '$stateChangeError',
241 | $transition$.to(),
242 | toParams,
243 | $transition$.from(),
244 | fromParams,
245 | error,
246 | $transition$.options(),
247 | $transition$
248 | );
249 |
250 | if (!evt.defaultPrevented) {
251 | $urlRouter.update();
252 | }
253 | });
254 | }
255 | }
256 |
257 | stateNotFoundHandler.$inject = ['$to$', '$from$', '$state', '$rootScope', '$urlRouter'];
258 | function stateNotFoundHandler($to$: TargetState, $from$: TargetState, injector: UIInjector): HookResult {
259 | const $state: StateService = injector.get('$state');
260 | const $rootScope: IScope = injector.get('$rootScope');
261 | const $urlRouter: UrlRouter = injector.get('$urlRouter');
262 |
263 | interface StateNotFoundEvent extends IAngularEvent {
264 | retry: Promise;
265 | }
266 |
267 | const redirect = { to: $to$.identifier(), toParams: $to$.params(), options: $to$.options() };
268 | const e = $rootScope.$broadcast('$stateNotFound', redirect, $from$.state(), $from$.params());
269 |
270 | if (e.defaultPrevented || e.retry) $urlRouter.update();
271 |
272 | function redirectFn(): TargetState {
273 | return $state.target(redirect.to, redirect.toParams, redirect.options);
274 | }
275 |
276 | if (e.defaultPrevented) {
277 | return false;
278 | } else if (e.retry || !!$state.get(redirect.to)) {
279 | return e.retry && isFunction(e.retry.then) ? e.retry.then(redirectFn) : redirectFn();
280 | }
281 | }
282 |
283 | $StateEventsProvider.$inject = ['$stateProvider'];
284 | function $StateEventsProvider($stateProvider: StateProvider) {
285 | $StateEventsProvider.prototype.instance = this;
286 |
287 | interface IEventsToggle {
288 | [key: string]: boolean;
289 | $stateChangeStart: boolean;
290 | $stateNotFound: boolean;
291 | $stateChangeSuccess: boolean;
292 | $stateChangeError: boolean;
293 | }
294 |
295 | let runtime = false;
296 | const allEvents = ['$stateChangeStart', '$stateNotFound', '$stateChangeSuccess', '$stateChangeError'];
297 | const enabledStateEvents = allEvents.map((e) => [e, true]).reduce(applyPairs, {});
298 |
299 | function assertNotRuntime() {
300 | if (runtime) throw new Error('Cannot enable events at runtime (use $stateEventsProvider');
301 | }
302 |
303 | /**
304 | * Enables the deprecated UI-Router 0.2.x State Events
305 | * [ '$stateChangeStart', '$stateNotFound', '$stateChangeSuccess', '$stateChangeError' ]
306 | */
307 | this.enable = function (...events: string[]) {
308 | assertNotRuntime();
309 | if (!events || !events.length) events = allEvents;
310 | events.forEach((event) => (enabledStateEvents[event] = true));
311 | };
312 |
313 | /**
314 | * Disables the deprecated UI-Router 0.2.x State Events
315 | * [ '$stateChangeStart', '$stateNotFound', '$stateChangeSuccess', '$stateChangeError' ]
316 | */
317 | this.disable = function (...events: string[]) {
318 | assertNotRuntime();
319 | if (!events || !events.length) events = allEvents;
320 | events.forEach((event) => delete enabledStateEvents[event]);
321 | };
322 |
323 | this.enabled = () => enabledStateEvents;
324 |
325 | this.$get = $get;
326 | $get.$inject = ['$transitions'];
327 | function $get($transitions: TransitionService) {
328 | runtime = true;
329 |
330 | if (enabledStateEvents['$stateNotFound']) $stateProvider.onInvalid(stateNotFoundHandler);
331 | if (enabledStateEvents.$stateChangeStart) $transitions.onBefore({}, stateChangeStartHandler, { priority: 1000 });
332 |
333 | return {
334 | provider: $StateEventsProvider.prototype.instance,
335 | };
336 | }
337 | }
338 |
339 | angular
340 | .module('ui.router.state.events', ['ui.router.state'])
341 | .provider('$stateEvents', ($StateEventsProvider as any) as IServiceProviderFactory)
342 | .run([
343 | '$stateEvents',
344 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
345 | function ($stateEvents: any) {
346 | /* Invokes $get() */
347 | },
348 | ]);
349 | })();
350 |
--------------------------------------------------------------------------------
/src/locationServices.ts:
--------------------------------------------------------------------------------
1 | /** @publicapi @module ng1 */ /** */
2 | import { LocationConfig, LocationServices, UIRouter, ParamType, isDefined } from '@uirouter/core';
3 | import { val, createProxyFunctions, removeFrom, isObject } from '@uirouter/core';
4 | import { ILocationService, ILocationProvider, IWindowService } from 'angular';
5 |
6 | /**
7 | * Implements UI-Router LocationServices and LocationConfig using Angular 1's $location service
8 | * @internalapi
9 | */
10 | export class Ng1LocationServices implements LocationConfig, LocationServices {
11 | private $locationProvider: ILocationProvider;
12 | private $location: ILocationService;
13 | private $sniffer: any;
14 | private $browser: any;
15 | private $window: IWindowService;
16 |
17 | path;
18 | search;
19 | hash;
20 | hashPrefix;
21 | port;
22 | protocol;
23 | host;
24 |
25 | private _baseHref: string;
26 |
27 | // .onChange() registry
28 | private _urlListeners: Function[] = [];
29 |
30 | /**
31 | * Applys ng1-specific path parameter encoding
32 | *
33 | * The Angular 1 `$location` service is a bit weird.
34 | * It doesn't allow slashes to be encoded/decoded bi-directionally.
35 | *
36 | * See the writeup at https://github.com/angular-ui/ui-router/issues/2598
37 | *
38 | * This code patches the `path` parameter type so it encoded/decodes slashes as ~2F
39 | *
40 | * @param router
41 | */
42 | static monkeyPatchPathParameterType(router: UIRouter) {
43 | const pathType: ParamType = router.urlMatcherFactory.type('path');
44 |
45 | pathType.encode = (x: any) =>
46 | x != null ? x.toString().replace(/(~|\/)/g, (m) => ({ '~': '~~', '/': '~2F' }[m])) : x;
47 |
48 | pathType.decode = (x: string) =>
49 | x != null ? x.toString().replace(/(~~|~2F)/g, (m) => ({ '~~': '~', '~2F': '/' }[m])) : x;
50 | }
51 |
52 | // eslint-disable-next-line @typescript-eslint/no-empty-function
53 | dispose() {}
54 |
55 | constructor($locationProvider: ILocationProvider) {
56 | this.$locationProvider = $locationProvider;
57 | const _lp = val($locationProvider);
58 | createProxyFunctions(_lp, this, _lp, ['hashPrefix']);
59 | }
60 |
61 | onChange(callback: Function) {
62 | this._urlListeners.push(callback);
63 | return () => removeFrom(this._urlListeners)(callback);
64 | }
65 |
66 | html5Mode() {
67 | let html5Mode: any = this.$locationProvider.html5Mode();
68 | html5Mode = isObject(html5Mode) ? html5Mode.enabled : html5Mode;
69 | return html5Mode && this.$sniffer.history;
70 | }
71 |
72 | baseHref() {
73 | return this._baseHref || (this._baseHref = this.$browser.baseHref() || this.$window.location.pathname);
74 | }
75 |
76 | url(newUrl?: string, replace = false, state?) {
77 | if (isDefined(newUrl)) this.$location.url(newUrl);
78 | if (replace) this.$location.replace();
79 | if (state) this.$location.state(state);
80 | return this.$location.url();
81 | }
82 |
83 | _runtimeServices($rootScope, $location: ILocationService, $sniffer, $browser, $window: IWindowService) {
84 | this.$location = $location;
85 | this.$sniffer = $sniffer;
86 | this.$browser = $browser;
87 | this.$window = $window;
88 |
89 | // Bind $locationChangeSuccess to the listeners registered in LocationService.onChange
90 | $rootScope.$on('$locationChangeSuccess', (evt) => this._urlListeners.forEach((fn) => fn(evt)));
91 | const _loc = val($location);
92 |
93 | // Bind these LocationService functions to $location
94 | createProxyFunctions(_loc, this, _loc, ['replace', 'path', 'search', 'hash']);
95 | // Bind these LocationConfig functions to $location
96 | createProxyFunctions(_loc, this, _loc, ['port', 'protocol', 'host']);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/services.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-empty-function */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | /**
4 | * # Angular 1 types
5 | *
6 | * UI-Router core provides various Typescript types which you can use for code completion and validating parameter values, etc.
7 | * The customizations to the core types for Angular UI-Router are documented here.
8 | *
9 | * The optional [[$resolve]] service is also documented here.
10 | *
11 | * @preferred @publicapi @module ng1
12 | */ /** */
13 | import { ng as angular } from './angular';
14 | import {
15 | IRootScopeService,
16 | IQService,
17 | ILocationService,
18 | ILocationProvider,
19 | IHttpService,
20 | ITemplateCacheService,
21 | } from 'angular';
22 | import {
23 | services,
24 | applyPairs,
25 | isString,
26 | trace,
27 | extend,
28 | UIRouter,
29 | StateService,
30 | UrlRouter,
31 | UrlMatcherFactory,
32 | ResolveContext,
33 | unnestR,
34 | TypedMap,
35 | } from '@uirouter/core';
36 | import { ng1ViewsBuilder, getNg1ViewConfigFactory } from './statebuilders/views';
37 | import { TemplateFactory } from './templateFactory';
38 | import { StateProvider } from './stateProvider';
39 | import { getStateHookBuilder } from './statebuilders/onEnterExitRetain';
40 | import { Ng1LocationServices } from './locationServices';
41 | import { UrlRouterProvider } from './urlRouterProvider';
42 | import IInjectorService = angular.auto.IInjectorService;
43 |
44 | angular.module('ui.router.angular1', []);
45 | const mod_init = angular.module('ui.router.init', ['ng']);
46 | const mod_util = angular.module('ui.router.util', ['ui.router.init']);
47 | const mod_rtr = angular.module('ui.router.router', ['ui.router.util']);
48 | const mod_state = angular.module('ui.router.state', ['ui.router.router', 'ui.router.util', 'ui.router.angular1']);
49 | const mod_main = angular.module('ui.router', ['ui.router.init', 'ui.router.state', 'ui.router.angular1']);
50 | const mod_cmpt = angular.module('ui.router.compat', ['ui.router']);
51 |
52 | declare module '@uirouter/core/lib/router' {
53 | interface UIRouter {
54 | /** @hidden */
55 | stateProvider: StateProvider;
56 | /** @hidden */
57 | urlRouterProvider: UrlRouterProvider;
58 | }
59 | }
60 |
61 | let router: UIRouter = null;
62 |
63 | $uiRouterProvider.$inject = ['$locationProvider'];
64 | /** This angular 1 provider instantiates a Router and exposes its services via the angular injector */
65 | function $uiRouterProvider($locationProvider: ILocationProvider) {
66 | // Create a new instance of the Router when the $uiRouterProvider is initialized
67 | router = this.router = new UIRouter();
68 | router.stateProvider = new StateProvider(router.stateRegistry, router.stateService);
69 |
70 | // Apply ng1 specific StateBuilder code for `views`, `resolve`, and `onExit/Retain/Enter` properties
71 | router.stateRegistry.decorator('views', ng1ViewsBuilder);
72 | router.stateRegistry.decorator('onExit', getStateHookBuilder('onExit'));
73 | router.stateRegistry.decorator('onRetain', getStateHookBuilder('onRetain'));
74 | router.stateRegistry.decorator('onEnter', getStateHookBuilder('onEnter'));
75 |
76 | router.viewService._pluginapi._viewConfigFactory('ng1', getNg1ViewConfigFactory());
77 |
78 | // Disable decoding of params by UrlMatcherFactory because $location already handles this
79 | router.urlService.config._decodeParams = false;
80 |
81 | const ng1LocationService = (router.locationService = router.locationConfig = new Ng1LocationServices(
82 | $locationProvider
83 | ));
84 |
85 | Ng1LocationServices.monkeyPatchPathParameterType(router);
86 |
87 | // backwards compat: also expose router instance as $uiRouterProvider.router
88 | router['router'] = router;
89 | router['$get'] = $get;
90 | $get.$inject = ['$location', '$browser', '$window', '$sniffer', '$rootScope', '$http', '$templateCache'];
91 | function $get(
92 | $location: ILocationService,
93 | $browser: any,
94 | $window: any,
95 | $sniffer: any,
96 | $rootScope: ng.IScope,
97 | $http: IHttpService,
98 | $templateCache: ITemplateCacheService
99 | ) {
100 | ng1LocationService._runtimeServices($rootScope, $location, $sniffer, $browser, $window);
101 | delete router['router'];
102 | delete router['$get'];
103 | return router;
104 | }
105 | return router;
106 | }
107 |
108 | const getProviderFor = (serviceName) => [
109 | '$uiRouterProvider',
110 | ($urp) => {
111 | const service = $urp.router[serviceName];
112 | service['$get'] = () => service;
113 | return service;
114 | },
115 | ];
116 |
117 | // This effectively calls $get() on `$uiRouterProvider` to trigger init (when ng enters runtime)
118 | runBlock.$inject = ['$injector', '$q', '$uiRouter'];
119 | function runBlock($injector: IInjectorService, $q: IQService, $uiRouter: UIRouter) {
120 | services.$injector = $injector;
121 | services.$q = $q;
122 |
123 | // https://github.com/angular-ui/ui-router/issues/3678
124 | if (!Object.prototype.hasOwnProperty.call($injector, 'strictDi')) {
125 | try {
126 | $injector.invoke(function (checkStrictDi) {});
127 | } catch (error) {
128 | $injector.strictDi = !!/strict mode/.exec(error && error.toString());
129 | }
130 | }
131 |
132 | // The $injector is now available.
133 | // Find any resolvables that had dependency annotation deferred
134 | $uiRouter.stateRegistry
135 | .get()
136 | .map((x) => x.$$state().resolvables)
137 | .reduce(unnestR, [])
138 | .filter((x) => x.deps === 'deferred')
139 | .forEach((resolvable) => (resolvable.deps = $injector.annotate(resolvable.resolveFn, $injector.strictDi)));
140 | }
141 |
142 | // $urlRouter service and $urlRouterProvider
143 | const getUrlRouterProvider = (uiRouter: UIRouter) => (uiRouter.urlRouterProvider = new UrlRouterProvider(uiRouter));
144 |
145 | // $state service and $stateProvider
146 | // $urlRouter service and $urlRouterProvider
147 | const getStateProvider = () => extend(router.stateProvider, { $get: () => router.stateService });
148 |
149 | watchDigests.$inject = ['$rootScope'];
150 | export function watchDigests($rootScope: IRootScopeService) {
151 | $rootScope.$watch(function () {
152 | trace.approximateDigests++;
153 | });
154 | }
155 |
156 | mod_init.provider('$uiRouter', $uiRouterProvider);
157 | mod_rtr.provider('$urlRouter', ['$uiRouterProvider', getUrlRouterProvider]);
158 | mod_util.provider('$urlService', getProviderFor('urlService'));
159 | mod_util.provider('$urlMatcherFactory', ['$uiRouterProvider', () => router.urlMatcherFactory]);
160 | mod_util.provider('$templateFactory', () => new TemplateFactory());
161 | mod_state.provider('$stateRegistry', getProviderFor('stateRegistry'));
162 | mod_state.provider('$uiRouterGlobals', getProviderFor('globals'));
163 | mod_state.provider('$transitions', getProviderFor('transitionService'));
164 | mod_state.provider('$state', ['$uiRouterProvider', getStateProvider]);
165 |
166 | mod_state.factory('$stateParams', ['$uiRouter', ($uiRouter: UIRouter) => $uiRouter.globals.params]);
167 | mod_main.factory('$view', () => router.viewService);
168 | mod_main.service('$trace', () => trace);
169 |
170 | mod_main.run(watchDigests);
171 | mod_util.run(['$urlMatcherFactory', function ($urlMatcherFactory: UrlMatcherFactory) {}]);
172 | mod_state.run(['$state', function ($state: StateService) {}]);
173 | mod_rtr.run(['$urlRouter', function ($urlRouter: UrlRouter) {}]);
174 | mod_init.run(runBlock);
175 |
176 | /** @hidden TODO: find a place to move this */
177 | export const getLocals = (ctx: ResolveContext): TypedMap => {
178 | const tokens = ctx.getTokens().filter(isString);
179 |
180 | const tuples = tokens.map((key) => {
181 | const resolvable = ctx.getResolvable(key);
182 | const waitPolicy = ctx.getPolicy(resolvable).async;
183 | return [key, waitPolicy === 'NOWAIT' ? resolvable.promise : resolvable.data];
184 | });
185 |
186 | return tuples.reduce(applyPairs, {});
187 | };
188 |
--------------------------------------------------------------------------------
/src/stateFilters.ts:
--------------------------------------------------------------------------------
1 | /** @publicapi @module ng1 */ /** */
2 |
3 | import { ng as angular } from './angular';
4 | import { Obj, StateService, StateOrName } from '@uirouter/core';
5 |
6 | /**
7 | * `isState` Filter: truthy if the current state is the parameter
8 | *
9 | * Translates to [[StateService.is]] `$state.is("stateName")`.
10 | *
11 | * #### Example:
12 | * ```html
13 | * show if state is 'stateName'
14 | * ```
15 | */
16 | $IsStateFilter.$inject = ['$state'];
17 | function $IsStateFilter($state: StateService) {
18 | const isFilter: any = function (state: StateOrName, params: Obj, options?: { relative?: StateOrName }) {
19 | return $state.is(state, params, options);
20 | };
21 | isFilter.$stateful = true;
22 | return isFilter;
23 | }
24 |
25 | /**
26 | * `includedByState` Filter: truthy if the current state includes the parameter
27 | *
28 | * Translates to [[StateService.includes]]` $state.is("fullOrPartialStateName")`.
29 | *
30 | * #### Example:
31 | * ```html
32 | * show if state includes 'fullOrPartialStateName'
33 | * ```
34 | */
35 | $IncludedByStateFilter.$inject = ['$state'];
36 | function $IncludedByStateFilter($state: StateService) {
37 | const includesFilter: any = function (state: StateOrName, params: Obj, options: { relative?: StateOrName }) {
38 | return $state.includes(state, params, options);
39 | };
40 | includesFilter.$stateful = true;
41 | return includesFilter;
42 | }
43 |
44 | angular.module('ui.router.state').filter('isState', $IsStateFilter).filter('includedByState', $IncludedByStateFilter);
45 |
46 | export { $IsStateFilter, $IncludedByStateFilter };
47 |
--------------------------------------------------------------------------------
/src/stateProvider.ts:
--------------------------------------------------------------------------------
1 | /** @publicapi @module ng1 */ /** */
2 | import {
3 | val,
4 | isObject,
5 | createProxyFunctions,
6 | BuilderFunction,
7 | StateRegistry,
8 | StateService,
9 | OnInvalidCallback,
10 | } from '@uirouter/core';
11 | import { Ng1StateDeclaration } from './interface';
12 |
13 | /**
14 | * The Angular 1 `StateProvider`
15 | *
16 | * The `$stateProvider` works similar to Angular's v1 router, but it focuses purely
17 | * on state.
18 | *
19 | * A state corresponds to a "place" in the application in terms of the overall UI and
20 | * navigation. A state describes (via the controller / template / view properties) what
21 | * the UI looks like and does at that place.
22 | *
23 | * States often have things in common, and the primary way of factoring out these
24 | * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
25 | * nested states.
26 | *
27 | * The `$stateProvider` provides interfaces to declare these states for your app.
28 | */
29 | export class StateProvider {
30 | constructor(private stateRegistry: StateRegistry, private stateService: StateService) {
31 | createProxyFunctions(val(StateProvider.prototype), this, val(this));
32 | }
33 |
34 | /**
35 | * Decorates states when they are registered
36 | *
37 | * Allows you to extend (carefully) or override (at your own peril) the
38 | * `stateBuilder` object used internally by [[StateRegistry]].
39 | * This can be used to add custom functionality to ui-router,
40 | * for example inferring templateUrl based on the state name.
41 | *
42 | * When passing only a name, it returns the current (original or decorated) builder
43 | * function that matches `name`.
44 | *
45 | * The builder functions that can be decorated are listed below. Though not all
46 | * necessarily have a good use case for decoration, that is up to you to decide.
47 | *
48 | * In addition, users can attach custom decorators, which will generate new
49 | * properties within the state's internal definition. There is currently no clear
50 | * use-case for this beyond accessing internal states (i.e. $state.$current),
51 | * however, expect this to become increasingly relevant as we introduce additional
52 | * meta-programming features.
53 | *
54 | * **Warning**: Decorators should not be interdependent because the order of
55 | * execution of the builder functions in non-deterministic. Builder functions
56 | * should only be dependent on the state definition object and super function.
57 | *
58 | *
59 | * Existing builder functions and current return values:
60 | *
61 | * - **parent** `{object}` - returns the parent state object.
62 | * - **data** `{object}` - returns state data, including any inherited data that is not
63 | * overridden by own values (if any).
64 | * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
65 | * or `null`.
66 | * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
67 | * navigable).
68 | * - **params** `{object}` - returns an array of state params that are ensured to
69 | * be a super-set of parent's params.
70 | * - **views** `{object}` - returns a views object where each key is an absolute view
71 | * name (i.e. "viewName@stateName") and each value is the config object
72 | * (template, controller) for the view. Even when you don't use the views object
73 | * explicitly on a state config, one is still created for you internally.
74 | * So by decorating this builder function you have access to decorating template
75 | * and controller properties.
76 | * - **ownParams** `{object}` - returns an array of params that belong to the state,
77 | * not including any params defined by ancestor states.
78 | * - **path** `{string}` - returns the full path from the root down to this state.
79 | * Needed for state activation.
80 | * - **includes** `{object}` - returns an object that includes every state that
81 | * would pass a `$state.includes()` test.
82 | *
83 | * #### Example:
84 | * Override the internal 'views' builder with a function that takes the state
85 | * definition, and a reference to the internal function being overridden:
86 | * ```js
87 | * $stateProvider.decorator('views', function (state, parent) {
88 | * let result = {},
89 | * views = parent(state);
90 | *
91 | * angular.forEach(views, function (config, name) {
92 | * let autoName = (state.name + '.' + name).replace('.', '/');
93 | * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
94 | * result[name] = config;
95 | * });
96 | * return result;
97 | * });
98 | *
99 | * $stateProvider.state('home', {
100 | * views: {
101 | * 'contact.list': { controller: 'ListController' },
102 | * 'contact.item': { controller: 'ItemController' }
103 | * }
104 | * });
105 | * ```
106 | *
107 | *
108 | * ```js
109 | * // Auto-populates list and item views with /partials/home/contact/list.html,
110 | * // and /partials/home/contact/item.html, respectively.
111 | * $state.go('home');
112 | * ```
113 | *
114 | * @param {string} name The name of the builder function to decorate.
115 | * @param {object} func A function that is responsible for decorating the original
116 | * builder function. The function receives two parameters:
117 | *
118 | * - `{object}` - state - The state config object.
119 | * - `{object}` - super - The original builder function.
120 | *
121 | * @return {object} $stateProvider - $stateProvider instance
122 | */
123 | decorator(name: string, func: BuilderFunction) {
124 | return this.stateRegistry.decorator(name, func) || this;
125 | }
126 |
127 | /**
128 | * Registers a state
129 | *
130 | * ### This is a passthrough to [[StateRegistry.register]].
131 | *
132 | * Registers a state configuration under a given state name.
133 | * The stateConfig object has the following acceptable properties.
134 | *
135 | *
136 | *
137 | * - **`template`** - {string|function=} - html template as a string or a function that returns
138 | * an html template as a string which should be used by the uiView directives. This property
139 | * takes precedence over templateUrl.
140 | *
141 | * If `template` is a function, it will be called with the following parameters:
142 | *
143 | * - {array.<object>} - state parameters extracted from the current $location.path() by
144 | * applying the current state
145 | *
146 | *
147 | *
148 | * - **`templateUrl`** - {string|function=} - path or function that returns a path to an html
149 | * template that should be used by uiView.
150 | *
151 | * If `templateUrl` is a function, it will be called with the following parameters:
152 | *
153 | * - {array.<object>} - state parameters extracted from the current $location.path() by
154 | * applying the current state
155 | *
156 | *
157 | *
158 | * - **`templateProvider`** - {function=} - Provider function that returns HTML content
159 | * string.
160 | *
161 | *
162 | *
163 | * - **`controller`** - {string|function=} - Controller fn that should be associated with newly
164 | * related scope or the name of a registered controller if passed as a string.
165 | *
166 | *
167 | *
168 | * - **`controllerProvider`** - {function=} - Injectable provider function that returns
169 | * the actual controller or string.
170 | *
171 | *
172 | *
173 | * - **`controllerAs`** – {string=} – A controller alias name. If present the controller will be
174 | * published to scope under the controllerAs name.
175 | *
176 | *
177 | *
178 | * - **`resolve`** - {object.<string, function>=} - An optional map of dependencies which
179 | * should be injected into the controller. If any of these dependencies are promises,
180 | * the router will wait for them all to be resolved or one to be rejected before the
181 | * controller is instantiated. If all the promises are resolved successfully, the values
182 | * of the resolved promises are injected and $stateChangeSuccess event is fired. If any
183 | * of the promises are rejected the $stateChangeError event is fired. The map object is:
184 | *
185 | * - key - {string}: name of dependency to be injected into controller
186 | * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
187 | * it is injected and return value it treated as dependency. If result is a promise, it is
188 | * resolved before its value is injected into controller.
189 | *
190 | *
191 | *
192 | * - **`url`** - {string=} - A url with optional parameters. When a state is navigated or
193 | * transitioned to, the `$stateParams` service will be populated with any
194 | * parameters that were passed.
195 | *
196 | *
197 | *
198 | * - **`params`** - {object=} - An array of parameter names or regular expressions. Only
199 | * use this within a state if you are not using url. Otherwise you can specify your
200 | * parameters within the url. When a state is navigated or transitioned to, the
201 | * $stateParams service will be populated with any parameters that were passed.
202 | *
203 | *
204 | *
205 | * - **`views`** - {object=} - Use the views property to set up multiple views or to target views
206 | * manually/explicitly.
207 | *
208 | *
209 | *
210 | * - **`abstract`** - {boolean=} - An abstract state will never be directly activated,
211 | * but can provide inherited properties to its common children states.
212 | *
213 | *
214 | *
215 | * - **`onEnter`** - {object=} - Callback function for when a state is entered. Good way
216 | * to trigger an action or dispatch an event, such as opening a dialog.
217 | * If minifying your scripts, make sure to use the `['injection1', 'injection2', function(injection1, injection2){}]` syntax.
218 | *
219 | *
220 | *
221 | * - **`onExit`** - {object=} - Callback function for when a state is exited. Good way to
222 | * trigger an action or dispatch an event, such as opening a dialog.
223 | * If minifying your scripts, make sure to use the `['injection1', 'injection2', function(injection1, injection2){}]` syntax.
224 | *
225 | *
226 | *
227 | * - **`reloadOnSearch = true`** - {boolean=} - If `false`, will not retrigger the same state
228 | * just because a search/query parameter has changed (via $location.search() or $location.hash()).
229 | * Useful for when you'd like to modify $location.search() without triggering a reload.
230 | *
231 | *
232 | *
233 | * - **`data`** - {object=} - Arbitrary data object, useful for custom configuration.
234 | *
235 | * #### Example:
236 | * Some state name examples
237 | * ```js
238 | * // stateName can be a single top-level name (must be unique).
239 | * $stateProvider.state("home", {});
240 | *
241 | * // Or it can be a nested state name. This state is a child of the
242 | * // above "home" state.
243 | * $stateProvider.state("home.newest", {});
244 | *
245 | * // Nest states as deeply as needed.
246 | * $stateProvider.state("home.newest.abc.xyz.inception", {});
247 | *
248 | * // state() returns $stateProvider, so you can chain state declarations.
249 | * $stateProvider
250 | * .state("home", {})
251 | * .state("about", {})
252 | * .state("contacts", {});
253 | * ```
254 | *
255 | * @param {string} name A unique state name, e.g. "home", "about", "contacts".
256 | * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
257 | * @param {object} definition State configuration object.
258 | */
259 | state(name: string, definition: Ng1StateDeclaration): StateProvider;
260 | state(definition: Ng1StateDeclaration): StateProvider;
261 | state(name: any, definition?: any) {
262 | if (isObject(name)) {
263 | definition = name;
264 | } else {
265 | definition.name = name;
266 | }
267 | this.stateRegistry.register(definition);
268 | return this;
269 | }
270 |
271 | /**
272 | * Registers an invalid state handler
273 | *
274 | * This is a passthrough to [[StateService.onInvalid]] for ng1.
275 | */
276 |
277 | onInvalid(callback: OnInvalidCallback): Function {
278 | return this.stateService.onInvalid(callback);
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/src/statebuilders/onEnterExitRetain.ts:
--------------------------------------------------------------------------------
1 | /** @publicapi @module ng1 */ /** */
2 | import {
3 | StateObject,
4 | TransitionStateHookFn,
5 | HookResult,
6 | Transition,
7 | services,
8 | ResolveContext,
9 | extend,
10 | } from '@uirouter/core';
11 | import { getLocals } from '../services';
12 | import { Ng1StateDeclaration } from '../interface';
13 |
14 | /**
15 | * This is a [[StateBuilder.builder]] function for angular1 `onEnter`, `onExit`,
16 | * `onRetain` callback hooks on a [[Ng1StateDeclaration]].
17 | *
18 | * When the [[StateBuilder]] builds a [[StateObject]] object from a raw [[StateDeclaration]], this builder
19 | * ensures that those hooks are injectable for @uirouter/angularjs (ng1).
20 | *
21 | * @internalapi
22 | */
23 | export const getStateHookBuilder = (hookName: 'onEnter' | 'onExit' | 'onRetain') =>
24 | function stateHookBuilder(stateObject: StateObject): TransitionStateHookFn {
25 | const hook = stateObject[hookName];
26 | const pathname = hookName === 'onExit' ? 'from' : 'to';
27 |
28 | function decoratedNg1Hook(trans: Transition, state: Ng1StateDeclaration): HookResult {
29 | const resolveContext = new ResolveContext(trans.treeChanges(pathname));
30 | const subContext = resolveContext.subContext(state.$$state());
31 | const locals = extend(getLocals(subContext), { $state$: state, $transition$: trans });
32 | return services.$injector.invoke(hook, this, locals);
33 | }
34 |
35 | return hook ? decoratedNg1Hook : undefined;
36 | };
37 |
--------------------------------------------------------------------------------
/src/statebuilders/views.ts:
--------------------------------------------------------------------------------
1 | /** @publicapi @module ng1 */ /** */
2 | import {
3 | StateObject,
4 | pick,
5 | forEach,
6 | tail,
7 | extend,
8 | isArray,
9 | isInjectable,
10 | isDefined,
11 | isString,
12 | services,
13 | trace,
14 | ViewConfig,
15 | ViewService,
16 | ViewConfigFactory,
17 | PathNode,
18 | ResolveContext,
19 | Resolvable,
20 | IInjectable,
21 | } from '@uirouter/core';
22 | import { Ng1ViewDeclaration } from '../interface';
23 | import { TemplateFactory } from '../templateFactory';
24 |
25 | /** @internalapi */
26 | export function getNg1ViewConfigFactory(): ViewConfigFactory {
27 | let templateFactory: TemplateFactory = null;
28 | return (path, view) => {
29 | templateFactory = templateFactory || services.$injector.get('$templateFactory');
30 | return [new Ng1ViewConfig(path, view, templateFactory)];
31 | };
32 | }
33 |
34 | /** @internalapi */
35 | const hasAnyKey = (keys, obj) => keys.reduce((acc, key) => acc || isDefined(obj[key]), false);
36 |
37 | /**
38 | * This is a [[StateBuilder.builder]] function for angular1 `views`.
39 | *
40 | * When the [[StateBuilder]] builds a [[StateObject]] object from a raw [[StateDeclaration]], this builder
41 | * handles the `views` property with logic specific to @uirouter/angularjs (ng1).
42 | *
43 | * If no `views: {}` property exists on the [[StateDeclaration]], then it creates the `views` object
44 | * and applies the state-level configuration to a view named `$default`.
45 | *
46 | * @internalapi
47 | */
48 | export function ng1ViewsBuilder(state: StateObject) {
49 | // Do not process root state
50 | if (!state.parent) return {};
51 |
52 | const tplKeys = ['templateProvider', 'templateUrl', 'template', 'notify', 'async'],
53 | ctrlKeys = ['controller', 'controllerProvider', 'controllerAs', 'resolveAs'],
54 | compKeys = ['component', 'bindings', 'componentProvider'],
55 | nonCompKeys = tplKeys.concat(ctrlKeys),
56 | allViewKeys = compKeys.concat(nonCompKeys);
57 |
58 | // Do not allow a state to have both state-level props and also a `views: {}` property.
59 | // A state without a `views: {}` property can declare properties for the `$default` view as properties of the state.
60 | // However, the `$default` approach should not be mixed with a separate `views: ` block.
61 | if (isDefined(state.views) && hasAnyKey(allViewKeys, state)) {
62 | throw new Error(
63 | `State '${state.name}' has a 'views' object. ` +
64 | `It cannot also have "view properties" at the state level. ` +
65 | `Move the following properties into a view (in the 'views' object): ` +
66 | ` ${allViewKeys.filter((key) => isDefined(state[key])).join(', ')}`
67 | );
68 | }
69 |
70 | const views: { [key: string]: Ng1ViewDeclaration } = {},
71 | viewsObject = state.views || { $default: pick(state, allViewKeys) };
72 |
73 | forEach(viewsObject, function (config: Ng1ViewDeclaration, name: string) {
74 | // Account for views: { "": { template... } }
75 | name = name || '$default';
76 | // Account for views: { header: "headerComponent" }
77 | if (isString(config)) config = { component: config };
78 |
79 | // Make a shallow copy of the config object
80 | config = extend({}, config);
81 |
82 | // Do not allow a view to mix props for component-style view with props for template/controller-style view
83 | if (hasAnyKey(compKeys, config) && hasAnyKey(nonCompKeys, config)) {
84 | throw new Error(
85 | `Cannot combine: ${compKeys.join('|')} with: ${nonCompKeys.join('|')} in stateview: '${name}@${state.name}'`
86 | );
87 | }
88 |
89 | config.resolveAs = config.resolveAs || '$resolve';
90 | config.$type = 'ng1';
91 | config.$context = state;
92 | config.$name = name;
93 |
94 | const normalized = ViewService.normalizeUIViewTarget(config.$context, config.$name);
95 | config.$uiViewName = normalized.uiViewName;
96 | config.$uiViewContextAnchor = normalized.uiViewContextAnchor;
97 |
98 | views[name] = config;
99 | });
100 | return views;
101 | }
102 |
103 | /** @hidden */
104 | let id = 0;
105 |
106 | /** @internalapi */
107 | export class Ng1ViewConfig implements ViewConfig {
108 | $id = id++;
109 | loaded = false;
110 | controller: Function; // actually IInjectable|string
111 | template: string;
112 | component: string;
113 | locals: any; // TODO: delete me
114 |
115 | constructor(public path: PathNode[], public viewDecl: Ng1ViewDeclaration, public factory: TemplateFactory) {}
116 |
117 | load() {
118 | const $q = services.$q;
119 | const context = new ResolveContext(this.path);
120 | const params = this.path.reduce((acc, node) => extend(acc, node.paramValues), {});
121 |
122 | const promises: any = {
123 | template: $q.when(this.factory.fromConfig(this.viewDecl, params, context)),
124 | controller: $q.when(this.getController(context)),
125 | };
126 |
127 | return $q.all(promises).then((results) => {
128 | trace.traceViewServiceEvent('Loaded', this);
129 | this.controller = results.controller;
130 | extend(this, results.template); // Either { template: "tpl" } or { component: "cmpName" }
131 | return this;
132 | });
133 | }
134 |
135 | getTemplate = (uiView, context: ResolveContext) =>
136 | this.component
137 | ? this.factory.makeComponentTemplate(uiView, context, this.component, this.viewDecl.bindings)
138 | : this.template;
139 |
140 | /**
141 | * Gets the controller for a view configuration.
142 | *
143 | * @returns {Function|Promise.} Returns a controller, or a promise that resolves to a controller.
144 | */
145 | getController(context: ResolveContext): IInjectable | string | Promise {
146 | const provider = this.viewDecl.controllerProvider;
147 | if (!isInjectable(provider)) return this.viewDecl.controller;
148 | const deps = services.$injector.annotate(provider);
149 | const providerFn = isArray(provider) ? tail(provider) : provider;
150 | const resolvable = new Resolvable('', providerFn, deps);
151 | return resolvable.get(context);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/templateFactory.ts:
--------------------------------------------------------------------------------
1 | /** @publicapi @module view */ /** */
2 | import { ng as angular } from './angular';
3 | import { IAugmentedJQuery } from 'angular';
4 | import {
5 | isArray,
6 | isDefined,
7 | isFunction,
8 | isObject,
9 | services,
10 | Obj,
11 | IInjectable,
12 | tail,
13 | kebobString,
14 | unnestR,
15 | ResolveContext,
16 | Resolvable,
17 | RawParams,
18 | } from '@uirouter/core';
19 | import { Ng1ViewDeclaration, TemplateFactoryProvider } from './interface';
20 |
21 | /**
22 | * Service which manages loading of templates from a ViewConfig.
23 | */
24 | export class TemplateFactory implements TemplateFactoryProvider {
25 | /** @hidden */ private _useHttp = angular.version.minor < 3;
26 | /** @hidden */ private $templateRequest;
27 | /** @hidden */ private $templateCache;
28 | /** @hidden */ private $http;
29 |
30 | /** @hidden */ $get = [
31 | '$http',
32 | '$templateCache',
33 | '$injector',
34 | ($http, $templateCache, $injector) => {
35 | this.$templateRequest = $injector.has && $injector.has('$templateRequest') && $injector.get('$templateRequest');
36 | this.$http = $http;
37 | this.$templateCache = $templateCache;
38 | return this;
39 | },
40 | ];
41 |
42 | /** @hidden */
43 | useHttpService(value: boolean) {
44 | this._useHttp = value;
45 | }
46 |
47 | /**
48 | * Creates a template from a configuration object.
49 | *
50 | * @param config Configuration object for which to load a template.
51 | * The following properties are search in the specified order, and the first one
52 | * that is defined is used to create the template:
53 | *
54 | * @param params Parameters to pass to the template function.
55 | * @param context The resolve context associated with the template's view
56 | *
57 | * @return {string|object} The template html as a string, or a promise for
58 | * that string,or `null` if no template is configured.
59 | */
60 | fromConfig(
61 | config: Ng1ViewDeclaration,
62 | params: any,
63 | context: ResolveContext
64 | ): Promise<{ template?: string; component?: string }> {
65 | const defaultTemplate = '';
66 |
67 | const asTemplate = (result) => services.$q.when(result).then((str) => ({ template: str }));
68 | const asComponent = (result) => services.$q.when(result).then((str) => ({ component: str }));
69 |
70 | return isDefined(config.template)
71 | ? asTemplate(this.fromString(config.template, params))
72 | : isDefined(config.templateUrl)
73 | ? asTemplate(this.fromUrl(config.templateUrl, params))
74 | : isDefined(config.templateProvider)
75 | ? asTemplate(this.fromProvider(config.templateProvider, params, context))
76 | : isDefined(config.component)
77 | ? asComponent(config.component)
78 | : isDefined(config.componentProvider)
79 | ? asComponent(this.fromComponentProvider(config.componentProvider, params, context))
80 | : asTemplate(defaultTemplate);
81 | }
82 |
83 | /**
84 | * Creates a template from a string or a function returning a string.
85 | *
86 | * @param template html template as a string or function that returns an html template as a string.
87 | * @param params Parameters to pass to the template function.
88 | *
89 | * @return {string|object} The template html as a string, or a promise for that
90 | * string.
91 | */
92 | fromString(template: string | Function, params?: RawParams) {
93 | return isFunction(template) ? (template)(params) : template;
94 | }
95 |
96 | /**
97 | * Loads a template from the a URL via `$http` and `$templateCache`.
98 | *
99 | * @param {string|Function} url url of the template to load, or a function
100 | * that returns a url.
101 | * @param {Object} params Parameters to pass to the url function.
102 | * @return {string|Promise.} The template html as a string, or a promise
103 | * for that string.
104 | */
105 | fromUrl(url: string | Function, params: any) {
106 | if (isFunction(url)) url = (url)(params);
107 | if (url == null) return null;
108 |
109 | if (this._useHttp) {
110 | return this.$http
111 | .get(url, { cache: this.$templateCache, headers: { Accept: 'text/html' } })
112 | .then(function (response) {
113 | return response.data;
114 | });
115 | }
116 |
117 | return this.$templateRequest(url);
118 | }
119 |
120 | /**
121 | * Creates a template by invoking an injectable provider function.
122 | *
123 | * @param provider Function to invoke via `locals`
124 | * @param {Function} injectFn a function used to invoke the template provider
125 | * @return {string|Promise.} The template html as a string, or a promise
126 | * for that string.
127 | */
128 | fromProvider(provider: IInjectable, params: any, context: ResolveContext) {
129 | const deps = services.$injector.annotate(provider);
130 | const providerFn = isArray(provider) ? tail(provider) : provider;
131 | const resolvable = new Resolvable('', providerFn, deps);
132 | return resolvable.get(context);
133 | }
134 |
135 | /**
136 | * Creates a component's template by invoking an injectable provider function.
137 | *
138 | * @param provider Function to invoke via `locals`
139 | * @param {Function} injectFn a function used to invoke the template provider
140 | * @return {string} The template html as a string: "".
141 | */
142 | fromComponentProvider(provider: IInjectable, params: any, context: ResolveContext) {
143 | const deps = services.$injector.annotate(provider);
144 | const providerFn = isArray(provider) ? tail(provider) : provider;
145 | const resolvable = new Resolvable('', providerFn, deps);
146 | return resolvable.get(context);
147 | }
148 |
149 | /**
150 | * Creates a template from a component's name
151 | *
152 | * This implements route-to-component.
153 | * It works by retrieving the component (directive) metadata from the injector.
154 | * It analyses the component's bindings, then constructs a template that instantiates the component.
155 | * The template wires input and output bindings to resolves or from the parent component.
156 | *
157 | * @param uiView {object} The parent ui-view (for binding outputs to callbacks)
158 | * @param context The ResolveContext (for binding outputs to callbacks returned from resolves)
159 | * @param component {string} Component's name in camel case.
160 | * @param bindings An object defining the component's bindings: {foo: '<'}
161 | * @return {string} The template as a string: "".
162 | */
163 | makeComponentTemplate(uiView: IAugmentedJQuery, context: ResolveContext, component: string, bindings?: any) {
164 | bindings = bindings || {};
165 |
166 | // Bind once prefix
167 | const prefix = angular.version.minor >= 3 ? '::' : '';
168 | // Convert to kebob name. Add x- prefix if the string starts with `x-` or `data-`
169 | const kebob = (camelCase: string) => {
170 | const kebobed = kebobString(camelCase);
171 | return /^(x|data)-/.exec(kebobed) ? `x-${kebobed}` : kebobed;
172 | };
173 |
174 | const attributeTpl = (input: BindingTuple) => {
175 | const { name, type } = input;
176 | const attrName = kebob(name);
177 | // If the ui-view has an attribute which matches a binding on the routed component
178 | // then pass that attribute through to the routed component template.
179 | // Prefer ui-view wired mappings to resolve data, unless the resolve was explicitly bound using `bindings:`
180 | if (uiView.attr(attrName) && !bindings[name]) return `${attrName}='${uiView.attr(attrName)}'`;
181 |
182 | const resolveName = bindings[name] || name;
183 | // Pre-evaluate the expression for "@" bindings by enclosing in {{ }}
184 | // some-attr="{{ ::$resolve.someResolveName }}"
185 | if (type === '@') return `${attrName}='{{${prefix}$resolve.${resolveName}}}'`;
186 |
187 | // Wire "&" callbacks to resolves that return a callback function
188 | // Get the result of the resolve (should be a function) and annotate it to get its arguments.
189 | // some-attr="$resolve.someResolveResultName(foo, bar)"
190 | if (type === '&') {
191 | const res = context.getResolvable(resolveName);
192 | const fn = res && res.data;
193 | const args = (fn && services.$injector.annotate(fn)) || [];
194 | // account for array style injection, i.e., ['foo', function(foo) {}]
195 | const arrayIdxStr = isArray(fn) ? `[${fn.length - 1}]` : '';
196 | return `${attrName}='$resolve.${resolveName}${arrayIdxStr}(${args.join(',')})'`;
197 | }
198 |
199 | // some-attr="::$resolve.someResolveName"
200 | return `${attrName}='${prefix}$resolve.${resolveName}'`;
201 | };
202 |
203 | const attrs = getComponentBindings(component).map(attributeTpl).join(' ');
204 | const kebobName = kebob(component);
205 | return `<${kebobName} ${attrs}>${kebobName}>`;
206 | }
207 | }
208 |
209 | // Gets all the directive(s)' inputs ('@', '=', and '<') and outputs ('&')
210 | function getComponentBindings(name: string) {
211 | const cmpDefs = services.$injector.get(name + 'Directive'); // could be multiple
212 | if (!cmpDefs || !cmpDefs.length) throw new Error(`Unable to find component named '${name}'`);
213 | return cmpDefs.map(getBindings).reduce(unnestR, []);
214 | }
215 |
216 | // Given a directive definition, find its object input attributes
217 | // Use different properties, depending on the type of directive (component, bindToController, normal)
218 | const getBindings = (def: any) => {
219 | if (isObject(def.bindToController)) return scopeBindings(def.bindToController);
220 | return scopeBindings(def.scope);
221 | };
222 |
223 | interface BindingTuple {
224 | name: string;
225 | type: string;
226 | }
227 |
228 | // for ng 1.2 style, process the scope: { input: "=foo" }
229 | // for ng 1.3 through ng 1.5, process the component's bindToController: { input: "=foo" } object
230 | const scopeBindings = (bindingsObj: Obj) =>
231 | Object.keys(bindingsObj || {})
232 | // [ 'input', [ '=foo', '=', 'foo' ] ]
233 | .map((key) => [key, /^([=<@&])[?]?(.*)/.exec(bindingsObj[key])])
234 | // skip malformed values
235 | .filter((tuple) => isDefined(tuple) && isArray(tuple[1]))
236 | // { name: ('foo' || 'input'), type: '=' }
237 | .map((tuple) => ({ name: tuple[1][2] || tuple[0], type: tuple[1][1] } as BindingTuple));
238 |
--------------------------------------------------------------------------------
/src/urlRouterProvider.ts:
--------------------------------------------------------------------------------
1 | /** @publicapi @module url */ /** */
2 | import {
3 | UIRouter,
4 | LocationServices,
5 | $InjectorLike,
6 | BaseUrlRule,
7 | UrlRuleHandlerFn,
8 | UrlMatcher,
9 | IInjectable,
10 | UrlRouter,
11 | } from '@uirouter/core';
12 | import { services, isString, isFunction, isArray, identity } from '@uirouter/core';
13 |
14 | export interface RawNg1RuleFunction {
15 | ($injector: $InjectorLike, $location: LocationServices): string | void;
16 | }
17 |
18 | /**
19 | * Manages rules for client-side URL
20 | *
21 | * ### Deprecation warning:
22 | * This class is now considered to be an internal API
23 | * Use the [[UrlService]] instead.
24 | * For configuring URL rules, use the [[UrlRulesApi]] which can be found as [[UrlService.rules]].
25 | *
26 | * This class manages the router rules for what to do when the URL changes.
27 | *
28 | * This provider remains for backwards compatibility.
29 | *
30 | * @internalapi
31 | * @deprecated
32 | */
33 | export class UrlRouterProvider {
34 | static injectableHandler(router: UIRouter, handler: IInjectable): UrlRuleHandlerFn {
35 | return (match) => services.$injector.invoke(handler, null, { $match: match, $stateParams: router.globals.params });
36 | }
37 |
38 | /** @hidden */
39 | constructor(/** @hidden */ private router: UIRouter) {}
40 |
41 | /** @hidden */
42 | $get(): UrlRouter {
43 | const urlService = this.router.urlService;
44 | this.router.urlRouter.update(true);
45 | if (!urlService.interceptDeferred) urlService.listen();
46 | return this.router.urlRouter;
47 | }
48 |
49 | /**
50 | * Registers a url handler function.
51 | *
52 | * Registers a low level url handler (a `rule`).
53 | * A rule detects specific URL patterns and returns a redirect, or performs some action.
54 | *
55 | * If a rule returns a string, the URL is replaced with the string, and all rules are fired again.
56 | *
57 | * #### Example:
58 | * ```js
59 | * var app = angular.module('app', ['ui.router.router']);
60 | *
61 | * app.config(function ($urlRouterProvider) {
62 | * // Here's an example of how you might allow case insensitive urls
63 | * $urlRouterProvider.rule(function ($injector, $location) {
64 | * var path = $location.path(),
65 | * normalized = path.toLowerCase();
66 | *
67 | * if (path !== normalized) {
68 | * return normalized;
69 | * }
70 | * });
71 | * });
72 | * ```
73 | *
74 | * @param ruleFn
75 | * Handler function that takes `$injector` and `$location` services as arguments.
76 | * You can use them to detect a url and return a different url as a string.
77 | *
78 | * @return [[UrlRouterProvider]] (`this`)
79 | */
80 | rule(ruleFn: RawNg1RuleFunction): UrlRouterProvider {
81 | if (!isFunction(ruleFn)) throw new Error("'rule' must be a function");
82 |
83 | const match = () => ruleFn(services.$injector, this.router.locationService);
84 |
85 | const rule = new BaseUrlRule(match, identity);
86 | this.router.urlService.rules.rule(rule);
87 | return this;
88 | }
89 |
90 | /**
91 | * Defines the path or behavior to use when no url can be matched.
92 | *
93 | * #### Example:
94 | * ```js
95 | * var app = angular.module('app', ['ui.router.router']);
96 | *
97 | * app.config(function ($urlRouterProvider) {
98 | * // if the path doesn't match any of the urls you configured
99 | * // otherwise will take care of routing the user to the
100 | * // specified url
101 | * $urlRouterProvider.otherwise('/index');
102 | *
103 | * // Example of using function rule as param
104 | * $urlRouterProvider.otherwise(function ($injector, $location) {
105 | * return '/a/valid/url';
106 | * });
107 | * });
108 | * ```
109 | *
110 | * @param rule
111 | * The url path you want to redirect to or a function rule that returns the url path or performs a `$state.go()`.
112 | * The function version is passed two params: `$injector` and `$location` services, and should return a url string.
113 | *
114 | * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
115 | */
116 | otherwise(rule: string | RawNg1RuleFunction): UrlRouterProvider {
117 | const urlRules = this.router.urlService.rules;
118 | if (isString(rule)) {
119 | urlRules.otherwise(rule);
120 | } else if (isFunction(rule)) {
121 | urlRules.otherwise(() => rule(services.$injector, this.router.locationService));
122 | } else {
123 | throw new Error("'rule' must be a string or function");
124 | }
125 |
126 | return this;
127 | }
128 |
129 | /**
130 | * Registers a handler for a given url matching.
131 | *
132 | * If the handler is a string, it is
133 | * treated as a redirect, and is interpolated according to the syntax of match
134 | * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
135 | *
136 | * If the handler is a function, it is injectable.
137 | * It gets invoked if `$location` matches.
138 | * You have the option of inject the match object as `$match`.
139 | *
140 | * The handler can return
141 | *
142 | * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
143 | * will continue trying to find another one that matches.
144 | * - **string** which is treated as a redirect and passed to `$location.url()`
145 | * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
146 | *
147 | * #### Example:
148 | * ```js
149 | * var app = angular.module('app', ['ui.router.router']);
150 | *
151 | * app.config(function ($urlRouterProvider) {
152 | * $urlRouterProvider.when($state.url, function ($match, $stateParams) {
153 | * if ($state.$current.navigable !== state ||
154 | * !equalForKeys($match, $stateParams) {
155 | * $state.transitionTo(state, $match, false);
156 | * }
157 | * });
158 | * });
159 | * ```
160 | *
161 | * @param what A pattern string to match, compiled as a [[UrlMatcher]].
162 | * @param handler The path (or function that returns a path) that you want to redirect your user to.
163 | * @param ruleCallback [optional] A callback that receives the `rule` registered with [[UrlMatcher.rule]]
164 | *
165 | * Note: the handler may also invoke arbitrary code, such as `$state.go()`
166 | */
167 | when(what: RegExp | UrlMatcher | string, handler: string | IInjectable): this {
168 | if (isArray(handler) || isFunction(handler)) {
169 | handler = UrlRouterProvider.injectableHandler(this.router, handler);
170 | }
171 |
172 | this.router.urlService.rules.when(what, handler as any);
173 | return this;
174 | }
175 |
176 | /**
177 | * Disables monitoring of the URL.
178 | *
179 | * Call this method before UI-Router has bootstrapped.
180 | * It will stop UI-Router from performing the initial url sync.
181 | *
182 | * This can be useful to perform some asynchronous initialization before the router starts.
183 | * Once the initialization is complete, call [[listen]] to tell UI-Router to start watching and synchronizing the URL.
184 | *
185 | * #### Example:
186 | * ```js
187 | * var app = angular.module('app', ['ui.router']);
188 | *
189 | * app.config(function ($urlRouterProvider) {
190 | * // Prevent $urlRouter from automatically intercepting URL changes;
191 | * $urlRouterProvider.deferIntercept();
192 | * })
193 | *
194 | * app.run(function (MyService, $urlRouter, $http) {
195 | * $http.get("/stuff").then(function(resp) {
196 | * MyService.doStuff(resp.data);
197 | * $urlRouter.listen();
198 | * $urlRouter.sync();
199 | * });
200 | * });
201 | * ```
202 | *
203 | * @param defer Indicates whether to defer location change interception.
204 | * Passing no parameter is equivalent to `true`.
205 | */
206 | deferIntercept(defer?: boolean): void {
207 | this.router.urlService.deferIntercept(defer);
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/src/viewScroll.ts:
--------------------------------------------------------------------------------
1 | /** @publicapi @module ng1 */ /** */
2 | import { ng as angular } from './angular';
3 | import { IServiceProviderFactory } from 'angular';
4 | import IAnchorScrollService = angular.IAnchorScrollService;
5 | import ITimeoutService = angular.ITimeoutService;
6 |
7 | export interface UIViewScrollProvider {
8 | /**
9 | * Uses standard anchorScroll behavior
10 | *
11 | * Reverts [[$uiViewScroll]] back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
12 | * service for scrolling based on the url anchor.
13 | */
14 | useAnchorScroll(): void;
15 | }
16 |
17 | /** @hidden */
18 | function $ViewScrollProvider() {
19 | let useAnchorScroll = false;
20 |
21 | this.useAnchorScroll = function () {
22 | useAnchorScroll = true;
23 | };
24 |
25 | this.$get = [
26 | '$anchorScroll',
27 | '$timeout',
28 | function ($anchorScroll: IAnchorScrollService, $timeout: ITimeoutService): Function {
29 | if (useAnchorScroll) {
30 | return $anchorScroll;
31 | }
32 |
33 | return function ($element: JQuery) {
34 | return $timeout(
35 | function () {
36 | $element[0].scrollIntoView();
37 | },
38 | 0,
39 | false
40 | );
41 | };
42 | },
43 | ];
44 | }
45 |
46 | angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider);
47 |
--------------------------------------------------------------------------------
/test/angular/jest-angular.js:
--------------------------------------------------------------------------------
1 | // This setup is used to inject specific versions of angularjs to test against
2 |
3 | // Jest is configured to alias `import * as angular from 'angular'` to this file in jest.config.js.
4 | // This file then imports angularjs bundle via the 'jest-angular-import' module alias which maps to
5 | // a specific version of the angularjs bundle.
6 | // It then exports the window.angular for use in tests that import from 'angular'
7 |
8 | require('jest-angular-import');
9 | module.exports = window.angular;
10 |
--------------------------------------------------------------------------------
/test/angular/update_all.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | for NGVER in 1.* ; do
4 | pushd $NGVER
5 | for PKG in angular angular-animate angular-mocks ; do
6 | curl -O -L https://unpkg.com/${PKG}@${NGVER}/$PKG.js
7 | done
8 | popd
9 | done
10 |
--------------------------------------------------------------------------------
/test/jest.init.ts:
--------------------------------------------------------------------------------
1 | const noop = () => {};
2 | Object.defineProperty(window, 'scrollTo', { value: noop, writable: true });
3 |
4 | import 'angular';
5 | import 'angular-mocks';
6 | import 'angular-animate';
7 | import './util/testUtilsNg1';
8 |
9 | require('../src/index');
10 |
--------------------------------------------------------------------------------
/test/ng1StateBuilderSpec.ts:
--------------------------------------------------------------------------------
1 | import { StateBuilder, StateMatcher, ng1ViewsBuilder, extend } from '../src/index';
2 | import { Resolvable } from '@uirouter/core';
3 | declare var inject;
4 |
5 | describe('Ng1 StateBuilder', function () {
6 | const parent = { name: '' };
7 | let builder,
8 | matcher,
9 | urlMatcherFactoryProvider: any = {
10 | compile: function () {},
11 | isMatcher: function () {},
12 | };
13 |
14 | beforeEach(function () {
15 | matcher = new StateMatcher({});
16 | builder = new StateBuilder(matcher, urlMatcherFactoryProvider);
17 | builder.builder('views', ng1ViewsBuilder);
18 | });
19 |
20 | it('should use the state object to build a default view, when no `views` property is found', function () {
21 | const config = { url: '/foo', templateUrl: '/foo.html', controller: 'FooController', parent: parent };
22 | const built = builder.builder('views')(config);
23 |
24 | expect(built.$default).not.toEqual(config);
25 | expect(built.$default).toEqual(
26 | expect.objectContaining({
27 | templateUrl: '/foo.html',
28 | controller: 'FooController',
29 | resolveAs: '$resolve',
30 | })
31 | );
32 | });
33 |
34 | it('It should use the views object to build views, when defined', function () {
35 | const config = { a: { foo: 'bar', controller: 'FooController' } };
36 | const builtViews = builder.builder('views')({ parent: parent, views: config });
37 | expect(builtViews.a.foo).toEqual(config.a.foo);
38 | expect(builtViews.a.controller).toEqual(config.a.controller);
39 | });
40 |
41 | it('should not allow a view config with both component and template keys', inject(function ($injector) {
42 | const config = {
43 | name: 'foo',
44 | url: '/foo',
45 | template: 'hey
',
46 | controller: 'FooController',
47 | parent: parent,
48 | };
49 | expect(() => builder.builder('views')(config)).not.toThrow();
50 | expect(() => builder.builder('views')(extend({ component: 'fooComponent' }, config))).toThrow();
51 | expect(() => builder.builder('views')(extend({ componentProvider: () => 'fooComponent' }, config))).toThrow();
52 | expect(() => builder.builder('views')(extend({ bindings: {} }, config))).toThrow();
53 | }));
54 |
55 | it('should replace a resolve: string value with a function that injects the service of the same name', inject(function (
56 | $injector
57 | ) {
58 | const config = { resolve: { foo: 'bar' } };
59 | expect(builder.builder('resolvables')).toBeDefined();
60 | const built: Resolvable[] = builder.builder('resolvables')(config);
61 | expect(built[0].deps).toEqual(['bar']);
62 | }));
63 | });
64 |
--------------------------------------------------------------------------------
/test/servicesSpec.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | import { UIRouter, trace } from '@uirouter/core';
3 |
4 | declare var inject;
5 |
6 | const module = angular['mock'].module;
7 |
8 | describe('UI-Router services', () => {
9 | let $uiRouterProvider: UIRouter, $uiRouter: UIRouter;
10 | let providers;
11 | let services;
12 |
13 | beforeEach(
14 | module('ui.router', function (
15 | _$uiRouterProvider_,
16 | $urlMatcherFactoryProvider,
17 | $urlRouterProvider,
18 | $stateRegistryProvider,
19 | $uiRouterGlobalsProvider,
20 | $transitionsProvider,
21 | $stateProvider
22 | ) {
23 | $uiRouterProvider = _$uiRouterProvider_;
24 |
25 | expect($uiRouterProvider['router']).toBe($uiRouterProvider);
26 |
27 | providers = {
28 | $uiRouterProvider,
29 | $urlMatcherFactoryProvider,
30 | $urlRouterProvider,
31 | $stateRegistryProvider,
32 | $uiRouterGlobalsProvider,
33 | $transitionsProvider,
34 | $stateProvider,
35 | };
36 | })
37 | );
38 |
39 | beforeEach(inject(function (
40 | _$uiRouter_,
41 | $urlMatcherFactory,
42 | $urlRouter,
43 | $stateRegistry,
44 | $uiRouterGlobals,
45 | $transitions,
46 | $state,
47 | $stateParams,
48 | $templateFactory,
49 | $view,
50 | $trace
51 | ) {
52 | $uiRouter = _$uiRouter_;
53 |
54 | services = {
55 | $urlMatcherFactory,
56 | $urlRouter,
57 | $stateRegistry,
58 | $uiRouterGlobals,
59 | $transitions,
60 | $state,
61 | $stateParams,
62 | $templateFactory,
63 | $view,
64 | $trace,
65 | };
66 | }));
67 |
68 | it('Should expose ui-router providers from the UIRouter instance', () => {
69 | expect(providers.$urlMatcherFactoryProvider).toBe($uiRouterProvider.urlMatcherFactory);
70 | expect(providers.$urlRouterProvider).toBe($uiRouterProvider.urlRouterProvider);
71 | expect(providers.$stateRegistryProvider).toBe($uiRouterProvider.stateRegistry);
72 | expect(providers.$uiRouterGlobalsProvider).toBe($uiRouterProvider.globals);
73 | expect(providers.$transitionsProvider).toBe($uiRouterProvider.transitionService);
74 | expect(providers.$stateProvider).toBe($uiRouterProvider.stateProvider);
75 | });
76 |
77 | it('Should expose ui-router services from the UIRouter instance', () => {
78 | expect($uiRouter).toBe($uiRouterProvider);
79 | expect(services.$urlMatcherFactory).toBe($uiRouter.urlMatcherFactory);
80 | expect(services.$urlRouter).toBe($uiRouter.urlRouter);
81 | expect(services.$stateRegistry).toBe($uiRouter.stateRegistry);
82 | expect(services.$uiRouterGlobals).toBe($uiRouter.globals);
83 | expect(services.$transitions).toBe($uiRouter.transitionService);
84 | expect(services.$state).toBe($uiRouter.stateService);
85 | expect(services.$stateParams).toBe($uiRouter.globals.params);
86 | expect(services.$templateFactory.constructor.name).toBe('TemplateFactory');
87 | expect(services.$view).toBe($uiRouter.viewService);
88 | expect(services.$trace).toBe(trace);
89 | });
90 | });
91 |
--------------------------------------------------------------------------------
/test/stateEventsSpec.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | import { StateDeclaration } from '@uirouter/core';
3 | import { resolvedError, obj, decorateExceptionHandler } from './util/testUtilsNg1';
4 | import '../src/legacy/stateEvents';
5 |
6 | declare var inject;
7 |
8 | const module = angular['mock'].module;
9 |
10 | describe('UI-Router v0.2.x $state events', function () {
11 | let $injector, stateProvider;
12 |
13 | beforeEach(
14 | module('ui.router.state.events', function ($stateEventsProvider, $exceptionHandlerProvider) {
15 | $stateEventsProvider.enable();
16 | decorateExceptionHandler($exceptionHandlerProvider);
17 | })
18 | );
19 |
20 | let log, logEvents, logEnterExit;
21 | function eventLogger(event, to, toParams, from, fromParams) {
22 | if (logEvents && angular.isFunction(to.to)) {
23 | const transition = to;
24 | log += event.name + '(' + transition.to().name + ',' + transition.from().name + ');';
25 | } else if (logEvents) {
26 | log +=
27 | event.name +
28 | '(' +
29 | (angular.isString(to.name) ? to.name : to) +
30 | ',' +
31 | (angular.isString(from.name) ? from.name : from) +
32 | ');';
33 | }
34 | }
35 | function callbackLogger(what) {
36 | return function () {
37 | if (logEnterExit) log += this.name + '.' + what + ';';
38 | };
39 | }
40 |
41 | const A: StateDeclaration = { data: {} },
42 | B: StateDeclaration = {},
43 | C: StateDeclaration = {},
44 | D: StateDeclaration = { params: { x: {}, y: {} } },
45 | DD: StateDeclaration = { parent: D, params: { z: {} } },
46 | E: StateDeclaration = { params: { i: {} } },
47 | F: StateDeclaration = {
48 | resolve: {
49 | delay: function ($timeout) {
50 | return $timeout(angular.noop, 50);
51 | },
52 | },
53 | };
54 |
55 | beforeEach(
56 | module(function ($stateProvider, $provide) {
57 | angular.forEach([A, B, C, D, DD], function (state) {
58 | state.onEnter = callbackLogger('onEnter');
59 | state.onExit = callbackLogger('onExit');
60 | });
61 | stateProvider = $stateProvider;
62 |
63 | $stateProvider
64 | .state('A', A)
65 | .state('B', B)
66 | .state('C', C)
67 | .state('D', D)
68 | .state('DD', DD)
69 | .state('E', E)
70 | .state('F', F);
71 | })
72 | );
73 |
74 | beforeEach(inject(function ($rootScope, _$injector_) {
75 | $injector = _$injector_;
76 | log = '';
77 | logEvents = logEnterExit = false;
78 | $rootScope.$on('$stateChangeStart', eventLogger);
79 | $rootScope.$on('$stateChangeSuccess', eventLogger);
80 | $rootScope.$on('$stateChangeError', eventLogger);
81 | $rootScope.$on('$stateNotFound', eventLogger);
82 | }));
83 |
84 | function $get(what) {
85 | return $injector.get(what);
86 | }
87 |
88 | function initStateTo(state, optionalParams?, optionalOptions?) {
89 | const $state = $get('$state'),
90 | $q = $get('$q');
91 | $state.transitionTo(state, optionalParams || {}, optionalOptions || {});
92 | $q.flush();
93 | expect($state.current).toBe(state);
94 | }
95 |
96 | describe('.transitionTo()', function () {
97 | it('triggers $stateChangeStart', inject(function ($state, $q, $rootScope) {
98 | initStateTo(E, { i: 'iii' }, { anOption: true });
99 | let called;
100 | $rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams, options) {
101 | expect(from).toBe(E);
102 |
103 | expect(obj(fromParams)).toEqual({ i: 'iii' });
104 |
105 | expect(to).toBe(D);
106 |
107 | expect(obj(toParams)).toEqual({ x: '1', y: '2' });
108 |
109 | expect(options.custom.anOption).toBe(false);
110 |
111 | expect($state.current).toBe(from); // $state not updated yet
112 | expect(obj($state.params)).toEqual(obj(fromParams));
113 | called = true;
114 | });
115 | $state.transitionTo(D, { x: '1', y: '2' }, { custom: { anOption: false } });
116 | $q.flush();
117 | expect(called).toBeTruthy();
118 | expect($state.current).toBe(D);
119 | }));
120 |
121 | it('can be cancelled by preventDefault() in $stateChangeStart', inject(function ($state, $q, $rootScope) {
122 | $state.defaultErrorHandler(function () {});
123 | initStateTo(A);
124 | let called;
125 | $rootScope.$on('$stateChangeStart', function (ev) {
126 | ev.preventDefault();
127 | called = true;
128 | });
129 | const promise = $state.transitionTo(B, {});
130 | $q.flush();
131 | expect(called).toBeTruthy();
132 | expect($state.current).toBe(A);
133 | expect(resolvedError(promise)).toBeTruthy();
134 | }));
135 |
136 | it('triggers $stateNotFound', inject(function ($state, $q, $rootScope) {
137 | initStateTo(E, { i: 'iii' });
138 | let called;
139 | $rootScope.$on('$stateNotFound', function (ev, unfoundState, fromState, fromParams) {
140 | expect(fromState).toBe(E);
141 | expect(obj(fromParams)).toEqual({ i: 'iii' });
142 | expect(unfoundState.to).toEqual('never_defined');
143 | expect(unfoundState.toParams).toEqual({ x: '1', y: '2' });
144 |
145 | expect($state.current).toBe(E); // $state not updated yet
146 | expect(obj($state.params)).toEqual({ i: 'iii' });
147 | called = true;
148 | });
149 | let message;
150 | $state.transitionTo('never_defined', { x: '1', y: '2' }).catch(function (e) {
151 | message = e.detail;
152 | });
153 | $q.flush();
154 | expect(message).toEqual("No such state 'never_defined'");
155 | expect(called).toBeTruthy();
156 | expect($state.current).toBe(E);
157 | }));
158 |
159 | it('throws Error on failed relative state resolution', inject(function ($state, $q) {
160 | $state.transitionTo(DD);
161 | $q.flush();
162 | let error,
163 | promise = $state.transitionTo('^.Z', null, { relative: $state.$current });
164 | promise.catch(function (e) {
165 | error = e.detail;
166 | });
167 | $q.flush();
168 |
169 | const err = "Could not resolve '^.Z' from state 'DD'";
170 | expect(error).toBe(err);
171 | }));
172 |
173 | it('sends $stateChangeError for exceptions in onEnter', inject(function (
174 | $state,
175 | $q,
176 | $rootScope,
177 | $exceptionHandler
178 | ) {
179 | $exceptionHandler.disabled = true;
180 | $state.defaultErrorHandler(function () {});
181 |
182 | stateProvider.state('onEnterFail', {
183 | onEnter: function () {
184 | throw new Error('negative onEnter');
185 | },
186 | });
187 |
188 | let called;
189 | $rootScope.$on('$stateChangeError', function (ev, to, toParams, from, fromParams, options) {
190 | called = true;
191 | });
192 |
193 | initStateTo(A);
194 | $state.transitionTo('onEnterFail');
195 | $q.flush();
196 |
197 | expect(called).toBeTruthy();
198 | expect($state.current.name).toEqual(A.name);
199 | }));
200 |
201 | it('can be cancelled by preventDefault() in $stateNotFound', inject(function ($state, $q, $rootScope) {
202 | initStateTo(A);
203 | let called;
204 | $rootScope.$on('$stateNotFound', function (ev) {
205 | ev.preventDefault();
206 | called = true;
207 | });
208 | const promise = $state.transitionTo('never_defined', {});
209 | $q.flush();
210 | expect(called).toBeTruthy();
211 | expect($state.current).toBe(A);
212 | expect(resolvedError(promise)).toBeTruthy();
213 | }));
214 |
215 | it('can be redirected in $stateNotFound', inject(function ($state, $q, $rootScope) {
216 | initStateTo(A);
217 | let called;
218 | $rootScope.$on('$stateNotFound', function (ev, redirect) {
219 | redirect.to = D;
220 | redirect.toParams = { x: '1', y: '2' };
221 | called = true;
222 | });
223 | const promise = $state.transitionTo('never_defined', { z: 3 });
224 | $q.flush();
225 | expect(called).toBeTruthy();
226 | expect($state.current).toBe(D);
227 | expect(obj($state.params)).toEqual({ x: '1', y: '2' });
228 | }));
229 |
230 | it('can lazy-define a state in $stateNotFound', inject(function ($state, $q, $rootScope) {
231 | initStateTo(DD, { x: 1, y: 2, z: 3 });
232 | let called;
233 | $rootScope.$on('$stateNotFound', function (ev, redirect) {
234 | stateProvider.state(redirect.to, { parent: DD, params: { w: {} } });
235 | ev.retry = called = true;
236 | });
237 | const promise = $state.go('DDD', { w: 4 });
238 | $q.flush();
239 | expect(called).toBeTruthy();
240 | expect($state.current.name).toEqual('DDD');
241 | expect(obj($state.params)).toEqual({ x: 1, y: 2, z: 3, w: 4 });
242 | }));
243 |
244 | it('can defer a state transition in $stateNotFound', inject(function ($state, $q, $rootScope) {
245 | initStateTo(A);
246 | let called;
247 | const deferred = $q.defer();
248 | $rootScope.$on('$stateNotFound', function (ev, redirect) {
249 | ev.retry = deferred.promise;
250 | called = true;
251 | });
252 | const promise = $state.go('AA', { a: 1 });
253 | stateProvider.state('AA', { parent: A, params: { a: {} } });
254 | deferred.resolve();
255 | $q.flush();
256 | expect(called).toBeTruthy();
257 | expect($state.current.name).toEqual('AA');
258 | expect(obj($state.params)).toEqual({ a: 1 });
259 | }));
260 |
261 | it('can defer and supersede a state transition in $stateNotFound', inject(function ($state, $q, $rootScope) {
262 | initStateTo(A);
263 | let called;
264 | const deferred = $q.defer();
265 | $rootScope.$on('$stateNotFound', function (ev, redirect) {
266 | ev.retry = deferred.promise;
267 | called = true;
268 | });
269 | const promise = $state.go('AA', { a: 1 });
270 | $state.go(B);
271 | stateProvider.state('AA', { parent: A, params: { a: {} } });
272 | deferred.resolve();
273 | $q.flush();
274 | expect(called).toBeTruthy();
275 | expect($state.current).toEqual(B);
276 | expect(obj($state.params)).toEqual({});
277 | }));
278 |
279 | it('triggers $stateChangeSuccess', inject(function ($state, $q, $rootScope) {
280 | initStateTo(E, { i: 'iii' });
281 | let called;
282 | $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
283 | expect(from).toBe(E);
284 | expect(obj(fromParams)).toEqual({ i: 'iii' });
285 | expect(to).toBe(D);
286 | expect(obj(toParams)).toEqual({ x: '1', y: '2' });
287 |
288 | expect($state.current).toBe(to); // $state has been updated
289 | expect(obj($state.params)).toEqual(obj(toParams));
290 | called = true;
291 | });
292 | $state.transitionTo(D, { x: '1', y: '2' });
293 | $q.flush();
294 | expect(called).toBeTruthy();
295 | expect($state.current).toBe(D);
296 | }));
297 |
298 | it('does not trigger $stateChangeSuccess when suppressed, but changes state', inject(function (
299 | $state,
300 | $q,
301 | $rootScope
302 | ) {
303 | initStateTo(E, { i: 'iii' });
304 | let called;
305 |
306 | $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
307 | called = true;
308 | });
309 |
310 | $state.transitionTo(D, { x: '1', y: '2' }, { notify: false });
311 | $q.flush();
312 |
313 | expect(called).toBeFalsy();
314 | expect($state.current).toBe(D);
315 | }));
316 |
317 | it('does not trigger $stateChangeSuccess when suppressed, but updates params', inject(function (
318 | $state,
319 | $q,
320 | $rootScope
321 | ) {
322 | initStateTo(E, { x: 'iii' });
323 | let called;
324 |
325 | $rootScope.$on('$stateChangeSuccess', function (ev, transition) {
326 | called = true;
327 | });
328 | $state.transitionTo(E, { i: '1', y: '2' }, { notify: false });
329 | $q.flush();
330 |
331 | expect(called).toBeFalsy();
332 | expect($state.params.i).toBe('1');
333 | expect($state.current).toBe(E);
334 | }));
335 |
336 | it('aborts pending transitions even when going back to the current state', inject(function ($state, $q, $timeout) {
337 | initStateTo(A);
338 | logEvents = true;
339 | $state.defaultErrorHandler(function () {});
340 |
341 | const superseded = $state.transitionTo(F, {});
342 | $q.flush();
343 | expect($state.current).toBe(A);
344 |
345 | $state.transitionTo(A, {});
346 | $q.flush();
347 | $timeout.flush();
348 | expect($state.current).toBe(A);
349 | expect(resolvedError(superseded)).toBeTruthy();
350 | expect(log).toBe('$stateChangeStart(F,A);');
351 | }));
352 |
353 | it('aborts pending transitions (last call wins)', inject(function ($state, $q, $timeout) {
354 | initStateTo(A);
355 | logEvents = true;
356 | $state.defaultErrorHandler(function () {});
357 |
358 | const superseded = $state.transitionTo(F, {});
359 | $q.flush();
360 |
361 | $state.transitionTo(C, {});
362 | $q.flush();
363 | $timeout.flush();
364 | expect($state.current).toBe(C);
365 | expect(resolvedError(superseded)).toBeTruthy();
366 | expect(log).toBe('$stateChangeStart(F,A);' + '$stateChangeStart(C,A);' + '$stateChangeSuccess(C,A);');
367 | }));
368 | });
369 | });
370 |
--------------------------------------------------------------------------------
/test/stateFiltersSpec.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | declare var inject;
3 |
4 | const module = angular['mock'].module;
5 |
6 | describe('isState filter', function () {
7 | beforeEach(module('ui.router'));
8 | beforeEach(
9 | module(function ($stateProvider) {
10 | $stateProvider.state('a', { url: '/' }).state('a.b', { url: '/b' }).state('with-param', { url: '/with/:param' });
11 | })
12 | );
13 |
14 | it('should return true if the current state exactly matches the input state', inject(function (
15 | $parse,
16 | $state,
17 | $q,
18 | $rootScope
19 | ) {
20 | $state.go('a');
21 | $q.flush();
22 | expect($parse('"a" | isState')($rootScope)).toBe(true);
23 | }));
24 |
25 | it('should return false if the current state does not exactly match the input state', inject(function (
26 | $parse,
27 | $q,
28 | $state,
29 | $rootScope
30 | ) {
31 | $state.go('a.b');
32 | $q.flush();
33 | expect($parse('"a" | isState')($rootScope)).toBe(false);
34 | }));
35 |
36 | it('should return true if the current state and param matches the input state', inject(function (
37 | $parse,
38 | $state,
39 | $q,
40 | $rootScope
41 | ) {
42 | $state.go('with-param', { param: 'a' });
43 | $q.flush();
44 | expect($parse('"with-param" | isState: {param: "a"}')($rootScope)).toBe(true);
45 | }));
46 |
47 | it('should return false if the current state and param does not match the input state', inject(function (
48 | $parse,
49 | $state,
50 | $q,
51 | $rootScope
52 | ) {
53 | $state.go('with-param', { param: 'b' });
54 | $q.flush();
55 | expect($parse('"with-param" | isState: {param: "a"}')($rootScope)).toBe(false);
56 | }));
57 | });
58 |
59 | describe('includedByState filter', function () {
60 | beforeEach(module('ui.router'));
61 | beforeEach(
62 | module(function ($stateProvider) {
63 | $stateProvider
64 | .state('a', { url: '/' })
65 | .state('a.b', { url: '/b' })
66 | .state('c', { url: '/c' })
67 | .state('d', { url: '/d/:id' });
68 | })
69 | );
70 |
71 | it('should return true if the current state exactly matches the input state', inject(function (
72 | $parse,
73 | $state,
74 | $q,
75 | $rootScope
76 | ) {
77 | $state.go('a');
78 | $q.flush();
79 | expect($parse('"a" | includedByState')($rootScope)).toBe(true);
80 | }));
81 |
82 | it('should return true if the current state includes the input state', inject(function (
83 | $parse,
84 | $state,
85 | $q,
86 | $rootScope
87 | ) {
88 | $state.go('a.b');
89 | $q.flush();
90 | expect($parse('"a" | includedByState')($rootScope)).toBe(true);
91 | }));
92 |
93 | it('should return false if the current state does not include input state', inject(function (
94 | $parse,
95 | $state,
96 | $q,
97 | $rootScope
98 | ) {
99 | $state.go('c');
100 | $q.flush();
101 | expect($parse('"a" | includedByState')($rootScope)).toBe(false);
102 | }));
103 |
104 | it('should return true if the current state include input state and params', inject(function (
105 | $parse,
106 | $state,
107 | $q,
108 | $rootScope
109 | ) {
110 | $state.go('d', { id: 123 });
111 | $q.flush();
112 | expect($parse('"d" | includedByState:{ id: 123 }')($rootScope)).toBe(true);
113 | }));
114 |
115 | it('should return false if the current state does not include input state and params', inject(function (
116 | $parse,
117 | $state,
118 | $q,
119 | $rootScope
120 | ) {
121 | $state.go('d', { id: 2377 });
122 | $q.flush();
123 | expect($parse('"d" | includedByState:{ id: 123 }')($rootScope)).toBe(false);
124 | }));
125 | });
126 |
--------------------------------------------------------------------------------
/test/templateFactorySpec.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | import { UIRouter } from '@uirouter/core';
3 |
4 | declare let inject;
5 |
6 | const module = angular['mock'].module;
7 |
8 | describe('templateFactory', function () {
9 | beforeEach(module('ui.router'));
10 |
11 | it('exists', inject(function ($templateFactory) {
12 | expect($templateFactory).toBeDefined();
13 | }));
14 |
15 | if (angular.version.minor >= 3) {
16 | // Post 1.2, there is a $templateRequest and a $sce service
17 | describe('should follow $sce policy and', function () {
18 | it('accepts relative URLs', inject(function ($templateFactory, $httpBackend, $sce) {
19 | $httpBackend.expectGET('views/view.html').respond(200, 'template!');
20 | $templateFactory.fromUrl('views/view.html');
21 | $httpBackend.flush();
22 | }));
23 |
24 | it('rejects untrusted URLs', inject(function ($templateFactory, $httpBackend, $sce) {
25 | let error = 'No error thrown';
26 | try {
27 | $templateFactory.fromUrl('http://evil.com/views/view.html');
28 | } catch (e) {
29 | error = e.message;
30 | }
31 | expect(error).toMatch(/sce:insecurl/);
32 | }));
33 |
34 | it('accepts explicitly trusted URLs', inject(function ($templateFactory, $httpBackend, $sce) {
35 | $httpBackend.expectGET('http://evil.com/views/view.html').respond(200, 'template!');
36 | $templateFactory.fromUrl($sce.trustAsResourceUrl('http://evil.com/views/view.html'));
37 | $httpBackend.flush();
38 | }));
39 | });
40 | }
41 |
42 | if (angular.version.minor <= 2) {
43 | // 1.2 and before will use directly $http
44 | it('does not restrict URL loading', inject(function ($templateFactory, $httpBackend) {
45 | $httpBackend.expectGET('http://evil.com/views/view.html').respond(200, 'template!');
46 | $templateFactory.fromUrl('http://evil.com/views/view.html');
47 | $httpBackend.flush();
48 |
49 | $httpBackend.expectGET('data:text/html,foo').respond(200, 'template!');
50 | $templateFactory.fromUrl('data:text/html,foo');
51 | $httpBackend.flush();
52 | }));
53 |
54 | // Behavior not kept in >1.2 with $templateRequest
55 | it('should request templates as text/html', inject(function ($templateFactory, $httpBackend) {
56 | $httpBackend
57 | .expectGET('views/view.html', function (headers) {
58 | return headers.Accept === 'text/html';
59 | })
60 | .respond(200);
61 | $templateFactory.fromUrl('views/view.html');
62 | $httpBackend.flush();
63 | }));
64 | }
65 |
66 | describe('templateFactory with forced use of $http service', function () {
67 | beforeEach(function () {
68 | angular.module('forceHttpInTemplateFactory', []).config(function ($templateFactoryProvider) {
69 | $templateFactoryProvider.useHttpService(true);
70 | });
71 | module('ui.router');
72 | module('forceHttpInTemplateFactory');
73 | });
74 |
75 | it('does not restrict URL loading', inject(function ($templateFactory, $httpBackend) {
76 | $httpBackend.expectGET('http://evil.com/views/view.html').respond(200, 'template!');
77 | $templateFactory.fromUrl('http://evil.com/views/view.html');
78 | $httpBackend.flush();
79 |
80 | $httpBackend.expectGET('data:text/html,foo').respond(200, 'template!');
81 | $templateFactory.fromUrl('data:text/html,foo');
82 | $httpBackend.flush();
83 | }));
84 | });
85 |
86 | if (angular.version.minor >= 5) {
87 | describe('component template builder', () => {
88 | let router: UIRouter, el, rootScope;
89 | const cmp = { template: 'hi' };
90 |
91 | beforeEach(() => {
92 | const mod = angular.module('foo', []);
93 | mod.component('myComponent', cmp);
94 | mod.component('dataComponent', cmp);
95 | mod.component('xComponent', cmp);
96 | });
97 | beforeEach(module('foo'));
98 |
99 | beforeEach(inject(($uiRouter, $compile, $rootScope) => {
100 | router = $uiRouter;
101 | rootScope = $rootScope;
102 | el = $compile(angular.element('
'))($rootScope.$new());
103 | }));
104 |
105 | it('should not prefix the components dom element with anything', () => {
106 | router.stateRegistry.register({ name: 'cmp', component: 'myComponent' });
107 | router.stateService.go('cmp');
108 | rootScope.$digest();
109 | expect(el.html()).toMatch(/\ {
113 | router.stateRegistry.register({ name: 'cmp', component: 'dataComponent' });
114 | router.stateService.go('cmp');
115 | rootScope.$digest();
116 | expect(el.html()).toMatch(/\ {
120 | router.stateRegistry.register({ name: 'cmp', component: 'xComponent' });
121 | router.stateService.go('cmp');
122 | rootScope.$digest();
123 | expect(el.html()).toMatch(/\ $1/package.json
16 | jq ".typescript += { \"typescript$1\": \"./test/typescript/$1\" }" < ../../downstream_projects.json > temp.json
17 | mv temp.json ../../downstream_projects.json
18 |
--------------------------------------------------------------------------------
/test/typescript/template/index.ts:
--------------------------------------------------------------------------------
1 | import { UIRouter } from '@uirouter/angularjs';
2 | console.log(UIRouter);
3 |
--------------------------------------------------------------------------------
/test/typescript/template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Test against Typescript VERSION",
3 | "scripts": {
4 | "test": "tsc"
5 | },
6 | "dependencies": {
7 | "@types/angular": "latest",
8 | "@uirouter/angularjs": "latest",
9 | "typescript": "VERSION"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/typescript/template/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "module": "commonjs",
5 | "lib": ["es6", "dom"],
6 | "noImplicitAny": true,
7 | "noEmit": true,
8 | "target": "es5",
9 | "typeRoots": ["node_modules/@types"]
10 | },
11 | "files": ["index.ts"]
12 | }
13 |
--------------------------------------------------------------------------------
/test/urlRouterSpec.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | import { ILocationService, ILocationProvider } from 'angular';
3 | import { html5Compat } from './util/testUtilsNg1';
4 | import { UrlRule, UrlMatcher, UrlMatcherFactory, UrlRouter, StateService, UIRouter } from '../src/index';
5 | import { UrlRouterProvider } from '../src/urlRouterProvider';
6 |
7 | declare var inject;
8 |
9 | const module = angular['mock'].module;
10 |
11 | describe('UrlRouter', function () {
12 | let router: UIRouter;
13 | let $urp: UrlRouterProvider,
14 | $lp: ILocationProvider,
15 | $umf: UrlMatcherFactory,
16 | $s: StateService,
17 | $ur: UrlRouter,
18 | location: ILocationService,
19 | match,
20 | scope;
21 |
22 | describe('provider', function () {
23 | beforeEach(function () {
24 | angular.module('ui.router.router.test', []).config(function ($uiRouterProvider) {
25 | router = $uiRouterProvider;
26 | $umf = router.urlMatcherFactory;
27 | $urp = router.urlRouterProvider;
28 | $urp.deferIntercept();
29 | });
30 |
31 | module('ui.router.router', 'ui.router.router.test');
32 |
33 | inject(function ($rootScope, $location) {
34 | scope = $rootScope.$new();
35 | location = $location;
36 | });
37 | });
38 |
39 | it('should throw on non-function rules', function () {
40 | expect(function () {
41 | $urp.rule(null);
42 | }).toThrowError("'rule' must be a function");
43 | expect(function () {
44 | $urp.otherwise(null);
45 | }).toThrowError("'rule' must be a string or function");
46 | });
47 |
48 | it('should allow location changes to be deferred', inject(function ($urlRouter, $location, $rootScope) {
49 | const log = [];
50 |
51 | $urp.rule(function ($injector, $location) {
52 | log.push($location.path());
53 | return null;
54 | });
55 |
56 | $location.path('/foo');
57 | $rootScope.$broadcast('$locationChangeSuccess');
58 |
59 | expect(log).toEqual([]);
60 |
61 | $urlRouter.listen();
62 | $rootScope.$broadcast('$locationChangeSuccess');
63 |
64 | expect(log).toEqual(['/foo']);
65 | }));
66 | });
67 |
68 | describe('service', function () {
69 | beforeEach(function () {
70 | angular.module('ui.router.router.test', []).config(function ($uiRouterProvider, $locationProvider) {
71 | router = $uiRouterProvider;
72 | $umf = router.urlMatcherFactory;
73 | $urp = router.urlRouterProvider;
74 | $lp = $locationProvider;
75 | $locationProvider.hashPrefix('');
76 |
77 | $urp
78 | .rule(function ($injector, $location) {
79 | const path = $location.path();
80 | if (!/baz/.test(path)) return;
81 | return path.replace('baz', 'b4z');
82 | })
83 | .when('/foo/:param', function ($match) {
84 | match = ['/foo/:param', $match];
85 | })
86 | .when('/bar', function ($match) {
87 | match = ['/bar', $match];
88 | });
89 | });
90 |
91 | module('ui.router.router', 'ui.router.router.test');
92 |
93 | inject(function ($rootScope, $location, $injector) {
94 | scope = $rootScope.$new();
95 | location = $location;
96 | $ur = $injector.invoke($urp['$get'], $urp);
97 | $s = $injector.get('$sniffer');
98 | $s['history'] = true;
99 | });
100 | });
101 |
102 | it('should execute rewrite rules', function () {
103 | location.path('/foo');
104 | scope.$emit('$locationChangeSuccess');
105 | expect(location.path()).toBe('/foo');
106 |
107 | location.path('/baz');
108 | scope.$emit('$locationChangeSuccess');
109 | expect(location.path()).toBe('/b4z');
110 | });
111 |
112 | it('should keep otherwise last', function () {
113 | $urp.otherwise('/otherwise');
114 |
115 | location.path('/lastrule');
116 | scope.$emit('$locationChangeSuccess');
117 | expect(location.path()).toBe('/otherwise');
118 |
119 | $urp.when('/lastrule', function ($match) {
120 | match = ['/lastrule', $match];
121 | });
122 |
123 | location.path('/lastrule');
124 | scope.$emit('$locationChangeSuccess');
125 | expect(location.path()).toBe('/lastrule');
126 | });
127 |
128 | it('can be cancelled by preventDefault() in $locationChangeSuccess', inject(function () {
129 | let called;
130 | location.path('/baz');
131 | scope.$on('$locationChangeSuccess', function (ev) {
132 | ev.preventDefault();
133 | called = true;
134 | });
135 | scope.$emit('$locationChangeSuccess');
136 | expect(called).toBeTruthy();
137 | expect(location.path()).toBe('/baz');
138 | }));
139 |
140 | it('can be deferred and updated in $locationChangeSuccess', inject(function ($urlRouter, $timeout) {
141 | let called;
142 | location.path('/baz');
143 | scope.$on('$locationChangeSuccess', function (ev) {
144 | ev.preventDefault();
145 | called = true;
146 | $timeout(() => $urlRouter.sync(), 2000);
147 | });
148 | scope.$emit('$locationChangeSuccess');
149 | $timeout.flush();
150 | expect(called).toBeTruthy();
151 | expect(location.path()).toBe('/b4z');
152 | }));
153 |
154 | it('rule should return a deregistration function', function () {
155 | let count = 0;
156 | let rule: UrlRule = {
157 | match: () => count++,
158 | handler: (match) => match,
159 | matchPriority: () => 0,
160 | $id: 0,
161 | priority: 0,
162 | _group: 0,
163 | type: 'RAW',
164 | };
165 | const dereg = $ur.rule(rule as any);
166 |
167 | $ur.sync();
168 | expect(count).toBe(1);
169 | $ur.sync();
170 | expect(count).toBe(2);
171 |
172 | dereg();
173 | $ur.sync();
174 | expect(count).toBe(2);
175 | });
176 |
177 | it('removeRule should remove a previously registered rule', function () {
178 | let count = 0;
179 | let rule: UrlRule = {
180 | match: () => count++,
181 | handler: (match) => match,
182 | matchPriority: () => 0,
183 | $id: 0,
184 | priority: 0,
185 | _group: 0,
186 | type: 'RAW',
187 | };
188 | $ur.rule(rule as any);
189 |
190 | $ur.sync();
191 | expect(count).toBe(1);
192 | $ur.sync();
193 | expect(count).toBe(2);
194 |
195 | $ur.removeRule(rule);
196 | $ur.sync();
197 | expect(count).toBe(2);
198 | });
199 |
200 | describe('location updates', function () {
201 | it('can push location changes', inject(function ($urlRouter) {
202 | const spy = spyOn(router.locationService, 'url');
203 | $urlRouter.push($umf.compile('/hello/:name'), { name: 'world' });
204 | expect(spy).toHaveBeenCalled();
205 | expect(spy.calls.mostRecent().args[0]).toBe('/hello/world');
206 | }));
207 |
208 | it('can push a replacement location', inject(function ($urlRouter, $location) {
209 | const spy = spyOn(router.locationService, 'url');
210 | $urlRouter.push($umf.compile('/hello/:name'), { name: 'world' }, { replace: true });
211 | expect(spy).toHaveBeenCalled();
212 | expect(spy.calls.mostRecent().args.slice(0, 2)).toEqual(['/hello/world', true]);
213 | }));
214 |
215 | it('can push location changes with no parameters', inject(function ($urlRouter, $location) {
216 | const spy = spyOn(router.locationService, 'url');
217 | $urlRouter.push($umf.compile('/hello/:name', { state: { params: { name: '' } } }));
218 | expect(spy).toHaveBeenCalled();
219 | expect(spy.calls.mostRecent().args[0]).toBe('/hello/');
220 | }));
221 |
222 | it('can push an empty url', inject(function ($urlRouter, $location) {
223 | const spy = spyOn(router.locationService, 'url');
224 | $urlRouter.push($umf.compile('/{id}', { state: { params: { id: { squash: true, value: null } } } }));
225 | expect(spy).toHaveBeenCalled();
226 | expect(spy.calls.mostRecent().args[0]).toBe('');
227 | }));
228 |
229 | // Angular 1.2 doesn't seem to support $location.url("")
230 | if (angular.version.minor >= 3) {
231 | // Test for https://github.com/angular-ui/ui-router/issues/3563
232 | it('updates url after an empty url is pushed', inject(function ($urlRouter, $location) {
233 | $lp.html5Mode(false);
234 | const spy = spyOn(router.locationService, 'url').and.callThrough();
235 | $urlRouter.push($umf.compile('/foobar'));
236 | expect(spy.calls.mostRecent().args[0]).toBe('/foobar');
237 | $urlRouter.push($umf.compile('/{id}', { state: { params: { id: { squash: true, value: null } } } }));
238 | expect(spy.calls.mostRecent().args[0]).toBe('');
239 | expect(router.locationService.url()).toBe('/');
240 | }));
241 |
242 | // Test #2 for https://github.com/angular-ui/ui-router/issues/3563
243 | it('updates html5mode url after an empty url is pushed', inject(function ($urlRouter, $location) {
244 | $lp.html5Mode(true);
245 | const spy = spyOn(router.locationService, 'url').and.callThrough();
246 | $urlRouter.push($umf.compile('/foobar'));
247 | expect(spy.calls.mostRecent().args[0]).toBe('/foobar');
248 | $urlRouter.push($umf.compile('/{id}', { state: { params: { id: { squash: true, value: null } } } }));
249 | expect(spy.calls.mostRecent().args[0]).toBe('');
250 | expect(router.locationService.url()).toBe('/');
251 | }));
252 | }
253 |
254 | it('can push location changes that include a #fragment', inject(function ($urlRouter, $location) {
255 | // html5mode disabled
256 | $lp.html5Mode(false);
257 | expect(html5Compat($lp.html5Mode())).toBe(false);
258 | $urlRouter.push($umf.compile('/hello/:name'), { name: 'world', '#': 'frag' });
259 | expect($location.url()).toBe('/hello/world#frag');
260 | expect($location.hash()).toBe('frag');
261 |
262 | // html5mode enabled
263 | $lp.html5Mode(true);
264 | expect(html5Compat($lp.html5Mode())).toBe(true);
265 | $urlRouter.push($umf.compile('/hello/:name'), { name: 'world', '#': 'frag' });
266 | expect($location.url()).toBe('/hello/world#frag');
267 | expect($location.hash()).toBe('frag');
268 | }));
269 |
270 | it('can read and sync a copy of location URL', inject(function ($urlRouter, $location) {
271 | $location.url('/old');
272 |
273 | spyOn(router.locationService, 'url').and.callThrough();
274 | $urlRouter.update(true);
275 | expect(router.locationService.url).toHaveBeenCalled();
276 |
277 | $location.url('/new');
278 | $urlRouter.update();
279 |
280 | expect($location.url()).toBe('/old');
281 | }));
282 |
283 | it('can read and sync a copy of location URL including query params', inject(function ($urlRouter, $location) {
284 | $location.url('/old?param=foo');
285 |
286 | spyOn(router.locationService, 'url').and.callThrough();
287 | $urlRouter.update(true);
288 | expect(router.locationService.url).toHaveBeenCalled();
289 |
290 | $location.url('/new?param=bar');
291 | $urlRouter.update();
292 |
293 | expect($location.url()).toBe('/old?param=foo');
294 | }));
295 | });
296 |
297 | describe('URL generation', function () {
298 | it('should return null when UrlMatcher rejects parameters', inject(function ($urlRouter: UrlRouter) {
299 | $umf.type('custom', { is: (val) => val === 1138 });
300 | const matcher = $umf.compile('/foo/{param:custom}');
301 |
302 | expect($urlRouter.href(matcher, { param: 1138 })).toBe('#/foo/1138');
303 | expect($urlRouter.href(matcher, { param: 5 })).toBeNull();
304 | }));
305 |
306 | it('should handle the new html5Mode object config from Angular 1.3', inject(function ($urlRouter: UrlRouter) {
307 | $lp.html5Mode({
308 | enabled: false,
309 | });
310 |
311 | expect($urlRouter.href($umf.compile('/hello'))).toBe('#/hello');
312 | }));
313 |
314 | it('should return URLs with #fragments', inject(function ($urlRouter: UrlRouter) {
315 | // html5mode disabled
316 | $lp.html5Mode(false);
317 | expect(html5Compat($lp.html5Mode())).toBe(false);
318 | expect($urlRouter.href($umf.compile('/hello/:name'), { name: 'world', '#': 'frag' })).toBe(
319 | '#/hello/world#frag'
320 | );
321 |
322 | // html5mode enabled
323 | $lp.html5Mode(true);
324 | expect(html5Compat($lp.html5Mode())).toBe(true);
325 | expect($urlRouter.href($umf.compile('/hello/:name'), { name: 'world', '#': 'frag' })).toBe('/hello/world#frag');
326 | }));
327 |
328 | it('should return URLs with #fragments when html5Mode is true & browser does not support pushState', inject(function (
329 | $urlRouter: UrlRouter
330 | ) {
331 | $lp.html5Mode(true);
332 | $s['history'] = false;
333 | expect(html5Compat($lp.html5Mode())).toBe(true);
334 | expect($urlRouter.href($umf.compile('/hello/:name'), { name: 'world', '#': 'frag' })).toBe(
335 | '#/hello/world#frag'
336 | );
337 | }));
338 | });
339 | });
340 | });
341 |
--------------------------------------------------------------------------------
/test/util/testUtilsNg1.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 |
3 | // Promise testing support
4 | angular.module('ngMock').config(function ($provide, $locationProvider) {
5 | var oldFn = $locationProvider.html5Mode;
6 | $locationProvider.html5Mode = function () {
7 | var retval = oldFn.apply($locationProvider, arguments);
8 | return angular.isDefined(retval) && angular.isDefined(retval.enabled) ? retval.enabled : retval;
9 | };
10 |
11 | $provide.decorator('$q', function ($delegate, $rootScope) {
12 | $delegate.flush = function () {
13 | $rootScope.$digest();
14 | };
15 |
16 | // Add callbacks to the promise that expose the resolved value/error
17 | function expose(promise) {
18 | // Don't add hooks to the same promise twice (shouldn't happen anyway)
19 | if (!promise.hasOwnProperty('$$resolved')) {
20 | promise.$$resolved = false;
21 | promise.then(
22 | function (value) {
23 | promise.$$resolved = { success: true, value: value };
24 | },
25 | function (error) {
26 | promise.$$resolved = { success: false, error: error };
27 | }
28 | );
29 |
30 | // We need to expose() any then()ed promises recursively
31 | var qThen = promise.then;
32 | promise.then = function () {
33 | return expose(qThen.apply(this, arguments));
34 | };
35 | }
36 | return promise;
37 | }
38 |
39 | // Wrap functions that return a promise
40 | angular.forEach(['when', 'all', 'reject'], function (name) {
41 | var qFunc = $delegate[name];
42 | $delegate[name] = function () {
43 | return expose(qFunc.apply(this, arguments));
44 | };
45 | });
46 |
47 | // Wrap defer()
48 | var qDefer = $delegate.defer;
49 | $delegate.defer = function () {
50 | var deferred = qDefer();
51 | expose(deferred.promise);
52 | return deferred;
53 | };
54 |
55 | return $delegate;
56 | });
57 | });
58 |
59 | try {
60 | // Animation testing support
61 | angular.module('mock.animate').config(function ($provide) {
62 | $provide.decorator('$animate', function ($delegate) {
63 | $delegate.flush = function () {
64 | while (this.queue.length > 0) {
65 | this.flushNext(this.queue[0].method);
66 | }
67 | };
68 | return $delegate;
69 | });
70 | });
71 | } catch (e) {}
72 |
73 | export function testablePromise(promise) {
74 | if (!promise || !promise.then) throw new Error('Expected a promise, but got ' + jasmine.pp(promise) + '.');
75 | if (!angular.isDefined(promise.$$resolved)) throw new Error('Promise has not been augmented by ngMock');
76 | return promise;
77 | }
78 |
79 | export function resolvedPromise(promise) {
80 | var result = testablePromise(promise).$$resolved;
81 | if (!result) throw new Error('Promise is not resolved yet');
82 | return result;
83 | }
84 |
85 | export function resolvedValue(promise) {
86 | var result = resolvedPromise(promise);
87 | if (!result.success) throw result.error;
88 | return result.value;
89 | }
90 |
91 | export function resolvedError(promise) {
92 | var result = resolvedPromise(promise);
93 | if (result.success) throw new Error('Promise was expected to fail but returned ' + jasmine.pp(result.value) + '.');
94 | return result.error;
95 | }
96 |
97 | // Misc test utils
98 | export function caught(fn) {
99 | try {
100 | fn();
101 | return null;
102 | } catch (e) {
103 | return e;
104 | }
105 | }
106 |
107 | // Usage of this helper should be replaced with a custom matcher in jasmine 2.0+
108 | export function obj(object) {
109 | var o = {};
110 | angular.forEach(object, function (val, i) {
111 | const key = (i as any) as string;
112 | if (!/^\$/.test(key) && key != '#') o[key] = val;
113 | });
114 | return o;
115 | }
116 |
117 | export function html5Compat(html5mode) {
118 | return angular.isObject(html5mode) && html5mode.hasOwnProperty('enabled') ? html5mode.enabled : html5mode;
119 | }
120 |
121 | /**
122 | * The ng1 $exceptionHandler from angular-mocks will re-throw any exceptions thrown in a Promise.
123 | * This chunk of code decorates the handler, allowing a test to disable that behavior.
124 | * Inject $exceptionHandler and set `$exceptionHandler.disabled = true`
125 | */
126 | export function decorateExceptionHandler($exceptionHandlerProvider) {
127 | var $get = $exceptionHandlerProvider.$get;
128 |
129 | $exceptionHandlerProvider.$get = function () {
130 | var realHandler = $get.apply($exceptionHandlerProvider, arguments);
131 | function passThrough(e) {
132 | if (!passThrough['disabled']) {
133 | realHandler.apply(null, arguments);
134 | }
135 | }
136 | return passThrough;
137 | };
138 | }
139 |
140 | beforeEach(angular['mock'].module('ui.router.compat'));
141 |
--------------------------------------------------------------------------------
/test/viewHookSpec.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | import { StateService } from '@uirouter/core';
3 |
4 | declare var inject;
5 |
6 | describe('view hooks', () => {
7 | let app,
8 | ctrl,
9 | $state: StateService,
10 | $q,
11 | $timeout: angular.ITimeoutService,
12 | log = '';
13 | const component = {
14 | bindings: { cmpdata: '<' },
15 | template: '{{$ctrl.cmpdata}}',
16 | };
17 |
18 | const directive = {
19 | restrict: 'E',
20 | scope: { cmpdata: '=' },
21 | bindToController: true,
22 | controller: function () {},
23 | controllerAs: '$ctrl',
24 | template: '{{$ctrl.cmpdata}}',
25 | };
26 |
27 | beforeEach(() => {
28 | app = angular.module('viewhooks', []);
29 | });
30 |
31 | beforeEach(
32 | angular['mock'].module(($stateProvider) => {
33 | ctrl = function controller() {
34 | this.data = 'DATA';
35 | };
36 |
37 | if (angular.version.minor >= 5) {
38 | app.component('foo', angular.extend({}, component, { controller: ctrl }));
39 | app.component('bar', angular.extend({}, component));
40 | app.component('baz', angular.extend({}, component));
41 | } else if (angular.version.minor >= 2) {
42 | app.directive('foo', () => angular.extend({}, directive, { controller: ctrl }));
43 | app.directive('bar', () => angular.extend({}, directive));
44 | app.directive('baz', () => angular.extend({}, directive));
45 | }
46 |
47 | $stateProvider.state({ name: 'foo', url: '/foo', component: 'foo' });
48 | $stateProvider.state({ name: 'bar', url: '/bar', component: 'bar' });
49 | $stateProvider.state({ name: 'baz', url: '/baz', component: 'baz' });
50 | $stateProvider.state({ name: 'redirect', redirectTo: 'baz' });
51 | })
52 | );
53 |
54 | beforeEach(angular['mock'].module('viewhooks', 'ui.router'));
55 |
56 | beforeEach(inject((_$state_, _$q_, _$timeout_, $compile, $rootScope) => {
57 | $state = _$state_;
58 | $q = _$q_;
59 | $timeout = _$timeout_;
60 | $compile('
')($rootScope.$new());
61 | }));
62 |
63 | describe('uiCanExit', () => {
64 | beforeEach(() => {
65 | log = '';
66 | });
67 |
68 | const initial = () => {
69 | $state.go('foo');
70 | $q.flush();
71 | $timeout.flush();
72 | expect(log).toBe('');
73 | expect($state.current.name).toBe('foo');
74 | };
75 |
76 | it("can cancel a transition that would exit the view's state by returning false", () => {
77 | $state.defaultErrorHandler(function () {});
78 | ctrl.prototype.uiCanExit = function () {
79 | log += 'canexit;';
80 | return false;
81 | };
82 | initial();
83 |
84 | $state.go('bar');
85 | $q.flush();
86 | $timeout.flush();
87 | expect(log).toBe('canexit;');
88 | expect($state.current.name).toBe('foo');
89 | });
90 |
91 | it('can allow the transition by returning true', () => {
92 | ctrl.prototype.uiCanExit = function () {
93 | log += 'canexit;';
94 | return true;
95 | };
96 | initial();
97 |
98 | $state.go('bar');
99 | $q.flush();
100 | $timeout.flush();
101 | expect(log).toBe('canexit;');
102 | expect($state.current.name).toBe('bar');
103 | });
104 |
105 | it('can allow the transition by returning nothing', () => {
106 | ctrl.prototype.uiCanExit = function () {
107 | log += 'canexit;';
108 | };
109 | initial();
110 |
111 | $state.go('bar');
112 | $q.flush();
113 | $timeout.flush();
114 | expect(log).toBe('canexit;');
115 | expect($state.current.name).toBe('bar');
116 | });
117 |
118 | it('can redirect the transition', () => {
119 | ctrl.prototype.uiCanExit = function (trans) {
120 | log += 'canexit;';
121 | return trans.router.stateService.target('baz');
122 | };
123 | initial();
124 |
125 | $state.go('bar');
126 | $q.flush();
127 | $timeout.flush();
128 | expect(log).toBe('canexit;');
129 | expect($state.current.name).toBe('baz');
130 | });
131 |
132 | it('can cancel the transition by returning a rejected promise', inject(($q, $state) => {
133 | ctrl.prototype.uiCanExit = function () {
134 | log += 'canexit;';
135 | return $q.reject('nope');
136 | };
137 | initial();
138 |
139 | $state.defaultErrorHandler(function () {});
140 | $state.go('bar');
141 | $q.flush();
142 | $timeout.flush();
143 | expect(log).toBe('canexit;');
144 | expect($state.current.name).toBe('foo');
145 | }));
146 |
147 | it('can wait for a promise and then reject the transition', inject(($timeout) => {
148 | $state.defaultErrorHandler(function () {});
149 | ctrl.prototype.uiCanExit = function () {
150 | log += 'canexit;';
151 | return $timeout(() => {
152 | log += 'delay;';
153 | return false;
154 | }, 1000);
155 | };
156 | initial();
157 |
158 | $state.go('bar');
159 | $q.flush();
160 | $timeout.flush();
161 | expect(log).toBe('canexit;delay;');
162 | expect($state.current.name).toBe('foo');
163 | }));
164 |
165 | it('can wait for a promise and then allow the transition', inject(($timeout) => {
166 | ctrl.prototype.uiCanExit = function () {
167 | log += 'canexit;';
168 | return $timeout(() => {
169 | log += 'delay;';
170 | }, 1000);
171 | };
172 | initial();
173 |
174 | $state.go('bar');
175 | $q.flush();
176 | $timeout.flush();
177 | expect(log).toBe('canexit;delay;');
178 | expect($state.current.name).toBe('bar');
179 | }));
180 |
181 | it("has 'this' bound to the controller", () => {
182 | ctrl.prototype.uiCanExit = function () {
183 | log += this.data;
184 | };
185 | initial();
186 |
187 | $state.go('bar');
188 | $q.flush();
189 | $timeout.flush();
190 | expect(log).toBe('DATA');
191 | expect($state.current.name).toBe('bar');
192 | });
193 |
194 | it('receives the new Transition as the first argument', () => {
195 | const _state = $state;
196 | ctrl.prototype.uiCanExit = function (trans) {
197 | log += 'canexit;';
198 | expect(typeof trans.treeChanges).toBe('function');
199 | expect(trans.injector().get('$state')).toBe(_state);
200 | };
201 | initial();
202 |
203 | $state.go('bar');
204 | $q.flush();
205 | $timeout.flush();
206 | expect(log).toBe('canexit;');
207 | expect($state.current.name).toBe('bar');
208 | });
209 |
210 | // Test for https://github.com/angular-ui/ui-router/issues/3308
211 | it('should trigger once when answered truthy even if redirected', () => {
212 | ctrl.prototype.uiCanExit = function () {
213 | log += 'canexit;';
214 | return true;
215 | };
216 | initial();
217 |
218 | $state.go('redirect');
219 | $q.flush();
220 | $timeout.flush();
221 | expect(log).toBe('canexit;');
222 | expect($state.current.name).toBe('baz');
223 | });
224 |
225 | // Test for https://github.com/angular-ui/ui-router/issues/3308
226 | it('should trigger only once if returns a redirect', () => {
227 | ctrl.prototype.uiCanExit = function () {
228 | log += 'canexit;';
229 | return $state.target('bar');
230 | };
231 | initial();
232 |
233 | $state.go('redirect');
234 | $q.flush();
235 | $timeout.flush();
236 | expect(log).toBe('canexit;');
237 | expect($state.current.name).toBe('bar');
238 | });
239 | });
240 | });
241 |
--------------------------------------------------------------------------------
/test/viewScrollSpec.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | declare var inject;
3 |
4 | const module = angular['mock'].module;
5 |
6 | describe('uiView', function () {
7 | 'use strict';
8 |
9 | beforeEach(module('ui.router'));
10 |
11 | describe('scrollIntoView', function () {
12 | let elem;
13 |
14 | beforeEach(function () {
15 | elem = [{ scrollIntoView: jasmine.createSpy('scrollIntoView') }];
16 | });
17 |
18 | it('should scroll element into view after timeout', inject(function ($uiViewScroll, $timeout) {
19 | $uiViewScroll(elem);
20 | expect(elem[0].scrollIntoView).not.toHaveBeenCalled();
21 |
22 | $timeout.flush();
23 | expect(elem[0].scrollIntoView).toHaveBeenCalled();
24 | }));
25 |
26 | it('should return the promise from the timeout', inject(function ($uiViewScroll, $timeout) {
27 | const promise = $uiViewScroll(elem);
28 |
29 | $timeout.flush();
30 | expect(elem[0].scrollIntoView).toHaveBeenCalled();
31 | expect(promise).toBeDefined();
32 | }));
33 | });
34 |
35 | describe('useAnchorScroll', function () {
36 | beforeEach(
37 | module(function ($provide, $uiViewScrollProvider) {
38 | $provide.decorator('$anchorScroll', function ($delegate) {
39 | return jasmine.createSpy('$anchorScroll');
40 | });
41 | $uiViewScrollProvider.useAnchorScroll();
42 | })
43 | );
44 |
45 | it('should call $anchorScroll', inject(function ($uiViewScroll, $anchorScroll) {
46 | $uiViewScroll();
47 | expect($anchorScroll).toHaveBeenCalled();
48 | }));
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/test/viewSpec.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | import { tail, curry, PathNode, PathUtils, ViewService, StateMatcher, StateBuilder, StateObject } from '@uirouter/core';
3 | import { ng1ViewsBuilder, getNg1ViewConfigFactory } from '../src/statebuilders/views';
4 | import { Ng1StateDeclaration } from '../src/interface';
5 | declare var inject;
6 |
7 | describe('view', function () {
8 | let scope, $compile, $injector, elem, $controllerProvider, $urlMatcherFactoryProvider;
9 | let root: StateObject, states: { [key: string]: StateObject };
10 |
11 | beforeEach(
12 | angular.mock.module('ui.router', function (_$provide_, _$controllerProvider_, _$urlMatcherFactoryProvider_) {
13 | _$provide_.factory('foo', function () {
14 | return 'Foo';
15 | });
16 | $controllerProvider = _$controllerProvider_;
17 | $urlMatcherFactoryProvider = _$urlMatcherFactoryProvider_;
18 | })
19 | );
20 |
21 | let register;
22 | const registerState = curry(function (_states, stateBuilder, config) {
23 | const state = StateObject.create(config);
24 | const built: StateObject = stateBuilder.build(state);
25 | return (_states[built.name] = built);
26 | });
27 |
28 | beforeEach(inject(function ($rootScope, _$compile_, _$injector_) {
29 | scope = $rootScope.$new();
30 | $compile = _$compile_;
31 | $injector = _$injector_;
32 | elem = angular.element('');
33 |
34 | states = {};
35 | const matcher = new StateMatcher(states);
36 | const stateBuilder = new StateBuilder(matcher, $urlMatcherFactoryProvider);
37 | stateBuilder.builder('views', ng1ViewsBuilder);
38 | register = registerState(states, stateBuilder);
39 | root = register({ name: '' });
40 | }));
41 |
42 | describe('controller handling', function () {
43 | let state, path: PathNode[], ctrlExpression;
44 | beforeEach(() => {
45 | ctrlExpression = null;
46 | const stateDeclaration: Ng1StateDeclaration = {
47 | name: 'foo',
48 | template: 'test',
49 | controllerProvider: [
50 | 'foo',
51 | function (/* $stateParams, */ foo) {
52 | // todo: reimplement localized $stateParams
53 | ctrlExpression = /* $stateParams.type + */ foo + 'Controller as foo';
54 | return ctrlExpression;
55 | },
56 | ],
57 | };
58 |
59 | state = register(stateDeclaration);
60 | const $view = new ViewService(null);
61 | $view._pluginapi._viewConfigFactory('ng1', getNg1ViewConfigFactory());
62 |
63 | const _states = [root, state];
64 | path = _states.map((_state) => new PathNode(_state));
65 | PathUtils.applyViewConfigs($view, path, _states);
66 | });
67 |
68 | it('uses the controllerProvider to get controller dynamically', inject(function ($view, $q) {
69 | $controllerProvider.register('AcmeFooController', function ($scope, foo) {});
70 | elem.append($compile('
')(scope));
71 |
72 | const view = tail(path).views[0];
73 | view.load();
74 | $q.flush();
75 | expect(ctrlExpression).toEqual('FooController as foo');
76 | }));
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/tsconfig.docgen.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "baseUrl": ".",
6 | "paths": {
7 | "@uirouter/core": ["src/includes/@uirouter/core/src"],
8 | "@uirouter/core/lib/globals": ["src/includes/@uirouter/core/src/globals"],
9 | "@uirouter/core/lib/state/stateRegistry": ["src/includes/@uirouter/core/src/state/stateRegistry"],
10 | "@uirouter/core/lib/router": ["src/includes/@uirouter/core/src/router"]
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "emitDecoratorMetadata": true,
4 | "experimentalDecorators": true,
5 | "moduleResolution": "node",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "lib": ["es6", "dom"],
9 | "allowSyntheticDefaultImports": true,
10 | "outDir": "lib",
11 | "declaration": true,
12 | "skipLibCheck": true,
13 | "sourceMap": true,
14 | "inlineSources": true
15 | },
16 | "files": ["src/index.ts", "src/legacy/stateEvents.ts", "src/legacy/resolveService.ts"]
17 | }
18 |
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@uirouter/angularjs",
3 | "readme": "README.md",
4 | "out": "_doc",
5 | "stripInternal": true,
6 | "excludeNotExported": true,
7 | "excludePrivate": true
8 | }
9 |
--------------------------------------------------------------------------------