Passing an object using $state.go in AngularJS - angularjs

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;
}
})

Related

AngularJS router - parent controller loads when child is clicked

I am having strange results working with AngualarJS states. Here is app code:
/* myApp module */
var myApp = angular.module('myApp', ['ui.router'])
.config(function ($stateProvider) {
$stateProvider.state('home', {
url: "home",
template: '<div ui-view><h3>Home</h3><a ui-sref="home.child({reportID:1})">Child</a></div>',
params: { reportID: null },
controller: function ($scope) {
$scope.homeCtrlVar = "home";
console.log("Home controller loaded");
}
}).state('home.child', {
template: '<div><h3>Child</h3><a ui-sref="home">Back</a></div>',
controller: function ($scope) {
$scope.childCtrlVar = "child";
console.log("Child controller loaded");
}
});
})
.controller('MainCtrl', function ($scope, $state) {
console.log("MainCtrl initialized!");
$state.go("home");
});
And main page:
<div ng-app="myApp" ng-controller="MainCtrl">
<h2>My app</h2>
<div ui-view></div>
What's happening is that as long as there parameters for the home state and reportID value doesn't match between a parameter being sent and the state default the home controller is loaded when I click on Child. Can someone please explain why that's happening?
Fiddle
Here is updated code which works as you expect it to:
var myApp = angular.module('myApp', ['ui.router'])
.config(function ($stateProvider) {
$stateProvider.state('home', {
url: "home",
template: '<div ui-view><h3>Home</h3><a ui-sref="home.child({reportID:1})">Child</a></div>',
controller: function ($scope) {
$scope.homeCtrlVar = "home";
console.log("Home controller loaded");
}
}).state('home.child', {
url: "/:reportID",
params: { reportID: null },
template: '<div><h3>Child</h3><a ui-sref="home">Back</a></div>',
controller: function ($scope) {
$scope.childCtrlVar = "child";
console.log("Child controller loaded");
}
});
})
Problem with your approach:
specifying params reportID in home state instead of home.child state.
When user clicks on home.child({ reportId: 1}) it should load home.child, which is fine, and was working with old approach.
However, If you take notice, as you click on home.child({ reportId: 1}), you are sending new parameter reportID(old value was null). reportID belongs to home state, hence its controller is also loaded.
Note that url: "/:reportID" in state home.child is optional.

angularJS $stateParams fail to pass a value to a controller

