In the very new release of UI-Router1.0.0-alpha.1 Christopher Thielen announced dynamic parameters. For my understanding, if a param is configured to be dynamic, when changing it from the controller the URL should change accordingly. I tried several methods and couldn't make this happen. There doesn't seem to be binding between $state.params.myParam and $stateParams.myParam.
Can someone please share a working example, or tell me if it is indeed not working?
Thanks
It is not enough to just change the dynamic parameter. The location changes when performing state transition, using ui-sref or $state.go().
When transitioning to the current state where the only change is in parameters that are defined as dynamic, the controller is not reloaded.
example:
$stateProvider.state({
name: 'home',
url: '/home/:dynamicParam',
template: template,
controller: controller,
params: {
dynamicParam: {
dynamic: true
}
}
});
Change the param using: ui-sref=".({ dynamicParam: newVal })" or $state.go('.', { dynamicParam: newVal })
Here's a plunker made by Chris Thielen that demonstrates this.
Related
$stateProvider.state('state1', {
url:'/state1/:param1/and/{param2:.+}',
templateUrl: 'state1.html',
controller: 'State1Controller',
});
I'm trying to make param2 required by using regex as seen above. If it's empty, then the default state should load:
$urlRouterProvider.otherwise("/");
$stateProvider.state('otherwise',{
url: '/',
templateUrl:'default.html'
});
Now the results:
state1/1/and/1 goes to state1. Good.
state1/1/and goes to otherwise. Good.
But,
state1/1/and/ goes to no state! Neither states are loaded. It's not redirecting back to /. What?!
How do I properly make a parameter required?
Angular js ui-router url parameters are optional by default. For your case above we could make use of $stateParams to check if the required parameter is defined or not. Please check the code below.
if ($stateParams.param2=== undefined) {
// Navigate to home.
$location.path('/');
}
Hope this would solve your issue. Thanks.
I have a link on my page (inside the scope of angular app 1) which changes the hash location
/app/#/location
I have another angular app (2) which is not reacting to the hash location change in the way I expect it to (ie by firing locationChangeStart, and changing state). I don't fully understand why. Anybody can explain this to me?
Edit 1: yes, there are two angular apps on the page, of different angular versions, both bootstrapped (sigh, don't ask). The ui-router configuration looks like this:
$urlRouterProvider.otherwise(($injector) => {
let $state = $injector.get("$state");
[... snip ...]
$state.go('list');
});
// Now set up the states
$stateProvider
.state('messaging', {
url: "/messaging/{param}",
templateUrl: "messaging.html",
controller: 'MessagingController'
})
.state('list', {
url: "/list",
templateUrl: "list.html",
controller: 'ListController'
});
Nothing really too fancy here, and before anybody asks, yes, I do need to check on a state in the otherwise.
The bootstrapping looks like this:
angular.element(document).ready(function() {
angular.element(document.getElementById('app-bootstrap')).prepend('<div ui-view></div>');
angular.bootstrap(document.getElementById('app-bootstrap'), ['app']);
});
Edit 2: this seems like it may be not an angular-related issue at all, as using the window 'hashchange' event directly doesn't seem to fire either; I've confirmed that window.onhashchange is the correct function.
(angular 1.4.7)
Considering the following states taken from the ui-router documentation:
.state('state1', {
url: '/state1',
templateUrl: 'partials/state1.html'
controller: 'State1Ctrl'
})
.state('state1.list', {
url: '/list',
templateUrl: 'partials/state1.list.html',
})
And the controller for "partials/state1.html" for state "state1":
.controller('State1Ctrl', function () {
});
Is there any built-in feature to determine from within the controller or within the template, what state the controller/template is associated with?
For example:
.controller('State1Ctrl', function ($state) {
console.log($state.this); // state1
});
And if there is no built-in solution, how would you "decorate" $state or $stateParams, to contain this information?
The only solution I've come up with is to use $state.get() and then find the state with the controller or template value. This seems incredibly messy, though.
You can access the current state configuratin object like this:
$state.current
For further information take a look at the $state documentation.
You can do it as follow,
$state.current.name //Return the name of current state
Couldn't find this documented anywhere, so I looked in the source code.
There is a data field named $uiView attached to the ui-view element, it contains the view name and the associated state. You can get this state like this:
elem.closest('[ui-view]').data('$uiView').state
or even
elem.inheritedData('$uiView').state
We can see what is defined for current state, using the $state.current, check this example showing:
state1
{
"url": "/state1",
"template": "<div>state1 <pre>{{current | json }}</pre><div ui-view=\"\"></div> </div>",
"controller": "State1Ctrl",
"name": "state1"
}
list
{
"url": "/list",
"template": "<div>list <pre>{{current | json }}</pre></div>",
"controller": "State1Ctrl",
"name": "state1.list"
}
the controller:
.controller('State1Ctrl', function($scope, $state) {
$scope.current = $state.current
});
check that here
EXTEND: The above example will always return current state - i.e. if there is hierarchy of three states and we access the last state ('state1.list.detail') directly:
<a ui-sref="state1.list.detail({detailId:1})">....
Each controller will be provided with the same state: $state("state1.list.detail").
Why? beause this state has enough information what are all the views (hierarchically nested) and their controllers needed. We can observe that all in the
$state.$current // not .current
Quickly discussed here cite:
In addition, users can attach custom decorators, which will generate new properties within the state's internal definition. There is currently no clear use-case for this beyond accessing internal states (i.e. $state.$current), however, expect this to become increasingly relevant as we introduce additional meta-programming features.
BUT: there is NO way, how to get information from current controller instance, to which $state it belongs! Even any iterations, searching through some $state.get('stateName') will be unreliable, because simply there is no kept relation that way. One controller Class could be used for many views as different Instances. And also, from my experience, I do not remember any case, when I needed to know such information... wish now it is a bit more clear
This is useful if you are looking for Current state name, $state.current.name
Not sure it is the same version, but on 0.3.1 you can use this to get the current state:
$state.$current.name
And to perform a check:
$state.is('contact.details.item');
Documentation:
https://ui-router.github.io/ng1/docs/0.3.1/index.html#/api/ui.router.state.$state
A working "out of the box" Controller from your code, which shows state:
.controller('State1Ctrl', function ($state) {
console.log("Current State: ", $state.current.name);
});
If you want to check the current state
console.log("state", $state.current)
If you want to check the name of the current state name
console.log("statename", $state.current.name)
I am using ui-router to represent states in my AngularJS app. In it I'd like to change the state without changing the URL (basically a "detail view" is updated but this should not affect the URL).
I use <a ui-sref="item.detail({id: item.id})"> to display the detail but this only works if I specify a URL like url: "/detail-:id" in my $stateProvider.
It seems to me that the current state is only defined through the URL.
Just an additional information for new comers to this post:
Declaration of params in a state definition has changed to params: { id: {} } from params: ['id']
So be aware :)
Source: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$stateProvider
Thanks for your answer, it did help me in the right direction but I'd just like to add a more complete description.
In my specific issue there was a complicating factor because the state I needed to inject a non-URL parameter to was a child state. That complicated things slightly.
The params: ['id'] part goes in the $stateProvider declaration like this:
$stateProvider.state('parent', {
url: '/:parentParam',
templateUrl: '...',
controller: '...'
}).
state('parent.child', {
params: ['parentParam','childParam'],
templateUrl: '...',
controller: '...'
});
And the param name is connected to the ui-sref attribute like this:
<a ui-sref=".child({ childParam: 'foo' })">
And the catch is this:
If the parent state also has a URL parameter then the child needs
to also declare that in its params array. In the example above "parentParam" must be included in the childstate.
If you don't do that then a module-error will be thrown when the application is initialized. This is at least true on the latest version at the time of writing (v.0.2.10).
EDIT
#gulsahkandemir points out that
Declaration of params in a state definition has changed to params: {
id: {} } from params: ['id']
Judging by the changelog, this seems to be the case starting from v0.2.11
Details of params can be found in the official docs
I now figured out, that you need to use the params: ['id'] property of the state in order to have the key not stripped when not using a URL.
I can't figure out a reasonable way, which doesn't feel like a hack, to solve this rather trivial problem.
I want a guest to see a splash page when they access the index of the website and a logged in user to see their profile, with each page having it's own template and controller. Ideally, there would be two states for one url, and somehow I would be able to automatically alter the active one depending on the loggin status. Both of these views will have their own nested views so ng-include cannot be used (I assume).
I'm quite new to angular and ui router and think I might be overlooking an easy solution to the problem.
Could it be done with named views and ng-show?
If you're using UI Router, just create three states: the root state, with the '/' URL, and two direct descendant states with no URLs. In the onEnter of the root state, you detect the state of the user and transition to the correct child state accordingly. This gives the appearance of keeping the same URL for both child states, but allows you to have to separate states with separate configurations.
The templateUrl can be a function as well so you can check the logged in status and return a different view and define the controller in the view rather than as part of the state configuration
My Solution:
angular.module('myApp')
.config(function ($stateProvider) {
$stateProvider
.state('main', {
url: '/',
controller: function (Auth, $state) {
if (someCondition) {
$state.go('state1');
} else {
$state.go('state2');
}
}
});
});
where state 1 and state 2 are defined elsewhere.
For docs purposes, I used:
$rootScope.$on('$stateChangeStart', function(event, toState) {
if ((toState.name !== 'login') && (!$localStorage.nickname)) {
event.preventDefault();
$state.go('login');
}
});
Using $routeChangeStart didn't work for me.
It is used for me conditional view in ui-route
$stateProvider.state('dashboard.home', {
url: '/dashboard',
controller: 'MainCtrl',
// templateUrl: $rootScope.active_admin_template,
templateProvider: ['$stateParams', '$templateRequest','$rootScope', function ($stateParams, templateRequest,$rootScope) {
var templateUrl ='';
if ($rootScope.current_user.role == 'MANAGER'){
templateUrl ='views/manager_portal/dashboard.html';
}else{
templateUrl ='views/dashboard/home.html';
}
return templateRequest(templateUrl);
}]
});
If I understand the question; you want to make sure that the user who hasn't logged in cannot see a page that requires log in. Is that correct?
I've done so with code like this inside a controller:
if(!'some condition that determines if user has access to a page'){
$location.path( "/login" );
}
Anywhere (probably in some high-level controller) you should be able to just bind a '$routeChangeStart' event to the $rootScope and do your check then:
$rootScope.$on('$routeChangeStart', function(next, current){
if(next != '/login' && !userLoggedIn){
$location.path( "/login" );
}
});
This will get fired every time a new route is set, even on the first visit to the page.
The way I've done this is pretty simple. I made one for our A/B testing strategy. This is the gist:
resolve: {
swapTemplate: function(service) {
// all of this logic is in a service
if (inAbTest) {
this.self.templateUrl = '/new/template.html';
}
}
... other resolves
}
This gets called before the template is downloaded and therefor you're allowed to swap out the template url.
In my case, if two states can share logic of same controller, conditional template is a good choice. Otherwise, creating separate states is a good option.