I'm new to AngularJS and I'm developing a small contacts app using AngularJS and Material Design.
I'd like to pass an object (using ng-click) containing several fields to a different state using $state.go.
My HTML looks like this:
<md-list-item class="md-2-line" ng-repeat="c in contacts" >
<img src="../img/user.svg" class="md-avatar"/>
<div class="md-list-item-text" ng-click="goToContactInfo(c)" >
My JS code:
app.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
// configure "contacts" page
.state('contacts', {
url: "/contacts",
templateUrl: "contacts.html",
controller: function ($scope, $state) {
$scope.contacts = [
{ name: "aaa", phone: "555555" },
{ name : "bbb" , phone: "666666"}
];
$scope.goToContactInfo = function (contact) {
$state.go("contactInfo", contact);
};
}
})
// configure "contactInfo" page
.state('contactInfo', {
url: "/contactInfo",
templateUrl: "contactInfo.html",
controller: function ($scope, $stateParams) {
var contactInfo = $stateParams.contact;
$scope.name = contactInfo.name;
$scope.phone = contactInfo.phone;
}
})
I expect var contactInfo to be an object containing name and phone but I'm getting an undefined instead.
Your controller should be,
controller: function ($scope, $state) {
var contactInfo = $state.params.contact;
}
EDIT
First of all you cannot pass an Object to another state like this, and there are few mistkaes in your configuration.
You need to define your router config with the parameter. Also consider using an id to pass to another state and retrieve the details using an API call or use localstorage.
Here is the fix, i have considered phone number as the state param here,
.state('contactInfo', {
url: "contactInfo/:phone",
templateUrl: "contactInfo.html",
controller: function ($scope, $state, $stateParams) {
console.log('state2 params:', $stateParams);
var contactInfo = $stateParams.phone;
console.log(contactInfo);
alert(JSON.stringify(contactInfo));
$scope.name = contactInfo.name;
$scope.phone = contactInfo.phone;
}
})
and you need to pass the info as,
$state.go("contactInfo", { phone : contact.phone });
PLUNKER DEMO
$scope.goToContactInfo = function (contact) {
$state.go("contactInfo", {contact}); <-- this
};
.state('contactInfo', {
url: "/contactInfo",
templateUrl: "contactInfo.html",
params:{ <-- this
contact: null
},
controller: function ($scope, $state) {
var contactInfo = $state.params.contact;
alert(contactInfo);
$scope.name = contactInfo.name;
$scope.phone = contactInfo.phone;
}
})
Here are my states:
var states = angular.module("states", []);
states.config(function($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise("/landingPage");
$stateProvider
.state("landingPage", {
url: "/landingPage",
templateUrl: "../html/landing-page.html",
controller: "landingCtrl"
})
.state("units",{
url: "/units",
templateUrl: "../html/units.html",
controller: "unitsCtrl",
})
});
Here are my controllers:
var controllers = angular.module("controllers", []);
controllers.controller("landingCtrl", function($scope, $cordovaDialogs,$state) {
$scope.getStarted = function () {
$state.go("units");
};
});
controllers.controller("unitsCtrl", function($scope){
$scope.createUnits = function(){
console.log("Hello");
}();
});
here is the associated html bit:
<button class="button button-assertive btn newbtn" ng-click="getStarted()">Let's Get Started</button>
Basically, the console logs "hello", specifically 4 times, when i get transferred to "units" state. Can anybody steer me into some general directions as why this happens? I haven't the foggiest.
Attempting to navigate to a child route of a parent route with a parameter. The ng-click does go off and I can see it in the console registering the click, but I get no changes to the state. Am I declaring the ui-sref wrong?
<body ng-controller="myController as $ctrl" style="{margin-top: 70px;}">
<div>
<button><a ui-sref="idLevel.index({something: 1})" ng-click="$ctrl.test()">Link1</a></button>
<button><a ui-sref="idLevel.other({something: 2})" ng-click="$ctrl.test2()">link2</a></button>
</div>
<ui-view>
<div>
test
</div>
</ui-view>
// Code goes here
function appRoute($stateProvider, $locationProvider) {
$locationProvider.html5Mode(false);
$stateProvider
.state('idLevel', {
url: '/:something',
abstract: true,
})
.state('idLevel.index', {
url: '/index/',
component: 'component1'
})
.state('idLevel.other', {
url: '/other/',
component: 'component2'
})
}
var component1 = {
template: '<h1>indexRoute</h1><br><p>HELP!!{{$ctrl.params}}</p>',
controller: function($stateParams) {
var ctrl = this;
console.log('component1', $stateParams);
ctrl.params = $stateParams.id;
}
};
var component2 = {
template: '<div><h1>otherRoute</h1><br><p>Help2 {{$ctrl.params}}</div>',
controller: function($stateParams) {
var ctrl = this;
console.log('component2', $stateParams);
ctrl.params = $stateParams.id;
}
};
function myController($stateParams) {
var ctrl = this;
ctrl.test = test;
ctrl.test2 = test2;
function test() {
console.log('TEST LINK!', $stateParams.id)
}
function test2() {
console.log('TEST 2', $stateParams.id)
}
}
angular.module('app', ['ui.bootstrap', 'ui.router'])
.config(appRoute)
.controller('myController', myController)
.component('component1', component1)
.component('component2', component2);
myController.$inject = ['$stateParams'];
appRoute.$inject = ['$stateProvider', '$locationProvider'];
component1.$inject = ['$stateParams'];
component2.$inject = ['$stateParams'];
Plnkr here
I feel like I'm missing something simple here, but I can't figure out why its not allowing me to hit the route.
A parent state needs to have a <ui-view> in it's template for child states to render in.
Note that in plunker you can't use / as base since the editor is not in the root of site
Updated plunker
firstly your plunkr looks a bit different to the code you've shown, secondly, from what I can see, the plunkr seems to be working - you are getting a state change - you cant see it because the content is hidden behind the title nav bar.
If you open the plunkr in the separate window (blue button) you can just see the content under the navbar changing as you click the links.
I've a very strange problem, it apparently seems that simple views template are preventing a controller to be executed, I can't understand why.
I've built a simple plunker
Code is here:
angular.module('plunker', ["ui.router"])
.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise("/nested");
$stateProvider
.state('nested', {
url: '/nested',
controller: function($scope) {
$scope.sayHi = 'Hi';
this.sayHello = 'Hello';
},
controllerAs: 'dog',
//If I comment the "views" section, controller runs correctly
views: {
'main': {
template: 'MainContent'
},
'secondary': {
template: 'SecondaryContent'
}
}
})
$locationProvider.html5Mode(false).hashPrefix('!');
})
html:
<div ui-view>
<h1>{{dog.sayHello}}</h1>
<h1>{{sayHi}}</h1>
<p>Why no "hello" or "Hi" over here?</p>
<div ui-view="main"></div>
<div ui-view="secondary"></div>
</div>
If I comment out the "views" section in the state definition, controller runs correctly.
[edit]
Thanks to Radim, I resolved moving the controller definition in the views section:
views:{
'':{
controller: function ($scope) {
this.page = 'ten';
},
controllerAs:'dog'
},
'main#nested':{
template:'MainContent'
},
'secondary#nested':{
template:'SecondaryContent'
}
}
Check:
UI-Router Multiple Views Single Controller not work
The controller always belongs to view, not to state.
There is an updated plunker
.state('nested', {
url: '/nested',
//controller: function($scope) {
// $scope.sayHi = 'Hi';
// this.sayHello = 'Hello';
//},
//controllerAs: 'dog',
//If I comment the "views" section, controller runs correctly
views: {
'main': {
//template: 'MainContent', - let's use view, to consume dog.sayHello
templateUrl: 'view2.html',
controller: function($scope) {
$scope.sayHi = 'Hi';
this.sayHello = 'Hello';
},
controllerAs: 'dog',
},
'secondary': {
template: 'SecondaryContent',
controller: ...
}
}
check the updated plunekr
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>