Skip to content

Commit b97a01d

Browse files
committed
Merge pull request #961 from mbroadst/improve-sample-structure
migrate sample app to a more 'best practice' directory layout
2 parents 5a7c48c + 56262ea commit b97a01d

16 files changed

+365
-336
lines changed

sample/app/app.js

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Make sure to include the `ui.router` module as a dependency
2+
angular.module('uiRouterSample', [
3+
'uiRouterSample.contacts',
4+
'uiRouterSample.contacts.service',
5+
'uiRouterSample.utils.service',
6+
'ui.router',
7+
'ngAnimate'
8+
])
9+
10+
.run(
11+
[ '$rootScope', '$state', '$stateParams',
12+
function ($rootScope, $state, $stateParams) {
13+
14+
// It's very handy to add references to $state and $stateParams to the $rootScope
15+
// so that you can access them from any scope within your applications.For example,
16+
// <li ui-sref-active="active }"> will set the <li> // to active whenever
17+
// 'contacts.list' or one of its decendents is active.
18+
$rootScope.$state = $state;
19+
$rootScope.$stateParams = $stateParams;
20+
}
21+
]
22+
)
23+
24+
.config(
25+
[ '$stateProvider', '$urlRouterProvider',
26+
function ($stateProvider, $urlRouterProvider) {
27+
28+
/////////////////////////////
29+
// Redirects and Otherwise //
30+
/////////////////////////////
31+
32+
// Use $urlRouterProvider to configure any redirects (when) and invalid urls (otherwise).
33+
$urlRouterProvider
34+
35+
// The `when` method says if the url is ever the 1st param, then redirect to the 2nd param
36+
// Here we are just setting up some convenience urls.
37+
.when('/c?id', '/contacts/:id')
38+
.when('/user/:id', '/contacts/:id')
39+
40+
// If the url is ever invalid, e.g. '/asdf', then redirect to '/' aka the home state
41+
.otherwise('/');
42+
43+
44+
//////////////////////////
45+
// State Configurations //
46+
//////////////////////////
47+
48+
// Use $stateProvider to configure your states.
49+
$stateProvider
50+
51+
//////////
52+
// Home //
53+
//////////
54+
55+
.state("home", {
56+
57+
// Use a url of "/" to set a states as the "index".
58+
url: "/",
59+
60+
// Example of an inline template string. By default, templates
61+
// will populate the ui-view within the parent state's template.
62+
// For top level states, like this one, the parent template is
63+
// the index.html file. So this template will be inserted into the
64+
// ui-view within index.html.
65+
template: '<p class="lead">Welcome to the UI-Router Demo</p>' +
66+
'<p>Use the menu above to navigate. ' +
67+
'Pay attention to the <code>$state</code> and <code>$stateParams</code> values below.</p>' +
68+
'<p>Click these links—<a href="#/c?id=1">Alice</a> or ' +
69+
'<a href="#/user/42">Bob</a>—to see a url redirect in action.</p>'
70+
71+
})
72+
73+
///////////
74+
// About //
75+
///////////
76+
77+
.state('about', {
78+
url: '/about',
79+
80+
// Showing off how you could return a promise from templateProvider
81+
templateProvider: ['$timeout',
82+
function ( $timeout) {
83+
return $timeout(function () {
84+
return '<p class="lead">UI-Router Resources</p><ul>' +
85+
'<li><a href="https://github.com/angular-ui/ui-router/tree/master/sample">Source for this Sample</a></li>' +
86+
'<li><a href="https://github.com/angular-ui/ui-router">Github Main Page</a></li>' +
87+
'<li><a href="https://github.com/angular-ui/ui-router#quick-start">Quick Start</a></li>' +
88+
'<li><a href="https://github.com/angular-ui/ui-router/wiki">In-Depth Guide</a></li>' +
89+
'<li><a href="https://github.com/angular-ui/ui-router/wiki/Quick-Reference">API Reference</a></li>' +
90+
'</ul>';
91+
}, 100);
92+
}]
93+
})
94+
}
95+
]
96+
);
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
angular.module('uiRouterSample.contacts.service', [
2+
3+
])
4+
5+
// A RESTful factory for retreiving contacts from 'contacts.json'
6+
.factory('contacts', ['$http', function ($http, utils) {
7+
var path = 'assets/contacts.json';
8+
var contacts = $http.get(path).then(function (resp) {
9+
return resp.data.contacts;
10+
});
11+
12+
var factory = {};
13+
factory.all = function () {
14+
return contacts;
15+
};
16+
factory.get = function (id) {
17+
return contacts.then(function(){
18+
return utils.findById(contacts, id);
19+
})
20+
};
21+
return factory;
22+
}]);
File renamed without changes.

