Getting the name of a state in its `onEnter` hook - angularjs

I'm building an application where I want to toggle a property in a service the moment a user enters and leaves a route. To do this I need to know about the state's name in the onEnter and onExit hooks. This is relatively easy for the onExit hook since I can just inject the $state service and read the name of the current state. But since the current state has not been set yet when the onEnter hook is called there is no way of knowing what the state we're transitioning to.
I still need to to have fine control over other parts of the state so I'd rather not have any for loops. I'm looking for a way to be able to pass the onEnter function to the state, whilst still retrieving the state's name inside of the function itself.
Here is the code I've written:
function onEnter($state, Steps) {
var stateName = $state.current.name; // Not possible. The current state has not been set yet!
var step = Steps.getByStateName(stateName);
step.activate();
}
function onExit($state, Steps) {
var stateName = $state.current.name; // No problem. We know about the state.
var step = Steps.getByStateName(stateName);
step.deactivate();
}
$stateProvider
.state('step1', {
url: '/step1',
templateUrl: 'templates/step1.html',
controller: 'StepOneController',
onEnter: onEnter,
onExit: onExit
});
My solution I'm using for now is to use a factory to create context for the onEnter function passed to the state. This is far from ideal because I still need to pass the state's name to it.
Here is an example of said workaround:
function onEnterFactory(stateName) {
return function onEnter(Steps) {
var step = Steps.getByStateName(stateName);
step.activate();
}
}
$stateProvider
.state('step1', {
url: '/step1',
templateUrl: 'templates/step1.html',
controller: 'StepOneController',
onEnter: onEnterFactory('step1')
});

