How to handle long dynamic urls with angular ui-router? - angularjs

I am building the front-end app for a REST service, and most of the resources are located at long urls where most of the segments are dynamic based on records created in the app by users. Obviously I won't be able to know or create hardcoded routes for most of these records.
My question I suppose is how to handle urls like this with ui-router:
<semester>/<program>/<class>/enrollment
or
<semester>/myclasses/<class>/assignments
There is always at least one static, predictable segment in every resource url, and the segments are always in a predictable order.
Do I make abstract states for each segment in the url like:
$stateProvider.state(semester)
.state(program)
.state(class)
.state(assignments);
??
I've tried building routes that look like this:
param = {
name: "param",
url: "/:hue/:temp/param",
templateUrl: "http://localhost:81/route/tpl/param.tpl.html",
controller: "paramController"
};
but it ends up sending me back to the .otherwise() state when I link to the "param" state.
Thanks for any help, I'm a bit stumped.

I had a similar problem and I quickly coded this:
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider.state('app', {
url : "/app",
abstract : true,
templateUrl : "layout/navigation-drawer.tpl.html"
}).state('app.help', {
url : "/help",
views : {
'menuContent' : {
templateUrl : "layout/help.html"
}
}
}).state('app.settings', {
url : "/settings",
views : {
'menuContent' : {
templateUrl : "layout/settings.html"
}
}
}).state('app.rate-us', {
url : "/rate-us",
views : {
'menuContent' : {
templateUrl : "layout/rate-us.html"
}
}
}).state('app.projects', {
url : "/projects",
views : {
'menuContent' : {
templateUrl : "layout/projects.html",
controller : 'ProjectsCtrl'
}
}
}).state('app.forms', {
url : "/:project_name/forms",
views : {
'menuContent' : {
templateUrl : "layout/forms.html",
controller : 'FormsCtrl'
}
}
}).state('app.entries', {
url : "/:project_name/:form_name/entries/:form_id",
views : {
'menuContent' : {
templateUrl : "layout/entries.html",
controller : 'EntriesCtrl'
}
}
});
which is working, "/:project_name/:form_name/entries/:form_id" will resolve to something like app/Mirko_test/University/entries/1

Ok so I tested this out and it works in my case. It fails when the state is only a parameter, but it seems as long as each state has a non-parameterized bit, ui-router is able to parse down to children states. I haven't seen this case demonstrated or explained anywhere before. Most tutorials only cover simple hardcoded nested states and not parameterized ones.
It's not ideal, but it works.
I hope this helps someone else facing this issue. :)
var app = angular.module('app', ['ui.router'])
.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', function ( $stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise("/");
$locationProvider.html5Mode(true);
var semester = {
name: "semester",
abstract: true,
url: "semester/:sem",
templateUrl: "http://localhost:81/route/to/semtemplate.tpl.html",
controller: "semesterController"
},
program = {
name: "program",
parent: sem,
url: "program/:prg",
templateUrl: "http://localhost:81/route/to/prgtemplate.tpl.html",
controller: "programController"
},
classes = {
name: "classes",
parent: prg,
url: "/classes",
templateUrl: "http://localhost:81/route/to/clstemplate.tpl.html",
controller: "classesController"
};
$stateProvider.state(sem)
.state(prg)
.state(classes);
}]);
app.controller('paraController', ['$scope', '$stateParams', '$state',function($scope, $state, $stateParams){
console.log('paraController instantiated');
$scope.sem = $stateParams.params.sem;
$scope.prg = $stateParams.params.prg;
}]);
As this is a hierarchical REST api this pattern works perfectly, and when also taking advantage of scope inheritance from each controller it should be a good fit for my project. I haven't tested extremes of nested states, but it would be interesting to see how it behaves under even more parameterized states. The only limitation I have found is that each state needs to have a non-parameterized part as well. So /:sem fails but semester/:sem works fine.
It's not ideal as it makes URLs longer, but I haven't found a workable alternative.

