Angular route act like another route without url change - angularjs

I'm using Angular-UI router and in my project I have this structure of pages:
- Main (/main)
-- Table (/main/table/:userid)
-- Info (/main/info)
-- About (/main/about)
In case the user going to the /main I want it to act like the user hit the /main/table/1 without causing url change.
How can I achieve that ?
Here are my states:
$stateProvider
.state('main', {
'url': '/main',
'templateUrl': '/pages/main.html',
'controller': 'MainController',
'resolve': { ... }
})
.state('main.table', {
'url': '/main/table/:userid',
'templateUrl': '/pages/table.html',
})
.state('main.info', {
'url': '/main/info',
'templateUrl': '/pages/info.html',
})
.state('main.about', {
'url': '/main/about',
'templateUrl': '/pages/about.html',
})

I created working plunker here. The trick is to reuse the "main.table" stuff directly in the main state.
We can have main state define like this:
$stateProvider
.state('main', {
'url': '/main',
views: {
'': {
'templateUrl': '/pages/main.html',
'controller': 'MainController',
},
'#main': {
'templateUrl': '/pages/table.html',
'controller': 'TableController',
}
}
})
And these are almost unchanged, just the /main is replaced from url, it will be passed by parent.
.state('main.table', {
'url': '/table/:userid',
'templateUrl': '/pages/table.html',
'controller': 'TableController',
})
.state('main.info', {
'url': '/info',
'templateUrl': '/pages/info.html',
})
.state('main.about', {
'url': '/about',
'templateUrl': '/pages/about.html',
})
And this would be the controller for table view
.controller('TableController', function($scope, $stateParams) {
$scope.userid = $stateParams.userid || 1;
})
Check it all here
The technique used here is: The main state does have two views. One of them is the main - layout template. The second is immediately injecting other view into that layout. via absolute naming '#main' (unnamed view in the state main)
That view (for displaying table) is the same which we use for main.table state. We just check, that if there is no param userid - 1 is used
Read more about this multi views here
View Names - Relative vs. Absolute Names
small extract from example snippet:
$stateProvider
.state('contacts', {
// This will get automatically plugged into the unnamed ui-view
// of the parent state template. Since this is a top level state,
// its parent state template is index.html.
templateUrl: 'contacts.html'
})
.state('contacts.detail', {
views: {
////////////////////////////////////
// Relative Targeting //
// Targets parent state ui-view's //
////////////////////////////////////
// Relatively targets the 'detail' view in this state's parent state, 'contacts'.
// <div ui-view='detail'/> within contacts.html
"detail" : { },
// Relatively targets the unnamed view in this state's parent state, 'contacts'.
// <div ui-view/> within contacts.html
"" : { },
///////////////////////////////////////////////////////
// Absolute Targeting using '#' //
// Targets any view within this state or an ancestor //
///////////////////////////////////////////////////////
// Absolutely targets the 'info' view in this state, 'contacts.detail'.
// <div ui-view='info'/> within contacts.detail.html
"info#contacts.detail" : { }
// Absolutely targets the 'detail' view in the 'contacts' state.
// <div ui-view='detail'/> within contacts.html
"detail#contacts" : { }
// Absolutely targets the unnamed view in parent 'contacts' state.
// <div ui-view/> within contacts.html
"#contacts" : { }
// absolutely targets the 'status' view in root unnamed state.
// <div ui-view='status'/> within index.html
"status#" : { }
// absolutely targets the unnamed view in root unnamed state.
// <div ui-view/> within index.html
"#" : { }
});

stateProvider url property is responsible for browser URL routing mechanism.
stateProvider templateUrl property is a html partial view template which is going to render against a particular state, in our case it is main.
$stateProvider
.state('main', { // **main** is a state.
'url': '/main', // **/main** is a preferred url you want to set in the browser.
'templateUrl': '/main/table/1', // **/main/table/1** is a template to be rendered.
'controller': 'MainController',
'resolve': { ... }
})

Related

Issue with Nested State in Angular UI-Router

I'm trying to create 2 separate URL states within the same config file.
I was following this template below.
$stateProvider.state('parent', {
data:{
customData1: "Hello",
customData2: "World!"
}
})
.state('parent.child', {
data:{
// customData1 inherited from 'parent'
// but we'll overwrite customData2
customData2: "UI-Router!"
}
});
My code is below. The /reports/moveFrom works fine; however, the /drillDown route is not even registering. Hitting that url sends me back to my apps homepage.
I'm wondering if the parent.child notation is getting messed up by the app.report_moveFrom.drillDown as it has 2x (.)
Any help would be greatly appreciated.
$stateProvider
.state('app.report_moveFrom', {
url: '/reports/moveFrom',
views:{
'main': {
template: require('./moveFrom.html'),
controller: 'moveFromController as $ctrl'
}
},
title: 'moveFrom'
})
.state('app.report_moveFrom.drillDown', {
url: '/drillDown',
views:{
'main': {
template: require('./drillDown.html'),
controller: 'moveFromController as $ctrl'
}
},
title: 'drillDown'
})
Can you please add the code that you using to make a call.
You should be calling something like this:
/reports/moveFrom/drillDown
If you are not doing this, kindly change it once and check
This is the piece from the lib:
$stateProvider
.state('contacts', {
abstract: true,
url: '/contacts',
// Note: abstract still needs a ui-view for its children to populate.
// You can simply add it inline here.
template: '<ui-view/>'
})
.state('contacts.list', {
// url will become '/contacts/list'
url: '/list'
//...more
})
.state('contacts.detail', {
// url will become '/contacts/detail'
url: '/detail',
//...more
})
The point is: each . (dot) in state name represents nesting. I.e
// child of the 'app' state
.state('app.report_moveFrom', {
// child of the above 'app.report_moveFrom'
.state('app.report_moveFrom.drillDown', {
Also, any view targeting, is using by default relative naming. It means, UI-Router searches for (un)named view in the parent state. So, the 'app.report_moveFrom' must contain app.report_moveFrom if we use this syntax as this:
// parent for next state
.state('app.report_moveFrom', {
...
// the default view name resolution is related to parent
.state('app.report_moveFrom.drillDown', {
url: '/drillDown',
views:{
// that ui-view="main" target must be in the above state
'main': {
...
and finally, if we have a child, it inherits the parent url as well
// parent url
.state('app.report_moveFrom', {
url: '/reports/moveFrom',
...
})
//child url - is later extended with parent part
.state('app.report_moveFrom.drillDown', {
url: '/drillDown',
...
// and in runtime this state has '/reports/moveFrom' + '/drillDown'
// i.e.: '/reports/moveFrom/drillDown'
But we can change this default behavior, with just a few tricks.
1) Reset url, to start at root, and 2) target grand parent, with absolute view naming
.state('app.report_moveFrom.drillDown', {
// this sign at the beginning will rest url evaluation - to root level
url: '^/drillDown',
views:{
// we use absolute naming here, so this state will be placed into grand parent 'app'
'main#app': {
...
But the best would be (in my view) simply remove the '.' from the state name, and by default create a sibling (not a child)
.state('app.report_moveFrom', {
// remove dot
//.state('app.report_moveFrom.drillDown', {
.state('app.report_moveFrom_drillDown', {
...and now all will be working by design - because we did not created too much nested state

Issue with UI router and variables no longer being in the $scope after transitioning from one to another state

I have three states: one abstract state and two concrete states inheriting from the abstract one. I am transitioning from one state to another and I noticed that the variables that were in the $scope in one state are no longer in $scope after I have transitioned to the other state: see $scope.signupForm.member.email below.
Can someone please advise?
My UI router configuration:
$stateProvider
.state('signup', {
abstract: true,
views: {
'#': {
templateUrl: 'signup/views/signup.html'
}
}
})
.state('signup.form', {
url: '/signup',
views: {
'#signup': {
controller: 'SignupCtrl',
templateUrl: 'signup/views/signup.form.html'
}
}
})
.state('signup.success', {
url: '/signup/success',
views: {
'#signup': {
controller: 'SignupCtrl',
templateUrl: 'signup/views/signup.success.html'
}
}
})
Relevant snippet from my controller:
signupService.signup($scope.signupForm)
.success(function () {
//TODO: issue with controller no longer being in scope: signupForm.member.email is not displayed in template
$state.go('signup.success');
});
My email input (from signup.form.html):
<input type="email" name="email"
placeholder="{{'SIGNUP_FORM_EMAIL' | translate}}"
ng-model="signupForm.member.email" ng-required="true"
ng-pattern="EMAIL_PATTERN"
class="form-control"/>
Where I try to display the email (from signup.success.html):
<div class="panel-body">
success!
check your email at: {{signupForm.member.email}}
</div>
edit 1:
If I pull up the controller one level - by putting it into the abstract state i.e. 'signup', then signupFormCtrl - the angular form controller - is undefined!
<form name="signupFormCtrl" ng-submit="signup()" novalidate>
edit 2:
This is what I tried:
.state('signup', {
abstract: true,
views: {
'#': {
controller: 'SignupCtrl',
templateUrl: 'signup/views/signup.html'
}
}
})
.state('signup.form', {
url: '/signup',
views: {
'#signup': {
templateUrl: 'signup/views/signup.form.html'
}
}
})
.state('signup.success', {
url: '/signup/success',
views: {
'#signup': {
templateUrl: 'signup/views/signup.success.html'
}
}
})
Thre is a working plunker
This is feasable with UI-Router built-in features. We will need to introduce controller for our base state:
.state('signup', {
abstract: true,
templateUrl: 'signup/views/signup.html',
controller: 'SignupBaseCtrl',
})
Inside of this controller we would define a Model inside of a $scope:
.controller('SignupBaseCtrl', ['$scope', function ($scope) {
$scope.signupForm = { member : { email : null }};
}])
And now, if we would work with a model like this:
{{signupForm.member.email}}
In any of our child states, we would be accesing the same model, the same reference object singupForm.
And how it is possible? how it is working? All is clearly explained here:
Scope Inheritance by View Hierarchy Only
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states.
You can also check: Controller from Parent Layout is not access by child views
Data from one scope can not be accessed from a different scope. try using the rootScope for data that is to be used across scopes use $root in templates as in {{$root.signupForm.member.email}} and $rootScope in controllers as in $rootScope.signupForm.member.email

$stateParams field is undefined if not located within its corresponding ui-view

I am trying for a parent state/view to access a child state/view. I am looking for a workaround.
My state provider configuration is as follows:
.state('advertisement', {
url: '/advertisement', abstract: true, parent: 'authenticated'
})
.state('advertisement.new', {
url: '/new',
abstract: true,
views: {
'#': {
controller: 'AdvertisementSaveCtrl',
templateUrl: 'advertisement/views/advertisement.form.html'
}
}
})
.state('advertisement.new.field', {
url: '/:fieldId',
views: {
'#advertisement.new': {
templateUrl: function ($stateParams){
return 'advertisement/views/fields/advertisement.' + $stateParams.fieldId + '.html';
}
}
}
})
In my markup:
<li ui-sref-active="active" ng-class="{'active': isActive()}"><!-- not in scope! -->
<div ui-view></div><!-- this is the view targeted by advertisement.new.field -->
The advertisement.new.field state changes according to the current field (:fieldId). I have set up a number of links (located outside of my nested ui-view) that change the state by changing the :fieldId state param but obviously, the $stateParam.fieldId is undefined if it is not within the corresponding ui-view div.
To put it differently, it seems the isActive method has no access to $stateParam.fieldId...
Can anyone please provide a workaround?
In your code samples I don't see in which controller the isActive() method is defined. But it must be defined in a controller.
I assume the isActive() function is defined on a controller named MainController.
You can then inject the $state service in this controller and via the $state.params you can access the parameters of the active state even outside of the <ui-view> tag:
app.controller('MainController', function ($scope, $state) {
console.log($stateParams);
this.isActive = function() {
// access params of active state via $state.params
console.log($state.params.fieldId);
};
});

Issue with the Angular UI router when inheriting from a parent state

I have an issue with my Angular UI router configuration.
I have an abstract authenticated state that all of my authenticated pages inherit from. Furthermore, I have a dashboard state that tries to inherit from this authenticated state using the parent: 'authenticated' notation.
Here is my configuration:
.state('authenticated', {
abstract: true,
resolve: {
currentMember: ['$rootScope', '$q', function ($rootScope, $q) {
return $rootScope.authenticated || $q.reject({unAuthorized: true});
}]
}
})
.state('dashboard', {
url: '/dashboard',
controller: 'DashboardCtrl',
templateUrl: 'dashboard/views/dashboard.view.html',
parent: 'authenticated'
})
However, using the above configuration, my dashboard page is always empty...
If I comment out the parent property as follows:
//parent: 'authenticated'
..then the dashboard view is populated with its contents properly...
Can anyone please help?
What we need here is a target for our child view, we need a setting: template: "<ui-view />" in the parent state definition:
.state('authenticated', {
abstract: true,
template: "<ui-view />", // here
resolve: {
currentMember: ['$rootScope', '$q', function ($rootScope, $q) {
return $rootScope.authenticated || $q.reject({unAuthorized: true});
}]
}
})
This template: "<ui-view />", piece of code is now doing essential part of our state machine: it creates target for our child .state('dashboard'.... That state will now be placed (its unnamed view) into that parent target.
The reason why it was working, when we commented out the parent: setting was:
our view of the state 'dashboard' was injected into index.html target <div ui-view=""> - (index.html is so called root state).
The templatce could also be like '<div ui-view=""></div>'. I just used simplified expression resulting in the same behaviour...
To get more ideas about that all, please, check:
View Names - Relative vs. Absolute Names
Behind the scenes, every view gets assigned an absolute name that follows a scheme of viewname#statename, where viewname is the name used in the view directive and state name is the state's absolute name, e.g. contact.item. You can also choose to write your view names in the absolute syntax.
...
code snippet cite:
.state('contacts.detail', {
views: {
////////////////////////////////////
// Relative Targeting //
// Targets parent state ui-view's //
////////////////////////////////////
// Relatively targets the 'detail' view in this state's parent state, 'contacts'.
// <div ui-view='detail'/> within contacts.html
"detail" : { },
// Relatively targets the unnamed view in this state's parent state, 'contacts'.
// <div ui-view/> within contacts.html
"" : { },
///////////////////////////////////////////////////////
// Absolute Targeting using '#' //
// Targets any view within this state or an ancestor //
///////////////////////////////////////////////////////
// Absolutely targets the 'info' view in this state, 'contacts.detail'.
// <div ui-view='info'/> within contacts.detail.html
"info#contacts.detail" : { }
// Absolutely targets the 'detail' view in the 'contacts' state.
// <div ui-view='detail'/> within contacts.html
"detail#contacts" : { }
// Absolutely targets the unnamed view in parent 'contacts' state.
// <div ui-view/> within contacts.html
"#contacts" : { }

How do I prevent reload on named view, when state changes? AngularJS UI-Router

I'm using the excellent ui-router module in my application. As part of this, I'm using named views to manage the 'dynamic sub-navigation' I have in the app.
Consider the following:
$urlRouterProvider.otherwise('/person/list');
$stateProvider
.state('person', {
url: '/person',
abstract: true,
})
.state('person.list', {
url: '/list',
views: {
"main#": {
templateUrl: "person.list.html",
controller: 'PersonListController'
}
}
})
.state('person.details', {
url: '/{id}',
views: {
'main#': {
templateUrl: "person.details.html",
controller: 'PersonController'
},
'nav#': {
templateUrl: "person.nav.html",
controller: 'PersonNavController'
}
}
});
When users first visit the app, they are presented with a list of people. When they click on a person, they are taken to the details page. Pretty basic stuff. Here's the markup if it helps...
<div>
<aside ui-view="nav"></aside>
<div ui-view="main"></div>
</div>
However, the PersonNavController calls a REST service to get a list of people, so when viewing a person, the user is able to navigate sibling elements. Using the method above causes the template and controller to re-render, thus causing a delay after every click, despite the content never changing.
Is there a way to keep the 'nav#' view loaded, and only refresh the 'main#' view?
The way I am using ui-router in this scenarios is: move the views to the least common denominator.
Other words: In case that ui-view="nav" is shared among all the details and is the same for all of them (because it should be loaded only once) - it should be part of the list state (parent of the detail state)
the parent state defintion would be adjusted like this:
.state('person.list', {
url: '/list',
views: {
"main#": {
templateUrl: "person.list.html",
controller: 'PersonListController'
}
// here we target the person.list.html
// and its ui-view="nav"
'nav#person.list': {
templateUrl: "person.nav.html",
controller: 'PersonNavController'
}
}
So where is the trick? In the power of the angular ui-router. We can, during each state defintion, target the current view. Now, the nav view is part of the list state definition - i.e. it will not be reloaded during the detail switching (also check here for more explanation)
We just have to use the defined naming conventions, see:
View Names - Relative vs. Absolute Names
Few cited lines from the mentioned documentation:
views: {
////////////////////////////////////
// Relative Targeting //
// Targets parent state ui-view's //
////////////////////////////////////
// Relatively targets the 'detail' view in this state's parent state, 'contacts'.
// <div ui-view='detail'/> within contacts.html
"detail" : { },
// Relatively targets the unnamed view in this state's parent state, 'contacts'.
// <div ui-view/> within contacts.html
"" : { },
///////////////////////////////////////////////////////
// Absolute Targeting using '#' //
// Targets any view within this state or an ancestor //
///////////////////////////////////////////////////////
// Absolutely targets the 'info' view in this state, 'contacts.detail'.
// <div ui-view='info'/> within contacts.detail.html
"info#contacts.detail" : { }
// Absolutely targets the 'detail' view in the 'contacts' state.
// <div ui-view='detail'/> within contacts.html
"detail#contacts" : { }

Resources