Use this in onEnter onExit hooks. onEnter is invoked by following command:
$injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
The second paramater of $injector.invoke is the value of this for the function it calls. So your code should look as follows:
function onEnter(Steps) {
var stateName = this.name;
var step = Steps.getByStateName(stateName);
step.activate();
}
function onExit(Steps) {
var stateName = this.name;
var step = Steps.getByStateName(stateName);
step.deactivate();
}
$stateProvider
.state('step1', {
url: '/step1',
templateUrl: 'templates/step1.html',
controller: 'StepOneController',
onEnter: onEnter,
onExit: onExit
});
Here is a working example of accessing a state's name in the onEnter and onExit hooks:
angular.module('myApp', ['ui.router'])
.config(function($stateProvider) {
function onEnter() {
console.log('Entering state', this.name);
}
function onExit() {
console.log('Exiting state', this.name);
}
$stateProvider.state('state-1', {
url: '/state-1',
template: '<p>State 1</p>',
onEnter: onEnter,
onExit: onExit
}).state('state-2', {
url: '/state-2',
template: '<p>State 2</p>',
onEnter: onEnter,
onExit: onExit
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.6/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.3.1/angular-ui-router.js"></script>
<div ng-app="myApp">
<nav>
<a ui-sref="state-1">State 1</a>
<a ui-sref="state-2">State 2</a>
</nav>
<div ui-view></div>
</div>

In one of my projects we used something like this
app.run(function($rootScope, $state, $location) {
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams,
fromState) {
$state.previous = fromState;
});
to remember the previous state. But you might as well remember the new state and store the information somewhere.

You already know which state it will be, because you define it in the .state('statename',. To not write the same name twice, you can define the state variables beforehand:
var steps = ["step1", "step2", "step3"]; // or, something like Steps.getSteps()
$stateProvider
.state(steps[0], {
url: '/step1',
templateUrl: 'templates/step1.html',
controller: 'StepOneController',
onEnter: function(Steps) {
var stateName = steps[0];
var step = Steps.getByStateName(stateName);
step.activate();
},
onExit: function($state, Steps) {
var stateName = $state.current.name; // No problem. We know about the state.
var step = Steps.getByStateName(stateName);
step.deactivate();
}
});
You can even make it dynamic this way:
var steps = [
{ name: "step1", url: "/step1" },
{ name: "step2", url: "/step2" },
{ name: "step3", url: "/step3" }
]; // Or something like Steps.getSteps();
for (var i = 0; i < states.length; i++) {
var state = steps[i];
$stateProvider.state(state.name,
url: state.url,
templateUrl: "templates/" + state.name + ".html",
onEnter: function(Steps) {
var step = Steps.getByStateName(state.name);
step.activate();
// or just: state.activate();
},
onExit: function($state, Steps) {
var stateName = $state.current.name; // No problem. We know about the state.
var step = Steps.getByStateName(stateName);
step.deactivate();
}
}

You could extend your factory solution a little bit and make it more flexible.
Maybe have a provider that reacts to the state changes.Then you could just inject this provider/service to onEnter function or where-ever you may need it.
Related plunker here http://plnkr.co/edit/6Ri2hE
angular.module('app', ['ui.router'])
.provider('myState', function myStateProvider() {
var state;
this.onEnter = function() {
console.log('provider.onEnter', state);
};
this.$get = function($rootScope) {
var myState = {};
myState.initialize = function() {
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
state = toState;
});
};
myState.getState = function() {
return state;
};
return myState;
};
})
.config(function($stateProvider, myStateProvider) {
$stateProvider
.state('step1', {
url: '/step1',
template: '<div>step1 template</div>',
controller: function() {},
onEnter: myStateProvider.onEnter // usage example
})
.state('step2', {
url: '/step2',
template: '<div>step2 template</div>',
controller: function() {},
onEnter: function(myState) { // other usage example
console.log('state.onEnter', myState.getState());
}
});
})
.run(function(myState) {
myState.initialize();
});
<a ui-sref="step1">state:step1</a>
<a ui-sref="step2">state:step2</a>
<div ui-view></div>
This would console.log() the following, if links are clicked sequentially.

Add a 'name' property naming the state:
$stateProvider
.state('step1', {
name: 'step1' // <- property naming the state
url: '/step1',
templateUrl: 'templates/step1.html',
controller: 'StepOneController',
onEnter: onEnter
});
The route's name is then accessible by this.name in the onEnter callback:
function onEnter() {
var stateName = this.name; // <- Retrieve the state's name from its configuration
var step = Steps.getByStateName(stateName);
step.activate();
}
To not write the same state name twice, you could start by defining the states in a separate object and enriching the states with their name before adding them to the $stateProvider:
var routes = {
"step1": {
url: '/step1',
templateUrl: 'templates/step1.html',
controller: 'StepOneController',
onEnter: onEnter
}
};
for(var routeName in routes) {
var route = routes[routeName];
// Enrich route with its name before feeding
// it to the $stateProvider
route.name = routeName;
$stateProvider.state(route.name, route);
}

Maybe you can use resolve,
$stateProvider
.state('step1', {
url: '/step1',
templateUrl: 'templates/step1.html',
controller: 'StepOneController',
resolve: {
onenter : function( Steps ) {
// use this.self.name to access state name
var step = Steps.getByStateName(this.self.name);
step.activate();
}
}
} );
If the above this approach seems unclean then maybe one can use decorator to populate current state.
angular.config(function($provide) {
$provide.decorator('$state', function($delegate, $rootScope) {
$rootScope.$on('$stateChangeStart', function(event, state) {
$delegate.next = state;
});
return $delegate;
});
});
State will be available in $state.next inside resolve function.

Related

Can a UI-Router parent state access it's child's members?

I'm using AngularJS's UI-Router to manage routes for my web application.
I have two states: parent_state and child_state arranged as shown below.
$stateProvider
.state('parent_state', {
abstract: true,
views: {
'#' : {
templateUrl: 'http://example.com/parent.html',
controller: 'ParentCtrl'
}
}
})
.state('child_state', {
parent: 'parent_state',
url: '/child',
params: {
myArg: {value: null}
},
views: {
'mainarea#parent_state': {
templateUrl: 'http://example.com/child.html',
controller: 'ChildCtrl'
}
}
})
From within ChildCtrl, I can access myArg like this:
app.controller("ChildCtrl", function($stateParams) {
console.log('myArg = ', $stateParams.myArg);
});
Is it possible for me to access myArg and have it displayed in the html page parent.html? If so, how can it be done? I see that the ParentCtrl controller for the abstract state is never even called.
This question addresses a related topic. But it doesn't show me how to display a parameter to the child state in a template of the parent state.
The first thing that comes to my mind is to use events for notifying parent after child param change. See the following (you can even run it here).
Child, after rendering, emits an event to the parent with the changed value of the parameter. Parent grabs and displays it in its own template.
angular.module('myApp', ['ui.router'])
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('parent_state', {
abstract: true,
template: "<h1>Parent! Value from child: {{ paramFromChild }}</h1><div ui-view></div>",
controller: function ($scope) {
$scope.$on('childLoaded', function (e, param) {
$scope.paramFromChild = param;
});
}
})
.state('child_state', {
parent: 'parent_state',
url: '/child',
params: {
myArg: {value: null}
},
template: '<h2>Child! Value: {{ param }}</h2>',
controller: function($stateParams, $scope){
$scope.param = $stateParams.myArg;
$scope.$emit('childLoaded', $stateParams.myArg);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.10/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.20/angular-ui-router.js"></script>
<div ng-app="myApp">
<a ui-sref="child_state({myArg: 'first'})">First link</a>
<a ui-sref="child_state({myArg: 'second'})">First second</a>
<div ui-view></div>
</div>
Is it possible for me to access myArg and have it displayed in the
html page parent.html?
That is against the principle of the UI-Router. Parent params can be consumed in children, but not vice versa. How would parent view know about changes WITHOUT re-initializing the controller? You need something like watching.
The true way is to employ Multiple Named Views. Look at this working plunkr.
Yes, this is possible.
Using $stateChangeSuccess:
You can use $stateChangeSuccess to achieve this.
For example:
.state('main.parent', {
url: '/parent',
controller: 'ParentController',
controllerAs: 'vm',
templateUrl: 'app/parent.html',
data: {
title: 'Parent'
}
})
.state('main.parent.child', {
url: '/child',
controller: 'ChildController',
controllerAs: 'vm',
templateUrl: 'app/child.html'
})
And in the runblock call it as follows:
$rootScope.$on('$stateChangeSuccess', function (event, toState, fromState) {
var current = $state.$current;
if (current.data.hasOwnProperty('title')) {
$rootScope.title = current.data.title;
} else if(current.parent && current.parent.data.hasOwnProperty('title')) {
$rootScope.title = current.parent.data.title;
} else {
$rootScope.title = null;
}
});
Then you can access the $rootScope.title from the child controller since it is globally available.
Using a Factory or Service:
By writing setters and getters you can pass data between controllers. So, you can set the data from the child controller and get the data from the parent controller.
'use strict';
(function () {
var storeService = function () {
//Getters and Setters to keep the values in session
var headInfo = [];
return {
setData: function (key, data) {
headInfo[key] = data;
},
getData: function (key) {
return headInfo[key];
}
};
};
angular.module('MyApp')
.factory('StoreService', storeService);
})(angular);
Set data from child controller
StoreService.setData('title', $scope.title)
Get data
StoreService.getData('title');
Using events $emit, $on:
You can emit the scope value from the child controller and listen for it in the parent scope.

AngularJS: goto previous url with refresh using $window

I am a newbie of AngularJS using version 1.6.4, What i am trying to do is redirect user on previous page with a complete refresh. Right now i am getting back but page is not refreshing. Any idea how can i do that with a single line code.
user-login.component.js:
(function () {
"use strict";
var module = angular.module(__appName);
function controller(authService, $window, $location, $document) {
var model = this;
model.$onInit = function () {
//TODO:
};
model.login = function () {
authService.login(model.email, model.password).then(function (response) {
//$window.history.back();
//$window.history.go(-1);
//$window.location.href = '/';
console.log("url:"+$document.referrer);
//$document.referrer is showing undefined in console
$location.replace($document.referrer);
},
function (response) {
model.msg = response.error;
});
}
}
module.component("userLogin", {
templateUrl: "components/user-login/user-login.template.html",
bindings: {
email: "<",
password: "<"
},
controllerAs: "model",
controller: ["authService", "$window", "$location", "$document" controller]
});
}());
App.js:
"use strict";
//Global variables
var __apiRoot = "http://localhost:8000/api"; //No slash '/' at the end
var module = angular.module(__appName, [
"ui.router",
"angular-jwt"
]);
module.config(function ($stateProvider, $urlRouterProvider, $httpProvider, jwtOptionsProvider) {
$urlRouterProvider.otherwise('/app/home');
$stateProvider
.state("app", {
abstract: true,
url: "/app",
component: "appRouting"
})
.state("app.home", {
url: "/home",
component: "homeRouting"
})
.state("app.search", {
url: "/search/:q",
component: "searchRouting"
});
jwtOptionsProvider.config({
tokenGetter: ['authService', function (authService) {
return authService.getToken();
}],
whiteListedDomains: ['localhost']
});
$httpProvider.interceptors.push('jwtInterceptor');
});
If you are using angular-ui-router and have name of previous state then use :
$state.go('previousState', {}, { reload: true });
If you don't have the name of the previous state then you could use this piece of code it will run every time state change will occur.
$rootScope.previousState;
$rootScope.currentState;
$rootScope.$on('$stateChangeSuccess', function(ev, to, toParams, from,fromParams) {
$rootScope.previousState = from.name;
$rootScope.currentState = to.name;
console.log('Previous state:'+$rootScope.previousState)
console.log('Current state:'+$rootScope.currentState)
});
you can refresh page by simply using native javascript. window.location.reload()
use $location.replace(). You can get the previous URL of where you came from using $document.referrer. $location.replace($document.referrer)

$stateChangeSuccess, always the same value of $state.params

I can't wrap my head around this, I'm really struggling with ui-routeand $stateChangeSuccess and $watch, got two issues. I'm trying to change a value of a parameter, when I change the state, click a url. I think it's important to know that I use sub-states. With my current setup I get that the $stateChangeSuccess triggers twice on load, meaning it sets the value right away. What I'm trying to do is to only have it set on change of url.
My route looks like this, I'm trying to set the value of a parameter
.state('medications', {
url: '/medications',
templateUrl: '/partials/home.html',
controller: 'mainController',
params: { showSection: false },
resolve: {
postPromise: ['medicationservice', function(medicationservice) {
return medicationservice.getAll();
}]
}
})
.state('medications.add', {
url: '/add',
templateUrl: '/partials/home.html',
controller: 'mainController',
params: { showSection: true },
})
in my controller I got the following, where I set the openSesame parameter to false explicitly on init, but as described it triggers and sets it to true.
mainModule.controller('mainController', [
'$scope',
'$state',
'$rootScope',
'medicationservice',
function($scope, $state, $rootScope, medicationservice) {
$scope.medication = {};
$scope.medications = medicationservice.posts;
$scope.openSesame = false;
$rootScope.$on("$stateChangeSuccess", function() {
$scope.openSesame = true;
console.log($scope.openSesame);
});
}]);
in my plunker the state change works once, that is if I use the $rootScope.
You can actually use $stateChangeSuccess without $injecting $rootScope. You can set a listener on the $scope object instead. Take a look at the following plunker.
And the revised code:
(function() {
var app = angular.module('myApp', ['ui.router']);
app.controller('mainController', [
'$scope',
'$state',
function($scope, $state) {
$scope.medication = {};
$scope.openSesame = false;
$scope.$on('$stateChangeSuccess',function(event, toState){
$scope.openSesame = toState.params.showSection;
});
$scope.triggerMe = function() {
alert('yes');
};
}
]);
app.config([
'$stateProvider',
'$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('medications', {
url: '/medications',
template: '<div>{{openSesame}}</div>',
controller: 'mainController',
params: {
showSection: false
}
})
.state('medications.add', {
url: '/add',
template: '<div>{{openSesame}}</div>',
controller: 'mainController',
params: {
showSection: true
},
});
$urlRouterProvider.otherwise('medications');
}
]);
}());
Click your buttons medication and medication add to see the value change.
The callback of $stateChangeSuccess, you have access toState, fromState. Or check current state name
$rootScope.$on("$stateChangeSuccess", function() {
if ($state.current.name == "medications.add") {
$scope.openSesame = true;
}
console.log($scope.openSesame);
});

How to activate a state and pass object to the new view?

app.js:
$stateProvider.state('datasource', {
url: '/datasource',
templateUrl: 'views/datasource.html',
controller: 'datasourceController'
});
$stateProvider.state('datasourceList', {
url: '/datasource/:datasourceId',
templateUrl: 'views/datasource.list.html',
controller: 'datasourceController'
});
Then, I've a function activated onClick defined in (datasourceController)
$scope.submitSelected = function() {
if (angular.isDefined($scope.selectedSource)) {
$scope.datasource = dataSourcesService.find($scope.selectedSource.id);
$state.go('datasourceList', {datasourceId : datasource});
}
}
As I said, in my datasource.html view have an element click that triggers submitSelected() function
I can retrieve data successfully from my service based on ID when call it in function.
Problem is, i cant access the results (scope) of it in the view datasource.list.html after changing the state.
Add 'param' in state config
$stateProvider.state('datasource', {
url: '/datasource',
templateUrl: 'views/datasource.html',
controller: 'datasourceController'
});
$stateProvider.state('datasourceList', {
url: '/datasource/:datasourceId',
templateUrl: 'views/datasource.list.html',
params: ['index', 'anotherKey'],
controller: 'datasourceController'
});
And get values using $stateParams in another controller
app.controller('datasourceController', function($scope, $stateParams) {
var index = $stateParams.index;
var anotherKey = $stateParams.anotherKey;
});

Set Page title using UI-Router

I am migrating my AngularJS based app to use ui-router instead of the built in routing. I have it configured as shown below
.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/home');
$stateProvider
.state('home', {
url: '/home',
templateUrl : 'views/home.html',
data : { pageTitle: 'Home' }
})
.state('about', {
url: '/about',
templateUrl : 'views/about.html',
data : { pageTitle: 'About' }
})
});
How can I use the pageTitle variable to dynamically set the title of the page? Using the built in routing, I could do
$rootScope.$on("$routeChangeSuccess", function(currentRoute, previousRoute){
$rootScope.pageTitle = $route.current.data.pageTitle;
});
and then bind the variable in HTML as shown below
<title ng-bind="$root.pageTitle"></title>
Is there a similar event that I can hook into using ui-router? I noticed that there are 'onEnter' and 'onExit' functions but they seem to be tied to each state and will require me to repeat code to set the $rootScope variable for each state.
Use $stateChangeSuccess.
You can put it in a directive:
app.directive('updateTitle', ['$rootScope', '$timeout',
function($rootScope, $timeout) {
return {
link: function(scope, element) {
var listener = function(event, toState) {
var title = 'Default Title';
if (toState.data && toState.data.pageTitle) title = toState.data.pageTitle;
$timeout(function() {
element.text(title);
}, 0, false);
};
$rootScope.$on('$stateChangeSuccess', listener);
}
};
}
]);
And:
<title update-title></title>
Demo: http://run.plnkr.co/8tqvzlCw62Tl7t4j/#/home
Code: http://plnkr.co/edit/XO6RyBPURQFPodoFdYgX?p=preview
Even with $stateChangeSuccess the $timeout has been needed for the history to be correct, at least when I've tested myself.
Edit: Nov 24, 2014 - Declarative approach:
app.directive('title', ['$rootScope', '$timeout',
function($rootScope, $timeout) {
return {
link: function() {
var listener = function(event, toState) {
$timeout(function() {
$rootScope.title = (toState.data && toState.data.pageTitle)
? toState.data.pageTitle
: 'Default title';
});
};
$rootScope.$on('$stateChangeSuccess', listener);
}
};
}
]);
And:
<title>{{title}}</title>
Demo: http://run.plnkr.co/d4s3qBikieq8egX7/#/credits
Code: http://plnkr.co/edit/NpzQsxYGofswWQUBGthR?p=preview
There is a another way of doing this by combining most of the answers here already. I know this is already answered but I wanted to show the way I dynamically change page titles with ui-router.
If you take a look at ui-router sample app, they use the Angular .run block to add the $state variable to $rootScope.
// It's very handy to add references to $state and $stateParams to the $rootScope
// so that you can access them from any scope within your applications.
// For example, <li ng-class="{ active: $state.includes('contacts.list') }">
// will set the <li> to active whenever 'contacts.list' or one of its
// decendents is active.
.run([ '$rootScope', '$state', '$stateParams',
function ($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}])
With this defined, you can then easily dynamically update your page title with what you have posted but modified to use the defined state:
Setup the state the same way:
.state('home', {
url: '/home',
templateUrl : 'views/home.html',
data : { pageTitle: 'Home' }
})
But edit the html a bit...
<title ng-bind="$state.current.data.pageTitle"></title>
I can't say this is any better than the answers before, but it was easier for me to understand and implement.
The angular-ui-router-title plugin makes it easy to update the page title to a static or dynamic value based on the current state. It correctly works with browser history, too.
$stateChangeSuccess is now deprecated in UI-Router 1.x and disabled by default. You'll now need to use the new $transition service.
A solution isn't too difficult once you understand how $transition works. I got some help from #troig in understanding it all. Here's what I came up with for updating the title.
Put this in your Angular 1.6 application. Note that I'm using ECMAScript 6 syntax; if you are not, you'll need e.g. to change let to var.
.run(function($transitions, $window) {
$transitions.onSuccess({}, (transition) => {
let title = transition.to().title;
if (title) {
if (title instanceof Function) {
title = title.call(transition.to(), transition.params());
}
$window.document.title = title;
}
});
Then just add a title string to your state:
$stateProvider.state({
name: "foo",
url: "/foo",
template: "<foo-widget layout='row'/>",
title: "Foo Page""
});
That will make the words "Foo Page" show up in the title. (If a state has no title, the page title will not be updated. It would be a simple thing to update the code above to provide a default title if a state does not indicate one.)
The code also allows you to use a function for title. The this used to call the function will be the state itself, and the one argument will be the state parameters, like this example:
$stateProvider.state({
name: "bar",
url: "/bar/{code}",
template: "<bar-widget code='{{code}}' layout='row'/>",
title: function(params) {
return `Bar Code ${params.code}`;
}
});
For the URL path /bar/code/123 that would show "Bar Code 123" as the page title. Note that I'm using ECMAScript 6 syntax to format the string and extract params.code.
It would be nice if someone who had the time would put something like this into a directive and publish it for everyone to use.
Attaching $state to $rootscope to use anywhere in the app.
app.run(['$rootScope', '$state', '$stateParams',
function ($rootScope, $state, $stateParams) {
// It's very handy to add references to $state and $stateParams to the $rootScope
// so that you can access them from any scope within your applications.For example,
// <li ng-class="{ active: $state.includes('contacts.list') }"> will set the <li>
// to active whenever 'contacts.list' or one of its decendents is active.
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}
]
)
<title ng-bind="$state.current.name + ' - ui-router'">about - ui-router</title>
I found this way really easy:
.state('app.staff.client', {
url: '/client/mine',
title: 'My Clients'})
and then in my HTML like this:
<h3>{{ $state.current.title }}</h3>
Just update window.document.title:
.state('login', {
url: '/login',
templateUrl: "/Login",
controller: "loginCtrl",
onEnter: function($window){$window.document.title = "App Login"; }
})
That way 'ng-app' does not need to move up to the HTML tag and can stay on the body or lower.
I'm using ngMeta, which works well for not only setting page title but descriptions as well. It lets you set a specific title/description for each state, defaults for when a title/description is not specified, as well as default title suffixes (i.e., ' | MySiteName') and author value.
$stateProvider
.state('home', {
url: '/',
templateUrl: 'views/home.html',
controller: 'HomeController',
meta: {
'title': 'Home',
'titleSuffix': ' | MySiteName',
'description': 'This is my home page description lorem ipsum.'
},
})
You are actually really close with your first answer/question. Add your title as a data object:
.state('home', {
url: '/home',
templateUrl : 'views/home.html',
data : { pageTitle: 'Home' }
})
In your index.html bind the data directly to the page title:
<title data-ng-bind="$state.current.data.pageTitle + ' - Optional text'">Failsafe text</title>
I ended up with this combination of Martin's and tasseKATT's answers - simple and without any template related stuff:
$rootScope.$on("$stateChangeSuccess", function (event, toState) {
$timeout(function () { // Needed to ensure the title is changed *after* the url so that history entries are correct.
$window.document.title = toState.name;
});
});
Why not just:
$window.document.title = 'Title';
UPDATE: Full Directive Code
var DIRECTIVE = 'yourPageTitle';
yourPageTitle.$inject = ['$window'];
function yourPageTitle($window: ng.IWindowService): ng.IDirective {
return {
link: (scope, element, attrs) => {
attrs.$observe(DIRECTIVE, (value: string) => {
$window.document.title = value;
});
}
}
}
directive(DIRECTIVE, yourPageTitle);
Then in every page you would just include this directive:
<section
your-page-title="{{'somePage' | translate}}">
If you are using ES6, this works just fine :).
class PageTitle {
constructor($compile, $timeout) {
this.restrict = 'A';
this._$compile = $compile;
this.$timeout = $timeout;
}
compile(element) {
return this.link.bind(this);
}
link(scope, element, attrs, controller) {
let defaultTitle = attrs.pageTitle ? attrs.pageTitle : "My Awesome Sauce Site";
let listener = function(event, toState) {
let title = defaultTitle;
if (toState.data && toState.data.title) title = toState.data.title + ' | ' + title;
$('html head title').text(title);
};
scope.$on('$stateChangeStart', listener);
}
}
export function directiveFactory($compile) {
return new PageTitle($compile);
}
directiveFactory.injections = ['$compile', '$timeout'];
export default PageTitle;
Maybe you can try this directive.
https://github.com/afeiship/angular-dynamic-title
Here is the example:
html:
<title dynamic-title>Title</title>
State1 page
State2 page
javascript:
var TestModule = angular.module('TestApp', ['ui.router','nx.widget'])
.config(function ($stateProvider, $urlRouterProvider) {
//
// For any unmatched url, redirect to /state1
$urlRouterProvider.otherwise("/state1");
//
// Now set up the states
$stateProvider
.state('state1', {
url: "/state1",
templateUrl: "partials/state1.html",
data:{
pageTitle:'State1 page title11111'
}
})
.state('state2', {
url: "/state2",
templateUrl: "partials/state2.html",data:{
pageTitle:'State2 page title222222'
}
});
})
.controller('MainCtrl', function ($scope) {
console.log('initial ctrl!');
});
For Updated UI-Router 1.0.0+ versions,
(https://ui-router.github.io/guide/ng1/migrate-to-1_0)
Refer to following code
app.directive('pageTitle', [
'$rootScope',
'$timeout',
'$transitions',
function($rootScope, $timeout,$transitions) {
return {
restrict: 'A',
link: function() {
var listener = function($transitions) {
var default_title = "DEFAULT_TITLE";
$timeout(function() {
$rootScope.page_title = ($transitions.$to().data && $transitions.$to().data.pageTitle)
? default_title + ' - ' + $transitions.$to().data.pageTitle : default_title;
});
};
$transitions.onSuccess({ }, listener);
}
}
}
])
Add following to your index.html:
<title page-title ng-bind="page_title"></title>
if (abp.auth.hasPermission('Center.Category.GroupItem')) {
$stateProvider.state('groupItems', {
title: 'GroupItems',
url: '/groupItems',
templateUrl: '~/App/product/views/center/groupItem/index.cshtml'
controller: 'app.product.views.center.groupItem.index as vm'
});
}
<title>{{$state.current.title ? $state.current.title : 'MiniShop'}}</title>

Resources