I know this question is old, but I had essentially the same question recently and found the official answer. Apparently, angular ui-router now supports the notion of URL Parameters in URL Routing, which allow you to specify parameters, along the lines of the following:
$stateProvider
.state('contacts.detail', {
url: "/contacts/:contactId",
templateUrl: 'contacts.detail.html',
controller: function ($stateParams) {
// If we got here from a url of /contacts/42
expect($stateParams).toBe({contactId: 42});
}
})
For more info, go here: https://github.com/angular-ui/ui-router/wiki/URL-Routing#url-parameters

Related

Angularjs and dynamic routes

I am trying to create a link in my template angularjs by doing something like:
<a ng-href="/#!/content/[[value.id]]">[[key]]</a>
But I am wondering myself if is possible do something like symfony2 does, example:
routing.yml
home_redirect:
path: /
defaults:
_controller: FrontendBundle:Controller:function
path: /home
permanent: true
options:
expose: true
And using it in your twig template by doing:
one link to home
That is really, really helpful because I don't have to "hardcode" all my routes.
To ensure a proper routing, you can use ui-router.
Here is an exemple on plunker
How this works :
1 - Follow the installation guide on their github
2 - Write your state definition :
app.config(function($stateProvider, $urlRouterProvider){
//If no route match, you'll go to /index
$urlRouterProvider.otherwise('/index');
//my index state
$stateProvider
.state('index', {
url: '/index',
templateUrl: 'index2.html',
controller: 'IndexCtrl'
})
//the variable state depending on an url element
.state('hello', {
//you will be able to get name with $stateParams.name
url: '/hello/:name',
templateUrl: 'hello.html',
controller: 'HelloCtrl'
})
});
3 - Write links by their state name :
//add this directive to an html element
//This will go to /index
ui-sref="index"
//This will go to /hello/
ui-sref="hello"
//This will go to /hello/ben
ui-sref="hello({name:'ben'})"
//This will go to /hello/{myname}
ui-sref="hello({name:myname})"
4 - Get the param into your controller :
//inject $stateParams
app.controller('HelloCtrl', function($scope, $stateParams){
$scope.controller = "IndexCtrl";
//get the param name like this
$scope.name = $stateParams.name;
});
Hope it helped. Also keep in mind the ui-router got some really powerful tools such as resolve and nested state/view. You'll probably need theses now or later.
PS : If the plunker don't work, just fork it and save again.
You could do this :
'use strict';
angular.module('AngularModule')
.config(function ($stateProvider) {
$stateProvider
.state('YourStateName', {
url: '/your/url',
views: {
'aViewName': {
templateUrl:'views/components/templates/yourTemplate.html',
controller: 'YourController'
}
},
resolve: {
}
});
});
// then in your controller
angular.module('AngularModule')
.controller('MyController',function($scope, $state){
$scope.goTo = function(){
$state.go('YourStateName');
}
}
);
//in your html make sure the <a> tag is in scope with the 'MyController'
<a ng-click='goTo'>[[key]]</a>
or
you can just do this :
<a ng-href="/your/url"></a>
that way you bypass the controller you can still put logic in the controller that was specified in the state

AngularJs UI router - one state with multiple URLs

