Angular-UI ui-router supporting infinitely nested states - angularjs

I have a situation in my Angular app where I'd like to consider an infinitely nested state/route. For example, let's say that I have a person and I want to see the lineage of the family. I might have routes that look like this:
/person/0
/person/0/child/1
/person/0/child/1/child/2
/person/0/child/1/child/2/child/3
And this could go on forever, as the user click further down the tree. How does a route like this get defined using ui-router?

You can use regex to define your route and then parse the url to get the child id, like this:
$stateProvider.state('person', {
url: '/person/:id',
views: {
'': {
templateUrl: 'parentTemplate.html',
controller: 'ParentCtrl'
}
}
}).state('child', {
url: '/person/:id/{path:.*}',
templateUrl: function(params) {
// params.path contains something like `child/1/child/2`
// so you get get the current child id like this
var path = params.path.split('/').reverse(),
childId = path[0];
// for more flexibility you can retrieve all ids from the request and store them in $stateParams
// so you can easily work with them in your controller
// here you can write some logic to determine the right template
return 'childTemplate.html';
},
controller: 'ChildCtrl'
});

Related

Multiple named views with dynamic routing in angularjs

Edit: Here is the complete code at Plunker. Though I can not c anything in execution but same code working at local. However gives a console error though
It all works perfect. But due to :id in /news/:id/, i am getting jquery/angular errors in console which can not be tracked anywhere in my code
I can not c What i am doing wrong.
Edit: Solved plunker https://plnkr.co/edit/FWcuBgGpVdMj3CroFrYJ
First of all you are trying to use ui-router but you're including ngRoute script in your plunker. Change it to
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.3.1/angular-ui-router.min.js"></script>
Then everything should work fine!
I suggest you a few changes...
1. Use ui-sref instead of href because it's much easier to define
ui-sref="post({id:1})" which turns into href="#/news/1"
If you would like to change url some day, then you will have to just change your route file, not each href.
$stateProvider
.state('post', {
url: "news/:id"
or
$stateProvider
.state('post', {
url: "archive/:id"
or
$stateProvider
.state('post', {
url: "whatever/:id"
2. Use abstract state
In your example it's a way better to define abstract state which holds header, content and footer - it's a typical use case.
ui-router
Abstract States
An abstract state can have child states but can not get activated
itself. An 'abstract' state is simply a state that can't be
transitioned to. It is activated implicitly when one of its
descendants are activated.
Some examples of how you might use an abstract state are:
To prepend a url to all child state urls. To insert a template with
its own ui-view(s) that its child states will populate. Optionally
assign a controller to the template. The controller must pair to a
template. Additionally, inherit $scope objects down to children, just
understand that this happens via the view hierarchy, not the state
hierarchy. To provide resolved dependencies via resolve for use by
child states. To provide inherited custom data via data for use by
child states or an event listener. To run an onEnter or onExit
function that may modify the application in someway. Any combination
of the above. Remember: Abstract states still need their own
for their children to plug into. So if you are using an
abstract state just to prepend a url, set resolves/data, or run an
onEnter/Exit function, then you'll additionally need to set template:
"".
Here's a plunker which shows how I would do it.
https://plnkr.co/edit/5FvJaelyxdl5MuALt5VY?p=preview
Hope it helps.
Look at the documentation for ui router named views,
You can use following syntax for using multiple views
$stateProvider
.state('state',{
url: '',
views: {
'header': {
templateUrl: 'views/header.html',
controller: 'headerCtrl'
},
'content': {
template: '<div ui-view=" "></div>', //<-- child templates loaded to here
},
'footer': {
templateUrl: 'views/footer.html',
controller: 'footerCtrl'
}
}
})
.state('state.post', {
url: 'news/:id/:KeyWords'
templateUrl: 'views/post.html' //<-- This goes into content's ui-view
});
I'm guessing you want to keep the header and footer and change content views.
You can achieve this by making this state as parent to all other states
suppose
.state('main',{
abstract: true,
views: {
'header': ... ,
'content': {
template: '<ui-view></ui-view>',
}
'footer': ...
}
})
then all the child views will load their views in the ,
ex: in main.child etc, your template will load in the content's <ui-view></ui-view> tag
If you need to use a custom template depending on keywords you can do the following:
.config(['$routeProvider',
function($routeProvider, $routeParams) {
$routeProvider
.when('/news/:id/:keyWords', {
template: '<div ng-include="url"></div>',
controller: "exampleController"
})
then in the exampleController
function($routeParams, $scope) {
$scope.url = $routeParams.keyWords;
}

Custom Routes in UI Router Angular JS (URL Shortcuts)

I have some states which look like this:
.state('patient.patient-login', {
abstract: true,
template: '<ui-view></ui-view>'
})
.state('patient.patient-login.practiceID', {
url: "/patient-login/:practiceID",
scope: true,
templateUrl: "views/patient-login.html"
})
The problem is the :practiceID variable can be quite long and not user friendly, I want to give a user the ability to create a custom URL which is tied to the practice ID, for example on Facebook when you create an account you start out with a profile URL like this: http://facebook.com/123456789 but then you can change it to http://facebook.com/whateveryouwant
So essentially I want to accomplish something like this:
POINTER
http://domain.com/companyname > http://domain.com/#/patient-login/companyid
ANOTHER EXAMPLE
http://domain.com/ladentist > http://domain.com/#/patient-login/-85k3jfGEioltold
I was able to allow a user to set their unique url but I'm not sure how to even start to setup the routing like that?
Not sure what the rest of your project looks like but you could update your state to handle both id's and unique urls in the resolve property. Which would roughly look like this:
.state('patient.patient-login.practiceID', {
url: "/patient-login/:practice",
scope: true,
templateUrl: "views/patient-login.html",
resolve: {
patientData: [
'$stateParams',
'practiceSvc', // replace practiceSvc with however you are fetching data from your server
function($stateParams, practiceSvc) {
// check if the :practice param is an id or unique url
if ($stateParams.practice === /* id check */) {
// Call to API - get practice by ID
return practiceSvc.getByID($stateParams.practice);
} else {
// Call to API - get practice by unique url
return practiceSvc.getBySlug($stateParams.practice);
}
}
]
}
})
This assumes you can tell the difference between a unique url and an id. If you can't do that you could update it to check for the id first then the unique url if no practice is found.

Initialize data only once with ui-router and md-tabs

I am using ui-router with Material Design's tabs in order to load tab content dynamically in an ngGrid.
Every view has its separate controller with a service which requests the data. Any ideas on how I can configure ui-router to initialize the data only once for each tab?
Currently, every time I click on a tab a new request is being fired and ngGrid is reinitialized.
ng-router config file, just in case:
$stateProvider
.state('tab1', {
url: '/tab1',
views: {
'tab1': {
templateUrl: 'tab1.html',
controller: 'tab1Controller'
}
}
})
.state('tab2', {
url: '/tab2',
views: {
'tab2': {
templateUrl: 'tab2.html',
controller: 'tab2Controller'
}
}
})
.state('tab3', {
url: '/tab3',
views: {
'tab3': {
templateUrl: 'tab3.html',
controller: 'tab3Controller'
}
}
});
UPDATED
In case, that data are different for each tab, we have to options.
use inheritance (view inheritance),
use Services
The first case will mean, that all states do have same parent. This parent declares Model = {}. Every sub-state can later access this Model and add or use the data
var data = Model.DataForTab1;
if(!data){
data = loadData();
Model.DataForTab1 = data;
}
next time, we come to this tab, we can reuse existing, in parent $scope referenced data Model
The second approach is to use caching inside of a special services for each data load. Such service would have almost the same logic (return loaded cached data or load them)
Difference? Services will have longer life time (through whole application). The parent of tabs will keep the data JUST until we go to another parent state.
ORIGINAL part
If data are the same across all the states, the most suitable way (I'd say) with UI-Router, is to
introduce one common parent state,
use resolve in that parent (will be executed just once)
inherit from it, all children can get such data:
Check this Q & A
// virtual parent 'root'
$stateProvider
.state('root', {
abstract: true,
template: '<div ui-view></div>',
resolve: {objectX : function() { return {x : 'x', y : 'y'};}},
controller: 'rootController',
})
// each state will declare parent state
// but its name and url is not effected
.state('tab1', {
parent: "root",
url: '/tab1',
Check more here and the working plunker

dynamic scope values angularjs

I have multiple routes which serves different pages like so:
$routeProvider.
when('/routeone', {
templateUrl: '/routes/one',
resolve: {}
})
.when('/routetwo', {
templateUrl: '/routes/two',
controller: 'CampaignController',
resolve: {
}
})
.when('/routethree', {
templateUrl: '/routes/three',
resolve: {}
});
Each route page has different model fields but uses one single $scope variable i.e. $scope.template so in one route/template it is:
<span>{{template.var1}}</span>
on other it is
<span>{{template.var2}}</span>
I am fetching route specific data and storing it in $scope.template, but the issue is even when it is assigned it is not showing it on UI fields which are bound.. initially my $scope.template is set to empty string.
If i set all the fields that are used in all the route template like
$scope.template = { var1 = 'var1', var2='var2'}
it works which i don't want to do but if i set it to empty and fetch route specific on route change it doesn't.. can someone plz help..
here is the plnkr
I think you need this
https://docs.angularjs.org/api/ngRoute/service/$routeParams

ui-router: A route with no view template

Is it possible to setup a route in ui-router that only has a controller? The purpose being that at a certain URL, the only thing I'd like to do is take action programatically, and not display anything in terms of a view. I've read through the docs, but I'm not sure if they offer a way to do this.
Yes, I have read this: https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-open-a-dialogmodal-at-a-certain-state, but that is not quite what I am looking for.
For example, let's just say I have a basic body with view:
<body ui-view></body>
And some basic config:
// Routes
$stateProvider
.state('myaction', {
url: "/go/myaction",
onEnter: function() {
console.log('doing something');
}
});
When /go/myaction is visited, the view is blank. Is it possible to do this?
I was able to solve this problem by redirecting the headless state I was taking programmatic action in, to a state WITH a view at the end of the headless state:
$stateProvider
.state('myaction', {
url: "/go/myaction",
onEnter: function() {
console.log('doing something');
}
controller: function($state) {
$state.go('home');
}
});
You can't have a controller without a view but you can use onEnter instead of a controller. If you don't want to change the current view when accessing this state you can define it as a child state:
$stateProvider
// the parent state with a template
.state('home', {
url: '/home',
templateUrl: '/home.html',
controller: 'HomeCtrl'
})
// child of the 'home' state with no view
.state('home.action', {
url: '/action',
onEnter: function() {
alert('Hi');
},
});
Now in home.html you can do something like this:
<a href ui-sref=".action">Greet me!</a>
From the docs:
Warning: The controller will not be instantiated if template is not defined.
Why don't you use an empty string as a template to overcome this?
Yes, you can do that. Use absolute view names to re-use the <ui-view> of another state.
Take a look at this example:
Users go to my app, but depending on them being authenticated or not, I want to send them to a public or private page. I use the index state purely to see if they're logged in or not, and then redirect them to index.private or index.public.
The child states make use of absolute view names to use the <ui-view> element that corresponds to the index state. This way, I don't need to make a second nested <ui-view>.
$stateProvider.state('index', {
url: "/",
controller: 'IndexCtrl'
}).state('index.private', {
views: {
"#": {
templateUrl: 'private.html',
controller: 'PrivateCtrl'
}
}
}).state('index.public', {
views: {
"#": {
templateUrl: 'public.html',
controller: 'PublicCtrl'
}
}
});
A small note on this example: I'm using the # shortcut here. Normally you would use viewname#statename.
My solution for this was just to include a template (html file) that is blank.

Resources