sample/app/contacts/contacts.js

+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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+
// With abstract set to true, that means this state can not be explicitly activated.
15+
// It can only be implicitly activated by activating one of it's children.
16+
abstract: true,
17+
18+
// This abstract state will prepend '/contacts' onto the urls of all its children.
19+
url: '/contacts',
20+
21+
// Example of loading a template from a file. This is also a top level state,
22+
// so this template file will be loaded and then inserted into the ui-view
23+
// within index.html.
24+
templateUrl: 'app/contacts/contacts.html',
25+
26+
// Use `resolve` to resolve any asynchronous controller dependencies
27+
// *before* the controller is instantiated. In this case, since contacts
28+
// returns a promise, the controller will wait until contacts.all() is
29+
// resolved before instantiation. Non-promise return values are considered
30+
// to be resolved immediately.
31+
resolve: {
32+
contacts: ['contacts',
33+
function( contacts){
34+
return contacts.all();
35+
}]
36+
},
37+
38+
// You can pair a controller to your template. There *must* be a template to pair with.
39+
controller: ['$scope', '$state', 'contacts', 'utils',
40+
function ( $scope, $state, contacts, utils) {
41+
42+
// Add a 'contacts' field in this abstract parent's scope, so that all
43+
// child state views can access it in their scopes. Please note: scope
44+
// inheritance is not due to nesting of states, but rather choosing to
45+
// nest the templates of those states. It's normal scope inheritance.
46+
$scope.contacts = contacts;
47+
48+
$scope.goToRandom = function () {
49+
var randId = utils.newRandomKey($scope.contacts, "id", $state.params.contactId);
50+
51+
// $state.go() can be used as a high level convenience method
52+
// for activating a state programmatically.
53+
$state.go('contacts.detail', { contactId: randId });
54+
};
55+
}]
56+
})
57+
58+
/////////////////////
59+
// Contacts > List //
60+
/////////////////////
61+
62+
// Using a '.' within a state name declares a child within a parent.
63+
// So you have a new state 'list' within the parent 'contacts' state.
64+
.state('contacts.list', {
65+
66+
// Using an empty url means that this child state will become active
67+
// when its parent's url is navigated to. Urls of child states are
68+
// automatically appended to the urls of their parent. So this state's
69+
// url is '/contacts' (because '/contacts' + '').
70+
url: '',
71+
72+
// IMPORTANT: Now we have a state that is not a top level state. Its
73+
// template will be inserted into the ui-view within this state's
74+
// parent's template; so the ui-view within contacts.html. This is the
75+
// most important thing to remember about templates.
76+
templateUrl: 'app/contacts/contacts.list.html'
77+
})
78+
79+
///////////////////////
80+
// Contacts > Detail //
81+
///////////////////////
82+
83+
// You can have unlimited children within a state. Here is a second child
84+
// state within the 'contacts' parent state.
85+
.state('contacts.detail', {
86+
87+
// Urls can have parameters. They can be specified like :param or {param}.
88+
// If {} is used, then you can also specify a regex pattern that the param
89+
// must match. The regex is written after a colon (:). Note: Don't use capture
90+
// groups in your regex patterns, because the whole regex is wrapped again
91+
// behind the scenes. Our pattern below will only match numbers with a length
92+
// between 1 and 4.
93+
94+
// Since this state is also a child of 'contacts' its url is appended as well.
95+
// So its url will end up being '/contacts/{contactId:[0-9]{1,8}}'. When the
96+
// url becomes something like '/contacts/42' then this state becomes active
97+
// and the $stateParams object becomes { contactId: 42 }.
98+
url: '/{contactId:[0-9]{1,4}}',
99+
100+
// If there is more than a single ui-view in the parent template, or you would
101+
// like to target a ui-view from even higher up the state tree, you can use the
102+
// views object to configure multiple views. Each view can get its own template,
103+
// controller, and resolve data.
104+
105+
// View names can be relative or absolute. Relative view names do not use an '@'
106+
// symbol. They always refer to views within this state's parent template.
107+
// Absolute view names use a '@' symbol to distinguish the view and the state.
108+
// So 'foo@bar' means the ui-view named 'foo' within the 'bar' state's template.
109+
views: {
110+
111+
// So this one is targeting the unnamed view within the parent state's template.
112+
'': {
113+
templateUrl: 'app/contacts/contacts.detail.html',
114+
controller: ['$scope', '$stateParams', 'utils',
115+
function ( $scope, $stateParams, utils) {
116+
$scope.contact = utils.findById($scope.contacts, $stateParams.contactId);
117+
}]
118+
},
119+
120+
// This one is targeting the ui-view="hint" within the unnamed root, aka index.html.
121+
// This shows off how you could populate *any* view within *any* ancestor state.
122+
'hint@': {
123+
template: 'This is contacts.detail populating the "hint" ui-view'
124+
},
125+
126+
// This one is targeting the ui-view="menu" within the parent state's template.
127+
'menuTip': {
128+
// templateProvider is the final method for supplying a template.
129+
// There is: template, templateUrl, and templateProvider.
130+
templateProvider: ['$stateParams',
131+
function ( $stateParams) {
132+
// This is just to demonstrate that $stateParams injection works for templateProvider.
133+
// $stateParams are the parameters for the new state we're transitioning to, even
134+
// though the global '$stateParams' has not been updated yet.
135+
return '<hr><small class="muted">Contact ID: ' + $stateParams.contactId + '</small>';
136+
}]
137+
}
138+
}
139+
})
140+
141+
//////////////////////////////
142+
// Contacts > Detail > Item //
143+
//////////////////////////////
144+
145+
.state('contacts.detail.item', {
146+
147+
// So following what we've learned, this state's full url will end up being
148+
// '/contacts/{contactId}/item/:itemId'. We are using both types of parameters
149+
// in the same url, but they behave identically.
150+
url: '/item/:itemId',
151+
views: {
152+
153+
// This is targeting the unnamed ui-view within the parent state 'contact.detail'
154+
// We wouldn't have to do it this way if we didn't also want to set the 'hint' view below.
155+
// We could instead just set templateUrl and controller outside of the view obj.
156+
'': {
157+
templateUrl: 'app/contacts/contacts.detail.item.html',
158+
controller: ['$scope', '$stateParams', '$state', 'utils',
159+
function ( $scope, $stateParams, $state, utils) {
160+
$scope.item = utils.findById($scope.contact.items, $stateParams.itemId);
161+
162+
$scope.edit = function () {
163+
// Here we show off go's ability to navigate to a relative state. Using '^' to go upwards
164+
// and '.' to go down, you can navigate to any relative state (ancestor or descendant).
165+
// Here we are going down to the child state 'edit' (full name of 'contacts.detail.item.edit')
166+
$state.go('.edit', $stateParams);
167+
};
168+
}]
169+
},
170+
171+
// Here we see we are overriding the template that was set by 'contact.detail'
172+
'hint@': {
173+
template: ' This is contacts.detail.item overriding the "hint" ui-view'
174+
}
175+
}
176+
})
177+
178+
/////////////////////////////////////
179+
// Contacts > Detail > Item > Edit //
180+
/////////////////////////////////////
181+
182+
// Notice that this state has no 'url'. States do not require a url. You can use them
183+
// simply to organize your application into "places" where each "place" can configure
184+
// only what it needs. The only way to get to this state is via $state.go (or transitionTo)
185+
.state('contacts.detail.item.edit', {
186+
views: {
187+
188+
// This is targeting the unnamed view within the 'contact.detail' state
189+
// essentially swapping out the template that 'contact.detail.item' had
190+
// had inserted with this state's template.
191+
'@contacts.detail': {
192+
templateUrl: 'app/contacts/contacts.detail.item.edit.html',
193+
controller: ['$scope', '$stateParams', '$state', 'utils',
194+
function ( $scope, $stateParams, $state, utils) {
195+
$scope.item = utils.findById($scope.contact.items, $stateParams.itemId);
196+
$scope.done = function () {
197+
// Go back up. '^' means up one. '^.^' would be up twice, to the grandparent.
198+
$state.go('^', $stateParams);
199+
};
200+
}]
201+
}
202+
}
203+
});
204+
}
205+
]
206+
);
File renamed without changes.
File renamed without changes.

sample/common/utils/utils-service.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
angular.module('uiRouterSample.utils.service', [
2+
3+
])
4+
5+
.factory('utils', function () {
6+
return {
7+
// Util for finding an object by its 'id' property among an array
8+
findById: function findById(a, id) {
9+
for (var i = 0; i < a.length; i++) {
10+
if (a[i].id == id) return a[i];
11+
}
12+
return null;
13+
},
14+
15+
// Util for returning a randomKey from a collection that also isn't the current key
16+
newRandomKey: function newRandomKey(coll, key, currentKey){
17+
var randKey;
18+
do {
19+
randKey = coll[Math.floor(coll.length * Math.random())][key];
20+
} while (randKey == currentKey);
21+
return randKey;
22+
}
23+
};
24+
});
File renamed without changes.

0 commit comments

Comments
 (0)