I have a request to add in another URL parameter that directs to a state that I already have set up. For efficiency purposes, I'm trying to see if I can add multiple URLs to point to the same state, or should I just use the $UrlRouterProvider.when() method to re-direct to that state in this new case.
Ex. this is what already exists
.state('site.link1',
{
url: '/link1',
templateUrl: '/views/link1.html',
controller: 'link1Ctrl'
})
and the request is to add www.site.com/newlink that points to the link1 page. Is there something like this;
.state('site.link1',
{
url: '/link1, /newlink',
...
Try using the Regex and a parameter in the url. It is not optimal but works.
.state('site.link1',
{
url: '/{path:link1|newlink}',
templateUrl: '/views/link1.html',
controller: 'link1Ctrl'
})
More information on regex in Urls.
To generate links with ui-sref pass the same parameter with the state name as a function
<a ui-sref="site.link1({path:'link1'})" >site link 1</a>
<a ui-sref="site.link1({path:'newlink'})">site new link</a>
You use params:
https://github.com/angular-ui/ui-router/wiki/URL-Routing
.state('site.link',
{
url: '/{link}'
..
}
so when you use the same state like this
$state.go('site.link', {link: 'link1'})
$state.go('site.link', {link: 'link2'})
you can used when() function
.state('site.link1',
{
url: '/link1',
templateUrl: '/views/link1.html',
controller: 'link1Ctrl'
})
then on root config
angular.module('myApp', [...])
.config(function ($urlRouterProvider) {
$urlRouterProvider.when(/newlink/, ['$state','$match', function ($state, $match) {
$state.go('site.link1');
}]);
});
I found this approach to be quite simple and clean: create two equal states, just changing the url property
//Both root and login are the same, but with different url's.
var rootConfig = {
url: '/',
templateUrl:'html/authentication/login.html',
controller: 'authCtrl',
data: {
requireLogin: false
}
}
var loginConfig = Object.create(rootConfig)
loginConfig.url = '/login'
$stateProvider
.state('root', rootConfig)
.state('login', loginConfig)
I had almost the same problem, only with another constraint - I didn't want to use a redirect, since I wanted the url in the browser to stay the same, but display the same state.
This was because I wanted the chrome saved passwords to work for users that already saved the previous url.
In my case I wanted these two urls :
/gilly and
/new/gilly
to both point to the same state.
I solved this by having one state defined for /gilly, and for the second url, I defined an abstract state called /new.
This should be set up like this :
$stateProvider.state('new', {
abstract: true,
url: '/new'
template: '',
controller: function() { }
}).state('gilly', {
url: '/gilly',
template: 'gilly.html',
controller: 'GillyController'
}).state('new.gilly', {
url: '/gilly', // don't add the '/new' prefix here!
template: 'gilly.html',
controller: 'GillyController'
});

Angular-ui.router: Update URL without view refresh

I have an Angular SPA that presents a variety of recommendation lists, and a Google Map of locations, based on different cuts of some restaurant data (see m.amsterdamfoodie.nl). I want each of these lists to have their own URL. In order for Google to crawl the different lists I use <a> tags for the offcanvas navigation.
At present the <a> tag causes a view refresh, which is very noticeable with the map.
I can prevent this using ng-click and $event.preventDefault() (see code snippets below), but then I need to implement a means of updating the browser URL.
But in trying Angular's $state or the browser's history.pushstate, I end up triggering state changes and the view refresh...!
My question is therefore how can I update a model and the URL, but without refreshing the view? (See also Angular/UI-Router - How Can I Update The URL Without Refreshing Everything?)
I have experimented with a lot of approaches and currently have this html
Budget
In the controller:
this.action = ($event) ->
$event.preventDefault()
params = $event.target.href.match(/criteria\/(.*)\/(.*)$/)
# seems to cause a view refresh
# history.pushState({}, "page 2", "criteria/"+params[1]+"/"+params[2]);
# seems to cause a view refresh
# $state.transitionTo 'criteria', {criteria:params[1], q:params[2]}, {inherit:false}
updateModel(...)
And, what is I think is happening is that I am triggering the $stateProvider code:
angular.module 'afmnewApp'
.config ($stateProvider) ->
$stateProvider
.state 'main',
url: '/'
templateUrl: 'app/main/main.html'
controller: 'MainCtrl'
controllerAs: 'main'
.state 'criteria',
url: '/criteria/:criteria/:q'
templateUrl: 'app/main/main.html'
controller: 'MainCtrl'
controllerAs: 'main'
One possible clue is that with the code below if I load e.g. http://afmnew.herokuapp.com/criteria/cuisine/italian then the view refreshes as you navigate, whereas if I load http://afmnew.herokuapp.com/ there are no refreshes, but no URL updates instead. I don't understand why that is happening at all.
This is an example of the way to go if I understand correctly:
$state.go('my.state', {id:data.id}, {notify:false, reload:false});
//And to remove the id from the url:
$state.go('my.state', {id:undefined}, {notify:false, reload:false});
From user l-liava-l in the issue https://github.com/angular-ui/ui-router/issues/64
You can check the $state API here: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state
Based on our previous discussions, I want to give you some idea, how to use UI-Router here. I believe, I understand your challenge properly... There is a working example. If this not fully suites, please take it as some inspiration
DISCLAIMER: With a plunker, I was not able to achieve this: http://m.amsterdamfoodie.nl/, but the principle should be in that example similar
So, there is a state definition (we have only two states)
$stateProvider
.state('main', {
url: '/',
views: {
'#' : {
templateUrl: 'tpl.layout.html',
controller: 'MainCtrl',
},
'right#main' : { templateUrl: 'tpl.right.html',},
'map#main' : {
templateUrl: 'tpl.map.html',
controller: 'MapCtrl',
},
'list#main' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
'map' : {
templateUrl: 'tpl.map.html',
controller: 'MapCtrl',
},
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
}];
This would be our main tpl.layout.html
<div>
<section class="main">
<section class="map">
<div ui-view="map"></div>
</section>
<section class="list">
<div ui-view="list"></div>
</section>
</section>
<section class="right">
<div ui-view="right"></div>
</section>
</div>
As we can see, the main state does target these nested views of the main state: 'viewName#main', e.g. 'right#main'
Also the subview, main.criteria does inject into layout views.
Its url starts with a sign ^ (url : '^/criteria/:criteria/:value'), which allows to have / slash for main and not doubled slash for child
And also there are controllers, they are here a bit naive, but they should show, that on the background could be real data load (based on criteria).
The most important stuff here is, that the PARENT MainCtrl creates the $scope.Model = {}. This property will be (thanks to inheritance) shared among parent and children. That's why this all will work:
app.controller('MainCtrl', function($scope)
{
$scope.Model = {};
$scope.Model.data = ['Rest1', 'Rest2', 'Rest3', 'Rest4', 'Rest5'];
$scope.Model.randOrd = function (){ return (Math.round(Math.random())-0.5); };
})
.controller('ListCtrl', function($scope, $stateParams)
{
$scope.Model.list = []
$scope.Model.data
.sort( $scope.Model.randOrd )
.forEach(function(i) {$scope.Model.list.push(i + " - " + $stateParams.value || "root")})
$scope.Model.selected = $scope.Model.list[0];
$scope.Model.select = function(index){
$scope.Model.selected = $scope.Model.list[index];
}
})
This should get some idea how we can use the features provided for us by UI-Router:
Absolute Routes (^)
Scope Inheritance by View Hierarchy Only
View Names - Relative vs. Absolute Names
Check the above extract here, in the working example
Extend: new plunker here
If we do not want to have map view to be recreated, we can just omit that form the child state def:
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
// 'map' : {
// templateUrl: 'tpl.map.html',
// controller: 'MapCtrl',
//},
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
Now our map VIEW will be just recieving changes in the model (could be watched) but view and controller won't be rerendered
ALSO, there is another plunker http://plnkr.co/edit/y0GzHv?p=preview which uses the controllerAs
.state('main', {
url: '/',
views: {
'#' : {
templateUrl: 'tpl.layout.html',
controller: 'MainCtrl',
controllerAs: 'main', // here
},
...
},
})
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
controllerAs: 'list', // here
},
},
})
and that could be used like this:
<h4>{{main.hello()}}</h4>
<h4>{{list.hello()}}</h4>
The last plunker is here
you can use scope inheritance to update url without refreshing view
$stateProvider
.state('itemList', {
url: '/itemlist',
templateUrl: 'Scripts/app/item/ItemListTemplate.html',
controller: 'ItemListController as itemList'
//abstract: true //abstract maybe?
}).state('itemList.itemDetail', {
url: '/:itemName/:itemID',
templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
controller: 'ItemDetailController as itemDetail',
resolve: {
'CurrentItemID': ['$stateParams',function ($stateParams) {
return $stateParams['itemID'];
}]
}
})
if child view is inside parent view both controllers share same scope.
so you can place a dummy (or neccessary) ui-view inside parent view which will be populated by child view.
and insert a
$scope.loadChildData = function(itemID){..blabla..};
function in parent controller which will be called by child controller on controller load. so when a user clicks
<a ui-sref="childState({itemID: 12})">bla</a>
only child controller and child view will be refreshed. then you can call parent scope function with necessary parameters.
The short answer ended up being do not put the map inside a view that changes. The accepted answer provides a lot more detail on how to structure a page with sub-views, but the key point is not to make the map part of the view but to connect its behaviour to a view that does change and to use a Controller to update the market icons.

