├── .eslintrc.json
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── angular-ui-router-default.js
├── bower.json
├── build.txt
├── files.conf.js
├── gulpfile.js
├── index.d.ts
├── karma.conf.js
├── package.json
├── sample
├── app
│ ├── app.js
│ └── contacts
│ │ ├── contacts-service.js
│ │ ├── contacts.detail.html
│ │ ├── contacts.detail.item.edit.html
│ │ ├── contacts.detail.item.html
│ │ ├── contacts.html
│ │ ├── contacts.js
│ │ └── contacts.list.html
├── assets
│ └── contacts.json
├── common
│ └── utils
│ │ └── utils-service.js
├── css
│ └── styles.css
└── index.html
├── src
└── angular-ui-router-default.ts
├── test
└── angular-ui-router-default.spec.ts
├── tsconfig.json
└── tslint.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint:recommended",
3 | "env": {
4 | "browser": true
5 | },
6 | "rules": {
7 | "no-console": "error",
8 | "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
9 | "semi": ["error", "always"],
10 | "block-scoped-var": "error",
11 | "eqeqeq": "error",
12 | "brace-style": ["error", "1tbs"],
13 | "space-before-function-paren": ["error", "never"],
14 | "space-in-parens": ["error", "never"],
15 | "comma-spacing": ["error", { "before": false, "after": true }]
16 | }
17 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | src/**/*.js
2 | test/**/*.js
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "iojs"
4 | before_script:
5 | - export DISPLAY=:99.0
6 | - sh -e /etc/init.d/xvfb start
7 | - npm install
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Stepan Riha
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | angular-ui-router-default
2 | =========================
3 | [](https://travis-ci.org/nonplus/angular-ui-router-default)
4 | [](https://cdnjs.com/libraries/angular-ui-router-default)
5 |
6 | Motivation
7 | ----------
8 |
9 | Abstract state are useful for resolving values used by multiple child states. However, since one cannot navigate to an abstract state (`$state.go('abstract_parent')`) any part of the application that transitions state (`$state.go()`, `ui-sref`, etc.) must explicitly specify a non-abstract child state (`$state.go('abstract_parent.concrete-child')`).
10 |
11 | Abstract are also useful in top-level navigation links, since `ui-sref-active` is set for all their child states. However, since you can't directly navigate to the (`ui-sref="abstract_state"`), implementing these menu items usually requires an `ng-click` handler that navigates to a concrete state.
12 |
13 | The options for [How to: Set up a default/index child state](https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-set-up-a-defaultindex-child-state]) are tedious, non-intuitive and depend on URL routing. There is a need for a more convenient way of defining default child states with some great [ideas on how to configure these](https://github.com/angular-ui/ui-router/issues/27).
14 |
15 | This module provides basic support for specifying the default child state as a string.
16 |
17 | Loading the Module
18 | ------------------
19 |
20 | This module declares itself as ui.router.default, so it can be declared as a dependency of your application as normal:
21 |
22 | ```javascript
23 | var app = angular.module('myApp', ['ng', 'ui.router.default']);
24 | ```
25 |
26 | Defining Default Child State
27 | ----------------------------
28 |
29 | In your state definition for an abstract state, add a `default` property with the name of a child state (relative or absolute).
30 | The child state name can be provided statically as a string or dynamically as a function callback.
31 |
32 | When a state transtion targets this abstract state, it will be redirected to the default child state instead.
33 |
34 | ```javascript
35 | $stateProvider
36 | .state('parent', {
37 | abstract: true,
38 | default: '.index',
39 | template: ''
40 | })
41 | .state('parent.index', {
42 | // ...
43 | })
44 | .state('parent.page2', {
45 | // ...
46 | })
47 | .state('another', {
48 | abstract: true,
49 | default: ['$rootScope', function($rootScope) {
50 | return $rootScope.edit ? '.edit' : '.display';
51 | }]
52 | })
53 | .state('another.display', {
54 | // ...
55 | })
56 | .state('another.edit', {
57 | // ...
58 | })
59 | .state('anotherWithPromise',{
60 | abstract: true,
61 | default: ['$q',function($q){
62 | var defer = $q.defer();
63 | asyncFunctionThatReturnsPromise().then(function(){
64 | defer.resolve('anotherWithPromise.details');
65 | });
66 | return defer.promise;
67 | }]
68 | })
69 | .state('anotherWithPromise.details',{
70 | // ...
71 | })
72 | ```
73 |
74 | #### Older version (< 0.0.5)
75 |
76 | Older versions of this module specified the default state by assigning it to the `abstract` property:
77 |
78 | ```javascript
79 | $stateProvider
80 | .state('parent', {
81 | abstract: '.index',
82 | template: ''
83 | })
84 | // ...
85 | ```
86 |
87 | This behavior is still supported, but is **deprecated**, because it causes TypeScript conflicts. It is recommended
88 | that the `{ abstract: true, default: '.index' }` format is used instead.
89 |
90 | Using Default Child State
91 | -------------------------
92 |
93 | When a default child state is defined, the application can now navigate to the abstract parent state.
94 | ```javascript
95 | $state.go('parent');
96 | ```
97 |
98 | ```html
99 |
18 | // to active whenever 'contacts.list' or one of its decendents is active.
19 | $rootScope.$state = $state;
20 | $rootScope.$stateParams = $stateParams;
21 | }
22 | ]
23 | )
24 |
25 | .config(
26 | [ '$stateProvider', '$urlRouterProvider',
27 | function ($stateProvider, $urlRouterProvider) {
28 |
29 | /////////////////////////////
30 | // Redirects and Otherwise //
31 | /////////////////////////////
32 |
33 | // Use $urlRouterProvider to configure any redirects (when) and invalid urls (otherwise).
34 | $urlRouterProvider
35 |
36 | // The `when` method says if the url is ever the 1st param, then redirect to the 2nd param
37 | // Here we are just setting up some convenience urls.
38 | .when('/c?id', '/contacts/:id')
39 | .when('/user/:id', '/contacts/:id')
40 |
41 | // If the url is ever invalid, e.g. '/asdf', then redirect to '/' aka the home state
42 | .otherwise('/');
43 |
44 |
45 | //////////////////////////
46 | // State Configurations //
47 | //////////////////////////
48 |
49 | // Use $stateProvider to configure your states.
50 | $stateProvider
51 |
52 | //////////
53 | // Home //
54 | //////////
55 |
56 | .state("home", {
57 |
58 | // Use a url of "/" to set a states as the "index".
59 | url: "/",
60 |
61 | // Example of an inline template string. By default, templates
62 | // will populate the ui-view within the parent state's template.
63 | // For top level states, like this one, the parent template is
64 | // the index.html file. So this template will be inserted into the
65 | // ui-view within index.html.
66 | template: '
Welcome to the UI-Router Demo
' +
67 | '
Use the menu above to navigate. ' +
68 | 'Pay attention to the $state and $stateParams values below.
' +
69 | '
Click these links—Alice or ' +
70 | 'Bob—to see a url redirect in action.
'
71 |
72 | })
73 |
74 | ///////////
75 | // About //
76 | ///////////
77 |
78 | .state('about', {
79 | url: '/about',
80 |
81 | // Showing off how you could return a promise from templateProvider
82 | templateProvider: ['$timeout',
83 | function ( $timeout) {
84 | return $timeout(function () {
85 | return '
27 |
--------------------------------------------------------------------------------
/sample/app/contacts/contacts.js:
--------------------------------------------------------------------------------
1 | angular.module('uiRouterSample.contacts', [
2 | 'ui.router'
3 | ])
4 |
5 | .config(
6 | [ '$stateProvider', '$urlRouterProvider',
7 | function ($stateProvider, $urlRouterProvider) {
8 | $stateProvider
9 | //////////////
10 | // Contacts //
11 | //////////////
12 | .state('contacts', {
13 |
14 | // By specifying the name of a child state, when the abstract state is activated, it
15 | // will activate the specified child state instead.
16 | abstract: true,
17 | default: '.list',
18 |
19 | // This abstract state will prepend '/contacts' onto the urls of all its children.
20 | url: '/contacts',
21 |
22 | // Example of loading a template from a file. This is also a top level state,
23 | // so this template file will be loaded and then inserted into the ui-view
24 | // within index.html.
25 | templateUrl: 'app/contacts/contacts.html',
26 |
27 | // Use `resolve` to resolve any asynchronous controller dependencies
28 | // *before* the controller is instantiated. In this case, since contacts
29 | // returns a promise, the controller will wait until contacts.all() is
30 | // resolved before instantiation. Non-promise return values are considered
31 | // to be resolved immediately.
32 | resolve: {
33 | contacts: ['contacts',
34 | function( contacts){
35 | return contacts.all();
36 | }]
37 | },
38 |
39 | // You can pair a controller to your template. There *must* be a template to pair with.
40 | controller: ['$scope', '$state', 'contacts', 'utils',
41 | function ( $scope, $state, contacts, utils) {
42 |
43 | // Add a 'contacts' field in this abstract parent's scope, so that all
44 | // child state views can access it in their scopes. Please note: scope
45 | // inheritance is not due to nesting of states, but rather choosing to
46 | // nest the templates of those states. It's normal scope inheritance.
47 | $scope.contacts = contacts;
48 |
49 | $scope.goToRandom = function () {
50 | var randId = utils.newRandomKey($scope.contacts, "id", $state.params.contactId);
51 |
52 | // $state.go() can be used as a high level convenience method
53 | // for activating a state programmatically.
54 | $state.go('contacts.detail', { contactId: randId });
55 | };
56 | }]
57 | })
58 |
59 | /////////////////////
60 | // Contacts > List //
61 | /////////////////////
62 |
63 | // Using a '.' within a state name declares a child within a parent.
64 | // So you have a new state 'list' within the parent 'contacts' state.
65 | .state('contacts.list', {
66 |
67 | // Using an empty url means that this child state will become active
68 | // when its parent's url is navigated to. Urls of child states are
69 | // automatically appended to the urls of their parent. So this state's
70 | // url is '/contacts' (because '/contacts' + '').
71 | url: '',
72 |
73 | // IMPORTANT: Now we have a state that is not a top level state. Its
74 | // template will be inserted into the ui-view within this state's
75 | // parent's template; so the ui-view within contacts.html. This is the
76 | // most important thing to remember about templates.
77 | templateUrl: 'app/contacts/contacts.list.html'
78 | })
79 |
80 | ///////////////////////
81 | // Contacts > Detail //
82 | ///////////////////////
83 |
84 | // You can have unlimited children within a state. Here is a second child
85 | // state within the 'contacts' parent state.
86 | .state('contacts.detail', {
87 |
88 | // Urls can have parameters. They can be specified like :param or {param}.
89 | // If {} is used, then you can also specify a regex pattern that the param
90 | // must match. The regex is written after a colon (:). Note: Don't use capture
91 | // groups in your regex patterns, because the whole regex is wrapped again
92 | // behind the scenes. Our pattern below will only match numbers with a length
93 | // between 1 and 4.
94 |
95 | // Since this state is also a child of 'contacts' its url is appended as well.
96 | // So its url will end up being '/contacts/{contactId:[0-9]{1,4}}'. When the
97 | // url becomes something like '/contacts/42' then this state becomes active
98 | // and the $stateParams object becomes { contactId: 42 }.
99 | url: '/{contactId:[0-9]{1,4}}',
100 |
101 | // If there is more than a single ui-view in the parent template, or you would
102 | // like to target a ui-view from even higher up the state tree, you can use the
103 | // views object to configure multiple views. Each view can get its own template,
104 | // controller, and resolve data.
105 |
106 | // View names can be relative or absolute. Relative view names do not use an '@'
107 | // symbol. They always refer to views within this state's parent template.
108 | // Absolute view names use a '@' symbol to distinguish the view and the state.
109 | // So 'foo@bar' means the ui-view named 'foo' within the 'bar' state's template.
110 | views: {
111 |
112 | // So this one is targeting the unnamed view within the parent state's template.
113 | '': {
114 | templateUrl: 'app/contacts/contacts.detail.html',
115 | controller: ['$scope', '$stateParams', 'utils',
116 | function ( $scope, $stateParams, utils) {
117 | $scope.contact = utils.findById($scope.contacts, $stateParams.contactId);
118 | }]
119 | },
120 |
121 | // This one is targeting the ui-view="hint" within the unnamed root, aka index.html.
122 | // This shows off how you could populate *any* view within *any* ancestor state.
123 | 'hint@': {
124 | template: 'This is contacts.detail populating the "hint" ui-view'
125 | },
126 |
127 | // This one is targeting the ui-view="menuTip" within the parent state's template.
128 | 'menuTip': {
129 | // templateProvider is the final method for supplying a template.
130 | // There is: template, templateUrl, and templateProvider.
131 | templateProvider: ['$stateParams',
132 | function ( $stateParams) {
133 | // This is just to demonstrate that $stateParams injection works for templateProvider.
134 | // $stateParams are the parameters for the new state we're transitioning to, even
135 | // though the global '$stateParams' has not been updated yet.
136 | return 'Contact ID: ' + $stateParams.contactId + '';
137 | }]
138 | }
139 | }
140 | })
141 |
142 | //////////////////////////////
143 | // Contacts > Detail > Item //
144 | //////////////////////////////
145 |
146 | .state('contacts.detail.item', {
147 |
148 | // So following what we've learned, this state's full url will end up being
149 | // '/contacts/{contactId}/item/:itemId'. We are using both types of parameters
150 | // in the same url, but they behave identically.
151 | url: '/item/:itemId',
152 | views: {
153 |
154 | // This is targeting the unnamed ui-view within the parent state 'contact.detail'
155 | // We wouldn't have to do it this way if we didn't also want to set the 'hint' view below.
156 | // We could instead just set templateUrl and controller outside of the view obj.
157 | '': {
158 | templateUrl: 'app/contacts/contacts.detail.item.html',
159 | controller: ['$scope', '$stateParams', '$state', 'utils',
160 | function ( $scope, $stateParams, $state, utils) {
161 | $scope.item = utils.findById($scope.contact.items, $stateParams.itemId);
162 |
163 | $scope.edit = function () {
164 | // Here we show off go's ability to navigate to a relative state. Using '^' to go upwards
165 | // and '.' to go down, you can navigate to any relative state (ancestor or descendant).
166 | // Here we are going down to the child state 'edit' (full name of 'contacts.detail.item.edit')
167 | $state.go('.edit', $stateParams);
168 | };
169 | }]
170 | },
171 |
172 | // Here we see we are overriding the template that was set by 'contacts.detail'
173 | 'hint@': {
174 | template: ' This is contacts.detail.item overriding the "hint" ui-view'
175 | }
176 | }
177 | })
178 |
179 | /////////////////////////////////////
180 | // Contacts > Detail > Item > Edit //
181 | /////////////////////////////////////
182 |
183 | // Notice that this state has no 'url'. States do not require a url. You can use them
184 | // simply to organize your application into "places" where each "place" can configure
185 | // only what it needs. The only way to get to this state is via $state.go (or transitionTo)
186 | .state('contacts.detail.item.edit', {
187 | views: {
188 |
189 | // This is targeting the unnamed view within the 'contacts.detail' state
190 | // essentially swapping out the template that 'contacts.detail.item' had
191 | // inserted with this state's template.
192 | '@contacts.detail': {
193 | templateUrl: 'app/contacts/contacts.detail.item.edit.html',
194 | controller: ['$scope', '$stateParams', '$state', 'utils',
195 | function ( $scope, $stateParams, $state, utils) {
196 | $scope.item = utils.findById($scope.contact.items, $stateParams.itemId);
197 | $scope.done = function () {
198 | // Go back up. '^' means up one. '^.^' would be up twice, to the grandparent.
199 | $state.go('^', $stateParams);
200 | };
201 | }]
202 | }
203 | }
204 | });
205 | }
206 | ]
207 | );
208 |
--------------------------------------------------------------------------------
/sample/app/contacts/contacts.list.html:
--------------------------------------------------------------------------------
1 |