I have an state declared like this in my main App:
var app = angular.module("contactManagement",
["'ui.router'])
.config(["$stateProvider", "$urlRouterProvider", "$locationProvider",
function ($stateProvider, $urlRouterProvider, $locationProvider) {
$stateProvider.state('contacts.detail', {
url: "/contacts/:contactId",
templateUrl: 'contacts.detail.html',
controller: contactEditCtrl
}
...
and I am trying to pass the parameter "contactId" from the URL to the Controller in another file called contactEditCtrl.js like this:
.module("contactManagement ")
.controller("ContactEditCtrl", ["contactResource", ContactEditCtrl]);
function ContactEditCtrl(contactResource, $stateParams) {
var vm = this;
var contactId = $stateParams.contactId;
vm.title = '';
vm.message = '';
contactResource.get({ id: **contactId** },
...more stuff...
But the contactId does not reach the controller using the $stateParams as it said the documentation.
All I need is to inject that ContactId in the controller, but I do not get it!
Any idea on what I am missing here?
You forgot "params"
.state('contacts.detail', {
url: "/contacts/:contactId",
params:{contactId: null},
..more code...
})

How to pass custom data in $state.go() in angular-ui-router?

I want to pass a custom object to another state via $state.go() in UI-Router.
var obj = {
a: 1,
b: 2,
fun: function() {
console.log('fun');
}
}
$state.go('users', obj);
But I need to run fun() in target state, so I can't pass this object in URL parameter. In target controller, I tried to fetch the value of obj via $stateParams but got empty object {}:
function UserCtrl($stateParams) {
console.log($stateParams); // will be empty
}
So how to pass obj to state "users" correctly?
Define state with parameters like this:
$stateProvider
.state('user', {
url: '/user',
params: {
obj: null
},
templateUrl: 'templates/user.html',
controller: 'UserCont'
})
when calling pass parameter like this:
$state.go('user',{obj: myobj});
in the controller UserCon receive parameter like:
$state.params.obj
User $state is one of the parameter in the controller defined like
function UserCon($scope, $http, $state){
var obj = {
a: 1,
b: 2,
fun: function() {
console.log('fun');
}
}
$state.go('users', obj);
.....
$stateProvider
.state('user', {
url: '/user/:obj',
templateUrl: 'templates/user.html',
controller: 'UserCont'
})
..... or .....
$stateProvider
.state('user', {
url: '/user',
params: {
obj: null
},
templateUrl: 'templates/user.html',
controller: 'UserCont'
})
.....
function UserCtrl($state) {
console.log($state.params.obj);
}
//Use the method chaining mechnism as show below:
var RoutingApp = angular.module('RoutingApp', ['ui.router']);
RoutingApp.config( [ '$stateProvider', '$urlRouterProvider','$locationProvider', function( $stateProvider, $urlRouterProvider,$locationProvider ){
$stateProvider
.state('home', {
url:'/home',
templateUrl: 'template/home.html',
controller: 'homeController as homeCtrl',
data: {
customDatahome1:"Home Custom Data 1",
customDatahome2:"Home Custom Data 2"
}
})
.state('courses', {
url:'/courses',
templateUrl: 'template/courses.html',
controller: 'coursesController as coursesCtrl',
data: {
customDatacourses1:"Courses Custom Data 1",
customDatacourses2:"Courses Custom Data 2"
}
})
.state('students', {
url:'/students',
templateUrl: 'template/students.html',
controller: 'studentsController'
})
.state('studentDetails', {
url:'/students/:id',
templateUrl: 'template/studentDetails.html',
controller: 'studentDetailsController'
});
$urlRouterProvider.otherwise('/home');
$locationProvider.html5Mode(true);
} ] );
RoutingApp.controller( 'homeController', function( $state ){
var vm = this;
vm.message = "Home Page";
this.customDatahome1 = $state.current.data.customDatahome1;
this.customDatahome2 = $state.current.data.customDatahome2;
this.customDatacourses1 = $state.get('courses').data.customDatacourses1;
this.customDatacourses2 = $state.get('courses').data.customDatacourses2;
});
RoutingApp.controller('coursesController', function($scope){
var vm = this;
$scope.courses = ['PHP','AngularJS','Hadoop','Java','.Net','C#'];
console.log("Hello Courses");
});
Template:home.html
<h1 ng-controller="homeController as homeCtrl">{{homeCtrl.message}} <br> {{homeCtrl.message1}}</h1>
<div>
Vision Academy is established in 2011 By 2 Software Engineer offer very cost effective training.
</div>
<ul>
<li>Training Delivered by real time software experts having more than 10 years of experience </li>
<li>Realtime Project discussion relating to the possible interview questions</li>
<li>Trainees can attend training and use lab untill you get job</li>
<li>Resume prepration and mock up interview</li>
<li>100% placement asistance</li>
<li>Lab facility</li>
</ul>
<fieldset ng-controller="homeController as homeCtrl">
<legend>Home</legend>
Custom Data Home 1 : {{homeCtrl.customDatahome1}}<br>
Custom Data Home 2 : {{homeCtrl.customDatahome2}}<br>
</fieldset>
<fieldset ng-controller="homeController as homeCtrl">
<legend>Courses</legend>
Custom Data Courses 1 : {{homeCtrl.customDatacourses1}}<br>
Custom Data Courses 2 : {{homeCtrl.customDatacourses2}}<br>
</fieldset>

How to display single alert from multiple controllers with UI Bootstrap Alert

I am using UI Router and UI Bootstrap in my Angular app. I'd like to use a service so that I can display alert messages from various controllers. I want the alert to display at the top of the screen. Any suggestions on how to modify the code below so that the alert will display at the top of the page and display messages from different controllers?
I'm using this Stack Overflow post as a model.
HTML:
<alert ng-repeat="alert in allInfos()" type="{{alert.type}}" close="closeAlert($index)"
ng-cloak>{{alert.msg}}</alert>
Service:
.factory('Informer', function(){
var messages = [];
var Informer = {};
Informer.inform = function(msg, type) {
messages.push({
msg: msg,
type: type
});
};
Informer.allInfos = function() {
return messages;
};
Informer.remove = function(info) {
messages.splice(messages.indexOf(info), 1);
};
return Informer;
})
Controller:
.controller('PaymentFormCtrl',
function ($scope, $http, Informer) {
$scope.handleStripe = function () {
Informer.inform("There was a problem authorizing your card.", "danger");
$scope.messages = 'problem';
$scope.allInfos = Informer.allInfos;
$scope.remove = Informer.remove;
}
};
});
.controller('ContactFormCtrl',
function ($scope, $http, Informer) {
//. . .
Informer.inform("There is already an account with that email address", "danger");
$scope.messages = 'problem';
$scope.allInfos = Informer.allInfos;
$scope.remove = Informer.remove;
}
};
});
Routers:
.state('home', {
url: '/',
views: {
'top': {
templateUrl: 'views/bigHero.html'
},
'bottom': {
templateUrl: 'views/home.html',
controller: 'HomeCtrl'
}
}
})
.state('payment', {
url: '/payment',
views: {
'top': {
templateUrl: 'views/customerinfo.html',
controller: 'ContactFormCtrl'
},
'bottom': {
templateUrl: 'views/creditcard.html',
controller: 'PaymentFormCtrl'
},
}
});
});
You really have three good options that I can think of off the top of my head.
Create a global or what i like to call a "RootController" of your application bound higher up in your DOM so that the other controllers scope naturally extends it. i.e.:
<div ng-controller="RootController">
<div ui-view></div>
</div>
You can create a parent state with UI Router that both your child states inherit, giving a similar effect to the case above:
$stateProvider.state('parent', {controller: 'ParentController'});
$stateProvider.state('parent.child1', {controller: 'Child1Controller'});
$stateProvider.state('parent.child2', {controller: 'Child2Controller'});
You can pass all shared functionality through a service, which acts as an error message to your necessary controllers.
myService.service('errorService', function() {
this.errorMessage = 'Everything is happy!';
});
myService.controller('PaymentFormCtrl', function($scope, errorService) {
$scope.errorService = errorService;
$scope.setError = function() {
errorService.errorMessage = 'An error happened!';
};
});

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