How can I change my AngularJS routes to file?param1=a&param2=b format?

I need to change an existing AngularJS application from using URLs in the format:
example.com/thePage/#/section/1/subsection/1
To making the section & subsection parameters readable by the server with a format like so:
example.com/thePage?section=1&subsection=1
The environment does not offer something like mod_rewrite, so I need to change the routing in Angular to make it handle & generate these URLs. I believe I can do this using $locationProvider.html5Mode(true); however I’m not sure how to proceed from there. I’ve tried updating the existing routing to something like the below, however it fails to return a view (as if the routing isn’t working.
$stateProvider
.state('section', {
abstract: true,
url: '?section',
views: {
'header': {
template: '<h3></h3>'
},
'main': {
templateUrl: constants.baseUrl + 'views/section.html',
controller: 'sectionCtrl',
resolve: {
section: ['sectionervice', '$stateParams',
function (sectionervice, $stateParams) {
return sectionervice.getsection($stateParams);
}],
subsection: ['sectionervice', '$stateParams',
function (sectionervice, $stateParams) {
return sectionervice.getsubsection($stateParams);
}]
}
}
}
})
.state('section.detail.subsection', {
url: '&subsection=:sectionId',
views: {
'main': {
templateUrl: constants.baseUrl + 'views/section.detail.subsection.html',
controller: 'DictionaryCtrl'
}
}
});
It seems that $stateProvider may only work with the forward-slash(/) parameter delimiter. Is there another way to achieve this?
In the ui-router website has a simple example of you trying to do.
Maybe you can do the same thing, see the RouteProvider and StateProvider settings.
url: http://angular-ui.github.io/ui-router/sample/app/app.js
In the server side you can retrieve the url, so you can get your parameters.
[Edit]
About $locationProvider.html5Mode(true); you can do this and do the settings in route and state providers too, that don't interfere in functionality

Angularjs-ui-router: How to activate default views on app start up?

I have 2 states in my app currently. Each app has multiple views. I want on state to be activated on app start up. Right now, when the app starts, I only get the links. Then I have to click on any link to activate any state. How do I make a state opened by default?
states conf
var app = angular.module('dategenie', ['ui.router', 'ui.bootstrap', 'geolocation', 'ngIdle', 'infinite-scroll']);
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
// For any unmatched url, redirect to /profile
$urlRouterProvider.otherwise('/');
// Now set up the states
$stateProvider.state('profile', {
views: {
mainModule: {
url : '/profile'
, templateUrl : 'partials/profile.html'
, controller: 'ProfileCtrl'
}
, rightPaneModule: {
templateUrl: 'partials/location.html'
, controller: 'LocationCtrl'
}
}
})
.state('profiles', {
views: {
mainModule: {
url : '/'
, templateUrl : 'partials/home.html'
, controller : 'HomeCtrl'
}
, chatModule: {
templateUrl : 'partials/chat.html'
, controller: 'ChatCtrl'
}
}
});
}]);
HTML
a(ui-sref="profile") Profile
a(ui-sref="profiles") Home
a(href="/logout") Logout
div(ui-view="mainModule")
div(ui-view="chatModule")
div(ui-view="rightPaneModule")
Thanks!
First of all, your url declaration should be outside of the views object.
So this is how your profiles state would look like:
.state('profiles', {
url : '/',
views: {
mainModule: {
templateUrl : 'partials/home.html'
, controller : 'HomeCtrl'
},
chatModule: {
templateUrl : 'partials/chat.html'
, controller: 'ChatCtrl'
}
}
});
Note: I'm not sure if this is still a valid concern, but I would put all of my view names in quotes. If memory serves me right, this had some implications earlier on with UI-router - not sure if that still applies.
Secondly, you need to make sure you have HTML5 Pushstate enabled if you wish for routing to pick up an active state on "/". Otherwise your 'root' would be "/#/".
Here's some code you can slap in say a push-state.js file;
app.config(['$locationProvider', function($locationProvider) {
return $locationProvider.html5Mode(true);
}]);
Quite useful to have in it's own file when you stumble upon errors with client side routing, I find most of my issues arise from PushState indescrepencies (so toggling it on and off is a nice little advantage when debugging).
Hope that works out for you, good luck : )

Resources