I am new to AngularJS and I am using Angular UI-Router for my SPA.
What I am trying to do is to update the Parent View values from the Child View. I have gone through the UI-Router documentation for Nested Views and Multiple Views but couldn't find a way to update the values.
My use case is, Parent view will be the Header and every time a Child View changes via the State Transition I want to update the header value which is part of the Parent View.
Code :
HTML File -
<div ui-view></div>
JS File where Angular UI-Routing configuration happens -
angular.module('myApp', ['ui.router']).config(['$stateProvider', '$routeProvider',
function($stateProvider, $routeProvider) {
$stateProvider
.state('main', {
resolve: {
resA: function() {
return {
'value': 'Hello !!'
};
}
},
controller: function($scope, resA) {
$scope.resA = resA.value;
},
abstract: true,
url: '/main',
template: '<h1>{{resA}}</h1>' +
'<div ui-view></div>'
})
.state('main.one', {
url: '/one',
views: {
'#main': {
template: "Im View One"
}
},
resolve: {
resB: function(resA) {
return {
'value': resA.value + ' from One'
};
}
},
controller: function($scope, resA, resB) {
$scope.resA = resB.value;
}
}).state('main.two', {
url: '/two',
views: {
'#main': {
template: '<div ui-view="sub1"></div>' +
'<div ui-view="sub2"></div>'
},
'sub1#main.two': {
template: "Am awesome"
},
'sub2#main.two': {
template: "Am awesome two/too"
}
},
resolve: {
resC: function(resA) {
return {
'value': resA.value + ' from Two'
};
}
},
controller: function($scope, resA, resC) {
$scope.resA = resC.value;
}
});
}]).run(['$rootScope', '$state', '$stateParams', function($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
$state.transitionTo('main.two');
}]);
Here is the JSFiddle Link of the same code snippet.
There are muliple ways you can update the parent scope.
Using controllerAs
https://jsfiddle.net/9n7wrevt/17/
controller: function($scope, resA) {
this.resA = resA.value;
},
controllerAs: 'main'
referring parent as below
controller: function($scope, resB) {
$scope.main.resA = resB.value;
}
Using $parent
https://jsfiddle.net/9n7wrevt/18/
controller: function($scope, resB) {
$scope.$parent.resA = resB.value;
}
Better way(highly recommended) is to use $scope $emit, $on to communicate between controllers.
https://jsfiddle.net/9n7wrevt/19/
Related
https://plnkr.co/edit/PWuKuVw9Dn9UJy6l8hZv?p=preview
I have 3 modules, routerApp, tickers and tags. Basically trying to rebuild my app out of smaller mini-apps.
The routerApp template contains the 2 directives for the other modules.
Expected
When you login, then click on the Count button in the tickers.component, I want to send the counter var into the tags.component $scope via $state.go.
Result
Nothing happens. No $state/variable update in the tags.component
Plnkr app.js code:
// TICKERS app module
var tickers = angular.module('tickers', ['ui.router'])
tickers.config(function($stateProvider) {
const tickers = {
name: 'tickers',
url: '/tickers',
parent: 'dashboard',
templateUrl: 'tickers-list.html',
bindToController: true,
controllerAs: 'tik',
controller: function() {
}
}
$stateProvider
.state(tickers);
})
tickers.component('tickersModule', {
templateUrl: 'tickers-list.html',
controller: function($scope, $state) {
console.log('Tickers init')
$scope.counter = 0;
$scope.increase = function() {
$scope.counter++;
console.log('increase', $scope.counter)
$state.go('dashboard.tags', { counter: $scope.counter });
}
}
})
// TAGS app module
var tags = angular.module('tags', ['ui.router'])
tags.config(function($stateProvider) {
const tags = {
name: 'tags',
url: '/tags?counter',
parent: 'dashboard',
params: {
counter: 0
},
templateUrl: 'tags-list.html',
bindToController: true,
controllerAs: 'tags',
controller: function($state) {
}
}
$stateProvider
.state(tags);
})
tags.component('tagsModule', {
templateUrl: 'tags-list.html',
controller: function($scope, $state) {
// Expecting this to update:
console.log('Tags init', $state)
$scope.counter = $state.params.counter;
}
})
// MAIN ROUTERAPP module
var routerApp = angular.module('routerApp', ['ui.router', 'tickers', 'tags']);
routerApp.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/login');
const login = {
name: 'login',
url: '/login',
templateUrl: 'login.html',
bindToController: true,
controllerAs: 'l',
controller: function($state) {
this.login = function() {
$state.go('dashboard', {})
}
}
}
const dashboard = {
name: 'dashboard',
url: '/dashboard',
templateUrl: 'dashboard.html',
controller: function() {
}
}
$stateProvider
.state(login)
.state(dashboard);
})
dashboard.html
<div class="jumbotron text-center">
<h1>The Dashboard</h1>
</div>
<div class="row">
<tickers-module></tickers-module>
<tags-module></tags-module>
</div>
The function in the tickers component that is trying to update the $state of the tags component:
$scope.increase = function() {
$scope.counter++;
console.log('increase', $scope.counter)
$state.go('dashboard.tags', { counter: $scope.counter });
}
Also tried: $state.go('tags', { counter: $scope.counter });
Finally the tags.component.
Note that here I'm not seeing the $scope.counter update nor the controller for the component getting refreshed due to a state change.
tags.component('tagsModule', {
templateUrl: 'tags-list.html',
controller: function($scope, $state) {
console.log('Tags init', $state)
$scope.counter = $state.params.counter;
}
})
Is the way I am architecting this going to work? What am I missing?
Update: Added some $rootScope events to watch for $state changes, hope this helps:
This is after clicking the login button and going from the login state to the dashboard state, but still nothing for clicking on the Count button.
So, it looks like you're properly passing the parameters in your $state.go call.
I think the issue here is that you're not properly handling the state parameters you're passing from the tickers component to the tags component.
Try injecting $stateParams into your tags component and pull the parameters off that object, for example (in your tags controller):
$scope.counter = $stateParams.counter;
Figured it out!
https://plnkr.co/edit/CvJLXKYh8Yf5npNa2mUh?p=preview
My problem was that in the $state object tags, I was using the same template as the tags.component.
Instead I needed to change it to something like <p>{{ counter }}</p>
var tags = angular.module('tags', ['ui.router'])
tags.config(function($stateProvider) {
const tags = {
name: 'tags',
url: '/tags',
parent: 'dashboard',
params: {
counter: 0
},
template: '<p>{{ counter }}</p>',
bindToController: true,
controller: function($scope, $state) {
console.log('tags state object', $state)
$scope.counter = $state.params.counter;
}
}
$stateProvider
.state(tags);
})
tags.component('tagsModule', {
templateUrl: 'tags-module-template.html',
controller: function($scope, $state) {
}
})
Then in my tags-module.template.html I needed to add a <div ui-view></div>
<div class="jumbotron text-center">
<h2>Tags list</h2>
<div ui-view></div> // <- here and this is where the $state object updates
{{ counter }}
</div>
Here is my :
config.router.js
app.config(['$stateProvider', '$urlRouterProvider', '$controllerProvider', '$compileProvider', '$filterProvider', '$provide', '$ocLazyLoadProvider', 'JS_REQUIRES',
function ($stateProvider, $urlRouterProvider, $controllerProvider, $compileProvider, $filterProvider, $provide, $ocLazyLoadProvider, jsRequires) {
app.controller = $controllerProvider.register;
app.directive = $compileProvider.directive;
app.filter = $filterProvider.register;
app.factory = $provide.factory;
app.service = $provide.service;
app.constant = $provide.constant;
app.value = $provide.value;
// LAZY MODULES
$ocLazyLoadProvider.config({
debug: false,
events: true,
modules: jsRequires.modules
});
// APPLICATION ROUTES
// -----------------------------------
$urlRouterProvider.otherwise('/login/signin');
//
// Set up the states
$stateProvider.state('app', {
url: "/app",
templateUrl: "assets/views/app.html",
resolve: loadSequence('modernizr', 'moment', 'angularMoment', 'uiSwitch', 'perfect-scrollbar-plugin', 'toaster', 'ngAside', 'vAccordion', 'sweet-alert', 'chartjs', 'tc.chartjs', 'oitozero.ngSweetAlert', 'chatCtrl'),
abstract: true
}).state('app.dashboard', {
url: "/dashboard",
templateUrl: "assets/views/dashboard.html",
resolve: loadSequence('jquery-sparkline', 'dashboardCtrl'),
title: 'Dashboard',
ncyBreadcrumb: {
label: 'Dashboard'
}
})
...
loginCtrl.js
app.controller('LoginCtrl', ["$scope", "alert", "auth", "$state", "$auth", "$timeout", function ($scope, alert, auth, $state, $auth, $timeout) {
$scope.submit = function () {
$auth.login({
email: $scope.email,
password: $scope.password
})
.then(function(res) {
var message = 'Thanks for coming back ' + res.data.user.email + '!';
if (!res.data.user.active)
{$auth.logout();
message = 'Just a reminder, please activate your account soon :)';}
alert('success', 'Welcome', message);
return null;
})
.then(function() {
$timeout(function() {
$state.go('main');
});
})
.catch(handleError);
} // submit function for login view
function handleError(err) {
alert('warning', 'oops there is a problem!', err.message);
}
}]);
main.js
var app = angular.module('myApp', ['my-app']);
app.run(['$rootScope', '$state', '$stateParams',
function ($rootScope, $state, $stateParams) {
// Attach Fastclick for eliminating the 300ms delay between a physical tap and the firing of a click event on mobile browsers
FastClick.attach(document.body);
// Set some reference to access them from any scope
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
// GLOBAL APP SCOPE
// set below basic information
$rootScope.app = {
name: 'My App',
author: 'example author',
description: 'My Platform',
version: '1.0',
year: ((new Date()).getFullYear()),
isMobile: (function () {
var check = false;
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
check = true;
};
return check;
})()
};
$rootScope.user = {
name: 'Peter',
job: 'ng-Dev'
};
}]);
the problem is when I add ng-controller="loginCtrl" to my login div on the html file for login, it works. But I have another div just below the login div:
<div class="copyright" >
{{app.year}} © {{ app.name }} by {{ app.author }}.
</div>
This doesn't work! however, I have a similar one above the login div, it works:
<div class="logo">
<img ng-src="{{app.layout.logo}}" alt="{{app.name}}"/>
</div>
where is the problem? How to address it?
thanks
if you are using angular ui-router, it's not neccessary to add ng-controller="loginCtrl" to your DIVs manually, instead add controller property in your $stateProvider.state
example:
.state('app.dashboard', {
url: "/dashboard",
templateUrl: "assets/views/dashboard.html",
title: 'Dashboard',
controller: 'dashboardCtrl',
resolve: {
deps: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load('path/to/your/controller.js');
}]
},
ncyBreadcrumb: {
label: 'Dashboard'
}
})
when you change your state usually views change to, that's the point right? setting controllers on the fly might not work as you expect.
checkout documentation
i have an angularjs app with components and i want to add a view as child of another view using ui.router. i honestly dont think that ui.router fully support component, this may happen in upcomping 1.0 version. But in the meanwhile is anything that i can do to use display view dynamically?
this is my $stateProvider (i've done my attempts)
$stateProvider
.state('home', {
url :'/home',
template :'<home></home>'
})
.state('projects', {
url:'/projects',
template: "<project-list></project-list>"
})
.state('projectDetails',{
url:'/:projectId',
template : '<project-detail projectDetails="projectDetails"></project-detail>'
, views :{
'chat':{
template : '<chat></chat>'
}
}
});
$urlRouterProvider.otherwise('home');
i have created a component in this way:
(function() {
'use strict';
function ChatController($scope, $element, $attrs, $firebaseArray) {
var ctrl = this;
console.log("it works");
}
angular.module('myapp').component('chat', {
templateUrl: '/app/component/project/chat.html',
controller: ChatController
});
})(window.angular);
and then i've added the view in projectDetails
<div ui-view="chat"></div>
My understanding of your requirement is that you have a component called 'Chat' which you want to load in the state 'project-details' as a nested/child view.
As you have already observed ui-router does not support this.
But this can be achieved by making your 'project-details' as a parent state and load other components included as a child state.
Plunker Demo
angular.module('app', [
'ui.router'
]).config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider.state('home', {
template: '<home></home>'
}).state('home.parts', {
url: '/',
views: {
'about-view#home': {
template: "<about></about>"
},
'chat-view#home': {
template: "<chat></chat>"
}
}
});
}).component('app', {
templateUrl: 'app.html'
}).component('home', {
templateUrl: 'home.html',
controller: homeController,
controllerAs: 'vm'
}).component('about', {
templateUrl: 'about.html',
controller: aboutController,
controllerAs: 'vm'
}).component('chat', {
templateUrl: 'chat.html',
controller: chatController,
controllerAs: 'vm'
});
function chatController() {
this.name = "chat";
}
function aboutController() {
this.name = "about";
}
function homeController() {
this.name = "home";
}
Hope this is what you wanted.Please do not add url to the parent state.Consider making it a abstract state.
Trying to use a reusable controller for a generic template
html
<div class="row col-lg-10 pull-right">
<div ng-init="catId = 1" ui-view="firstQuadrant"></div>
<div ng-init="catId = 2" ui-view="secondQuadrant"></div>
</div>
<div class="row col-lg-10 pull-right">
<div ng-init="catId = 3" ui-view="thirdQuadrant"></div>
<div ng-init="catId = 4" ui-view="fourthQuadrant"></div>
</div>
code snippet from my views object in $stateProvider:
views : {
'firstQuadrant#home': {
templateUrl: "../partials/quadrant/quadrant.html",
controller: "QuadrantCtrl"
},
'secondQuadrant#home': {
templateUrl: "../partials/quadrant/quadrant.html",
controller: "QuadrantCtrl"
},
'thirdQuadrant#home': {
templateUrl: "../partials/quadrant/quadrant.html",
controller: "QuadrantCtrl"
},
'fourthQuadrant#home': {
templateUrl: "../partials/quadrant/quadrant.html",
controller: "QuadrantCtrl"
}
}
controller code
.controller('QuadrantCtrl', ['$scope', '$rootScope', 'categories', 'utils',
function ($scope, $rootScope, categories, utils) {
$scope.$watch('catId', function () {
console($scope.catId);
$scope.categories = categories;
$scope.name = "It works! weee";
$scope.score = 99;
$scope.items = utils.findById($scope.categories, $scope.catId);
});
}]);
It only seems to use the last controller being instantiated (catId = 4)
how can I have 4 isolated scopes? Do I have to use directives instead?
Your scenario should work (not sure if this is good design). There is a working plunker
But we have to move the switch catId from ng-init into state defintion. Into resolve
If the states are defined like this:
// home
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'tpl.layout.html',
controller : "rootController",
})
the child state with multi-views
.state('child', {
parent: "home",
url: '/child',
templateUrl: 'tpl.example.html',
views : {
'firstQuadrant#home': {
templateUrl: "tpl.quadrant.html",
controller: "QuadrantCtrl",
resolve: { catId : function(){ return 1 } },
},
'secondQuadrant#home': {
templateUrl: "tpl.quadrant.html",
controller: "QuadrantCtrl",
resolve: { catId : function(){ return 2 } },
},
'thirdQuadrant#home': {
templateUrl: "tpl.quadrant.html",
controller: "QuadrantCtrl",
resolve: { catId : function(){ return 3 } },
},
'fourthQuadrant#home': {
templateUrl: "tpl.quadrant.html",
controller: "QuadrantCtrl",
resolve: { catId : function(){ return 4 } },
}
}
})
And simplified controller creates random number in the scope
.controller('QuadrantCtrl', ['$scope', '$rootScope', 'catId'
, function ($scope, $rootScope, catId) {
$scope.catId = catId;
console.log($scope.catId);
$scope.random = Math.random() * 100;
}])
Each view is then independent with its own instance of controller and $scope
Check it here
Then we can see results like this
quadrant
random number in the scope: 32.40865177940577
catId: 1
quadrant
random number in the scope: 17.18798188958317
catId: 2
quadrant
random number in the scope: 76.22438217513263
catId: 3
quadrant
random number in the scope: 41.46456739399582
catId: 4
if the quadrant template is:
<h4>quadrant</h4>
<p>random number in the scope: {{random}}</p>
<p>catId: {{catId}}</p>
All that is strictly following the documentation:
Multiple Named Views
The working example with above stuff
I'm learning angular (with ionic framework, includes phone gap)and I am trying to create a single app that lists some data and when you click on one it show details for it. I was able to list all the item but I can't manage how to show details for a single one. I know the issue probably has to do with $routeParams but I can't find what is wrong.
app.js
angular.module('app', ['ionic', 'app.controllers'])
.config(function($stateProvider, $urlRouterProvider, $routeProvider) {
.state('app.list', {
url: '/list',
views: {
'menuContent' :{
templateUrl: "templates/list.html"
}
}
})
.state('app.staff', {
url: '/staff/:staffid',
views: {
'menuContent' :{
templateUrl: "templates/staff-details.html",
controller: 'StaffDetailsCtrl'
},
}
})
});
controllers.js
.controller('FooCtrl', function($scope) {
$scope.staff = [
{ id: 1, name: 'Jim', color: 'red' },
{ id: 2, name: 'Bob', color: 'blue' },
{ id: 3, name: 'Peter', color: 'yellow' }
/*... etc... */
];
});
// Review Controller
.controller('StaffDetailsCtrl', function($scope, $routeParams) {
// Get staff
alert('yeeeees');
var id = $routeParams.staffid;
$scope.staff = $scope.staff.get(id);
});
staff-details.html
{{staff.id}}<br />
{{staff.name}}<br />
{{staff.color}}
UPDATE
Link to Plunker.
If you're using the angular-ui-router then your should be injecting $stateParams rather than $routeParams. So you code will look like:
angular.module('app', ['ionic', 'app.controllers'])
.config(function($stateProvider, $urlRouterProvider, $routeProvider) {
$stateProvider
.state('app.staff', {
url: '/staff/:staffid',
views: {
'menuContent' :{
templateUrl: "templates/staff-details.html",
controller: 'StaffDetailsCtrl'
},
}
})
});
// Review Controller
.controller('StaffDetailsCtrl', function($scope, $stateParams) {
// Get staff
alert('yeeeees');
var id = $stateParams.staffid;
$scope.staff = $scope.staff.get(id);
});