So, I'm making my first steps in AngularJS (1.5) and I'm trying to build a feature that will let me change few things in my layout based on the route.
As far as I understood I needed a service for this. Basically the setup I have is:
'use strict';
/* App Module */
var app = angular.module('app', [
'ngRoute',
'appControllers',
'AppServices'
]);
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
template: '<h1>Home page</h1>',
controller: 'MainController'
}).when('/page', {
template: '<h1>Page</h1>',
controller: 'PagesController'
}).otherwise({
redirectTo: '/'
});
}]);
var appControllers = angular.module('appControllers', []);
appControllers.controller('MainController', ['$rootScope', 'AppSetup', function($scope, AppSetup) {
$scope.app = AppSetup.build();
console.log('home');
}]);
appControllers.controller('PagesController', ['$rootScope', 'AppSetup', function($scope, AppSetup) {
AppSetup.setProperties({
meta: {
title: 'My Second Page'
}
});
console.log('page');
$scope.app = AppSetup.build();
}]);
var AppServices = angular.module('AppServices', []);
AppServices.service('AppSetup', [function() {
var properties = {
meta: {
title: 'My App • Best of the best'
}
},
styles;
this.setProperties = function(input) {
this.properties = angular.extend(properties, input);
};
//TODO: This will override app-wide styles.
this.setStyles = function(input) {
this.styles = angular.extend({}, input);
};
this.build = function() {
return {
properties: properties,
styles: styles
};
};
}]);
Plunkr here
So I have one defined properties object and want to override it when I visit a page. The problem is that when I go back to home, it doesn't set the default value. Obviously it's instantiated once the page is loaded and then remains the same until changed.
What's the best approach to do this?
I have tried adding a listener to the route, as #Raul A. suggested, but it's not working. Output from console:
Plunkr here
You can use the $routeChangeSuccess event if you are using routing and make changes in the function watching for it:
$rootScope.$on("$routeChangeSuccess", function(currentRoute, previousRoute){
//Do you changes here
});
Related
I am having some trouble getting to the controller for my state param. I am using the correct state to link to the next view.
<td><a ui-sref="orders({customerId: cust.id})">View Orders</a></td>
In my config file I am referencing the state that name and the route params. I commented out the resolve object for now. My goal is to get into the controller then pass the correct data. Notice that I am using controllerAs
My initial thought was ({customerId: ctrl.cust.id }) However that did not change the url route.
The url is changing to match the url name but is not connecting to the controller and is not giving me the view.
(function() {
'use strict';
angular
.module('app.orders')
.config(config);
function config($stateProvider) {
$stateProvider
.state('orders',{
// params: {customerid: null},
url:'/customers:customerId',
templateUrl: './components/orders/orders.html',
controller: 'OrdersController',
controllerAs: 'ctrl',
resolve: {
customerFactory: 'customerFactory',
customerInfo: function( customerFactory, $stateParams) {
return customerFactory.getCustomers($stateParams.id);
}
}
************** my main problem is the resolve. This is blocking me from getting into the next controller. *****************
resolve: {
customerId:[ '$stateParams','customerFactory', function( $stateParams, customerFactory) {
return customerFactory.getCustomers($stateParams.id);
}]
}
})
};
})();
For now my controller is very small. I just want to connect to it. I have checked my networks tab and see GET for the files.
(function() {
// 'use strict';
angular
.module('app.orders')
.controller('OrdersController', OrdersController);
function OrdersController($stateParams) {
console.log('in');
var vm = this;
vm.title = "Customer Orders";
vm.customer = null;
}
}());
I have referenced my module in the main javascript file.
(function () {
'use strict';
angular.module('app', ['app.services',
'app.customers',
'app.orders','ui.router']);
})();
When I comment out the resolve I am able to access the controller. So I know the problem is in the resolve. Here is my service. I am making a request to a Json file with $http request and using .then
Updates Here is my refactored service call I am getting back the correct customer in the console each time.
(function() {
angular
.module('app.services',[])
.constant('_', window._)
.factory('customersFactory', customersFactory);
function customersFactory($http, $log) {
return {
getCustomers: getCustomers,
getCustomer: getCustomer
};
function getCustomers(){
return $http.get('./Services/customers.json',{catch: true})
.then(getCustomerListComplete)
.catch(getCustomerListFailed);
function getCustomerListComplete(response) {
console.log('response.data',response.data);
return response.data;
}
function getCustomerListFailed(error) {
console.log('error', error);
}
}
function getCustomer(id) {
var url = './Services/customers.json';
return $http.get(url, {
catch: true
})
.then(function(response) {
console.log('promise id',id);
var data = response.data;
for(var i =0, len=data.length;i<len;i++) {
console.log('data[i].id',data[i].id);
if(data[i].id === parseInt(id)) {
console.log('data[i]', data[i]);
return data[i];
}
}
})
}
}
}());
There is a working example with your code
It is very hard to guess what is wrong. Based on suggestion I gave you here Have a expression error in ui-sref ... your code seems to be completely valid.
I placed your stuff into this app.orders.js file (the ONLY change is templateUrl path, just for plunker purposes):
angular
.module('app.orders', ['ui.router'])
'use strict';
angular
.module('app.orders')
.config(['$stateProvider', config]);
//config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('orders',{
// params: {customerid: null},
url:'/customers:customerId',
//templateUrl: './components/orders/orders.html',
templateUrl: 'components/orders/orders.html',
controller: 'OrdersController',
controllerAs: 'ctrl'
// resolve: {
// customerId:[ '$stateParams','customerFactory', function( $stateParams, customerFactory) {
// return customerFactory.getCustomers($stateParams.id);
// }]
// }
})
};
// 'use strict';
angular
.module('app.orders')
.controller('OrdersController', OrdersController);
OrdersController.$inject = ['$stateParams'];
function OrdersController($stateParams) {
console.log('in');
var vm = this;
vm.title = "Customer Orders " + $stateParams.customerId;
vm.customer = null;
}
And this is the working template components/orders/orders.html:
<div >
<h3>current state name: <var>{{$state.current.name}}</var></h3>
<h5>title</h5>
<pre>{{ctrl.title}}</pre>
...
When I call it like this:
<li ng-repeat="cust in [{id:1}, {id:2}]"
><a ui-sref="orders({customerId: cust.id})">View Orders - cust ID == {{cust.id}}</a>
</li>
Check it in action here
So, whil my previous answer was about make the state working without resolve, now we will observe few adjustments (and one fix) to make even resolve working.
There is a working plunker, extending the previous one.
FIX
The only fix, the most important change come from this definition:
angular
.module('app.services',[])
.factory('customersFactory', customersFactory);
see the plural in the factory name, the 'customersFactory'. While here:
...my main problem is the resolve. This is blocking me from getting into the next controller....
resolve: {
customerId:[ '$stateParams','customerFactory', function( $stateParams, customerFactory) {
return customerFactory.getCustomers($stateParams.id);
}]
}
we ask for 'customerFactory' (singular, no s in the middle)
Few improvements:
So, this would be our adjusted state def:
$stateProvider
.state('orders',{
// INTEGER is here used to later easily use LO_DASH
url:'/customers{customerId:int}', // int is the type
templateUrl: './components/orders/orders.html',
controller: 'OrdersController',
controllerAs: 'ctrl',
resolve: {
// wrong name with 's'
//customerId:[ '$stateParams','customerFactory',
// we use customer, because we also changed the factory
// implementation - to return customer related to
// $statePrams.customerId
customer:[ '$stateParams','customersFactory',
function( $stateParams, customersFactory) {
return customersFactory
//.getCustomers($stateParams.id)
.getCustomer($stateParams.customerId)
;
}]
}
})
Now, this is our adjusted factory, and its new method getCustomer
angular
.module('app.services', [])
.factory('customersFactory', customersFactory);
customersFactory.$inject = ['$http', '$log', '$q', '$stateParams'];
function customersFactory($http, $log, $q, $stateParams) {
return {
getCustomers: getCustomers,
getCustomer: getCustomer
};
function getCustomers() {
// see plunker for this, or above in question
}
// new function
function getCustomer(id) {
var url = "customer.data.json";
return $http
.get(url, {
catch: true
})
.then(function(response){
var data = response.data;
var customer = _.find(data, {"id" : id});
return customer;
})
;
}
}
this is our data.json:
[
{
"id" : 1, "name": "Abc", "Code" : "N1"
},
{
"id" : 2, "name": "Def", "Code" : "N22"
},
{
"id" : 3, "name": "Yyz", "Code" : "N333"
}
]
And here we have controller:
OrdersController.$inject = ['$stateParams', 'customer'];
function OrdersController($stateParams, customer) {
console.log('in');
var vm = this;
vm.title = "Customer Orders " + $stateParams.customerId;
vm.customer = customer;
}
a view to show customer
<h3>customer</h3>
<pre>{{ctrl.customer | json}}</pre>
Check it here in action
Fairly new to AngularJS and WebAPI here, and figure the best way to learn is by doing. Apologies in advance if this question seems simple - I've spent a day flipping through StackOverflow and tried them all.
I currently have a separate Master & Detail view, both with their own controller. I am trying to pass the selected ID through to the Details controller so I can query my database using the ID, though am getting "undefined" on my $routeParams. I'm unsure if I am missing something simple, or whether I'm even approaching this correctly.
The controller doesn't seem to like it when I inject '$routeParams' either.
My app.js module:
var app = angular.module("ProjectDashboardModule", ["ngRoute"]);
app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider
.when("/", { templateUrl: "/Home/Index" })
.when("/Project", { templateUrl: '/Project/Index', controller: 'ProjectCrudController' })
.when("/Project/project/:id", {templateUrl:'/Project/project', controller: 'ProjectTaskController' });
$routeProvider.otherwise({redirectTo: '/home' });
$locationProvider.html5Mode(true);
}]);
my Factory.js:
app.factory('projectFactory', ['$http', function ($http) {
var urlBase = '/api/Projects/';
var projectFactory = {};
projectFactory.getProjects = function () {
return $http.get(urlBase);
};
projectFactory.getSingleProject = function (id) {
return $http.get(urlBase + '/' + id);
};
return projectFactory;
}]);
my ProjectTaskController.js:
app.controller('ProjectTaskController', ['$scope', "$routeParams", 'projectFactory', function ($scope, $routeParams, projectFactory) {
alert($routeParams.id)
$scope.project;
$scope.message;
getProjectById($routeParams.id);
function getProjectById(id) {
projectFactory.getSingleProject(id)
.success(function (data) {
$scope.project = data;
})
.error(function (error) {
$scope.message = 'error retrieving project ' + error.message;
});
}
}]);
I found that my problem was that all my angular script references were scattered. I moved all my custom script references (controller, factory, module) to index.cshtml and fixed the issue.
I found several similar questions, however none of the answers helped. They all seem to involve some type of $location dependencies that I'm unable to get injected right.
My code below:
(function() {
// App dependencies
var app = angular.module('portalExchange',
['ngRoute',
'app-products',
'app-manage',
'app-profile']);
// [ Main Controller ] : PortalController
app.controller('PortalController', function($scope) {
if ($('.top_link_dashboard').hasClass('unactive_top')) {
$('.top_link_dashboard').removeClass('unactive_top');
$('.top_link_dashboard').addClass('active_top');
}
});
// Controller for Dashboard
app.controller('DashboardController', function() {
});
// Controller for Developers
app.controller('DevelopersController', function($scope) {
// Page.setTitle('Developers');
});
// Controller for Quote
app.controller('QuoteController', function($scope) {
// Page.setTitle('Begin Quote');
});
// Directive for Header
app.directive('appHeader', function () {
// Type of Directive, E for element, A for Attribute
// url of a template
return {
restrict: 'E',
templateUrl: 'templates/modules/globals/app-header.html'
};
});
// Directive for Footer
app.directive('appFooter', function () {
return {
restrict: 'E',
templateUrl: 'templates/modules/globals/app-footer.html',
controller: function(){
this.date = Date.now();
},
controllerAs:'footer'
};
});
// configure our routes
app.config(function($routeProvider) {
$routeProvider
// route for the dashboard page
.when('/', {
templateUrl : 'templates/sections/app-dashboard.html',
controller : 'DashboardController'
})
// route for the dashboard page
.when('/dashboard', {
title : 'My Dashboard',
templateUrl : 'templates/sections/app-dashboard.html',
controller : 'DashboardController'
})
// route : Developers Page
.when('/developers', {
title : 'For Developers',
templateUrl : 'templates/sections/app-developers.html',
controller : 'DevelopersController'
})
// route : Begin Quote
.when('/quote', {
title : 'Begin Quote',
templateUrl : 'templates/sections/app-quote.html',
controller : 'QuoteController'
});
});
app.run(['$rootScope', '$route', function($rootScope) {
$rootScope.$on('$routeChangeSuccess', function(newVal, oldVal) {
if (oldVal !== newVal) {
document.title = $route.current.title;
}
});
}]);
})();
The RUN function
app.run(['$rootScope', '$route', function($rootScope) {
$rootScope.$on('$routeChangeSuccess', function(newVal, oldVal) {
if (oldVal !== newVal) {
document.title = $route.current.title;
}
});
}]);
HTML
<!DOCTYPE html>
<html lang="en" ng-app="portalExchange" ng-controller="PortalController as portal">
<head>
<meta charset="utf-8">
<title ng-bind="title">myApp</title>
</head>
The way I do it is quite simple. In route configuration you define title:
.when('/dashboard', {
title : 'My Dashboard',
templateUrl : 'templates/sections/app-dashboard.html',
controller : 'DashboardController'
})
then you listen $routeChangeSuccess event and just set document.title. In application run block (the best place for this):
app.run(['$rootScope', '$route', function($rootScope, $route) {
$rootScope.$on('$routeChangeSuccess', function() {
document.title = $route.current.title;
});
}]);
The benefit of this approach is that it allows you to avoid one more binding ng-bind="title", which is good.
This is another way
app.run(['$rootScope', function($rootScope) {
$rootScope.$on('$routeChangeSuccess', function(_, current) {
document.title = current.$$route.title;
});
}]);
Because sometimes $route injection causes problem (for example, in running unit tests).
This is a little of topic, but I was trying to manage the page title in an angular application that uses ui-router and I ran into a couple of issues. First, of course, I had to change route and $routeChangeSuccess to $state and $stateChangeSuccess and second, I had an issue with the page title getting updated before the browser could add the previous page title to the history, so I had to add a timeout to the event handler resulting the following code:
angular.module('myApp').run(appRunFunction);
appRunFunction.$inject = ['$rootScope', '$state', '$timeout'];
function appRunFunction($rootScope, $state, $timeout) {
$rootScope.$on('$stateChangeSuccess', function() {
$timeout(function() { document.title = $state.current.title; }, 100);
});
}
I've been doing quite a lot of reading about angular dependency injection and factories vs services etc like in this post here - angular.service vs angular.factory
I'm struggling putting it into practise and wonder if you can give me suggestions on how you would do it.
My current code looks like this
var app = angular.module("martysCoolApp", ['firebase', 'ngRoute'])
function mainController($scope, $firebase) {
var db = new Firebase("https://**.firebaseio.com/");
$scope.messages = $firebase(db);
$scope.addItem = function(error) {
if (error.keyCode != 13) return;
$scope.messages.$add({ name: $scope.name, price: $scope.price });
$scope.name = "";
$scope.price = "";
};
}
I decided I wanted to use angular routes and split this basic function up into two different controllers that I would use for my test app. the MainController would just display everything in the firebase db and the AdminController would be able to add messages to it
var app = angular.module("martysCoolApp", ['firebase', 'ngRoute'])
.factory('fireBaseConnectionService', $firebase)
//code in here to connect to firebase and add messages
.controller('MainController', function(fireBaseConnectionService, $scope, $route, $routeParams, $location) {
$scope.$route = $route;
$scope.$location = $location;
$scope.$routeParams = $routeParams;
//code here to retrieve everything from firebase db
})
.controller('AdminController', function(fireBaseConnectionService, $scope, $routeParams) {
$scope.name = "AdminController";
$scope.params = $routeParams;
//code here to add a row to the db
})
.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/', {
redirectTo: '/menu'
})
.when('/menu', {
path: '/menu',
templateUrl: 'partials/menu.html',
controller: 'MainController'
})
.when('/admin', {
templateUrl: 'partials/admin.html',
controller: 'AdminController'
})
.otherwise({
redirectTo: '/'
});
$locationProvider.html5Mode(false);
});
My problem is I don't want to have to connect to the firebase db in each controller. I would like to have a factory that handles this for me and has maybe functions within that that I can call from my controllers to view everything in db and to add something to the db
factory()
As we’ve seen, the factory() method is a quick way to create and configure a service.
The factory() function takes two arguments:
• name (string)
This argument takes the name of the service we want to register.
• getFn (function)
This function runs when Angular creates the service.
angular.module('myApp')
.factory('myService', function() {
return {
'username': 'auser'
}
});
The getFn will be invoked once for the duration of the app lifecycle, as the service is a singleton
object. As with other Angular services, when we define our service, getFn can take an array or a
function that will take other injectable objects.
The getFn function can return anything from a primitive value to a function to an object (similar to
the value() function).
angular.module('myApp')
.factory('githubService', [
'$http', function($http) {
return {
getUserEvents: function(username) {
// ...
}
}
}]);
service()
If we want to register an instance of a service using a constructor function, we can use service(),
which enables us to register a constructor function for our service object.
The service() method takes two arguments:
• name (string)
This argument takes the name of the service instance we want to register.
• constructor (function)
Here is the constructor function that we’ll call to instantiate the instance.
The service() function will instantiate the instance using the new keyword when creating the
instance.
var Person = function($http) {
this.getName = function() {
return $http({
method: 'GET',
url: '/api/user'
});
};
};
angular.service('personService', Person);
provider
These factories are all created through the $provide service, which is responsible for instantiating
these providers at run time.
angular.module('myApp')
.factory('myService', function() {
return {
'username': 'auser'
}
})
// This is equivalent to the
// above use of factory
.provider('myService', {
$get: function() {
return {
'username': 'auser'
}
}
});
Why would we ever need to use the .provider() method when we can just use the .factory()
method?
The answer lies in whether we need the ability to externally configure a service returned by the
.provider() method using the Angular .config() function. Unlike the other methods of service
creation, we can inject a special attribute into the config() method.
from ng-book
All you have to do is just move the firebase connection into the service, and inject that service wherever you want . The connection line will execute the first time your app runs, given that you front load the service when your app runs, as you seem to be doing now:
.factory('fireBaseConnectionService', function($firebase){
var db = $firebase(new Firebase("https://**.firebaseio.com/"));//creating
//the firebase connection this line executes only once when the service is loaded
return{
getMessage:function(){
return db.whatever;
}
}
})
If you load the service script dynamically, on route where you need it, it will only connect to the database when it reaches that route. The code above will create one connection to the database, as the connection line is executed only once.
Just for anyone interested with the help of the answers above and this link - Firebase _ AngularJS this is what I ended up doing
var app = angular.module("martysCoolApp", ['firebase', 'ngRoute'])
.factory('fireBaseConnectionService', ["$firebase", function($firebase) {
var db = new Firebase("https://***.firebaseio.com/");
return {
getMessages: function() {
return $firebase(db);
},
addMessage: function(message) {
var messages = $firebase(db);
messages.$add(message);
}
}
}])
.controller('MainController', ["fireBaseConnectionService", "$scope", function (fireBaseConnectionService, $scope, $route, $routeParams, $location) {
$scope.$route = $route;
$scope.$location = $location;
$scope.$routeParams = $routeParams;
$scope.messages = fireBaseConnectionService.getMessages();
}])
.controller('AdminController', ["fireBaseConnectionService", "$scope", function(fireBaseConnectionService, $scope, $routeParams) {
$scope.name = "AdminController";
$scope.params = $routeParams;
$scope.addItem = function(error) {
if (error.keyCode != 13) return;
fireBaseConnectionService.addMessage({ name: $scope.name, price: $scope.price });
$scope.name = "";
$scope.price = "";
}
}])
.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/', {
redirectTo: '/menu'
})
.when('/menu', {
path: '/menu',
templateUrl: 'partials/menu.html',
controller: 'MainController'
})
.when('/admin', {
templateUrl: 'partials/admin.html',
controller: 'AdminController'
})
.otherwise({
redirectTo: '/'
});
$locationProvider.html5Mode(false);
});
The problem here is I am able to access the getRoutes(), but I am unable to access the injected constant -"configuration". What am I missing? Thanks.
(function () {
'use strict';
var app = angular.module('app');
app.constant('configuration', {
PARTIAL_PATH: "/app/components/partials"
});
app.module('app', [
'routeService'
]);
var routeServiceModule = angular.module('routeService', ['common']);
routeServiceModule.provider('routeConfig',function () {
this.getRoutes = function () {
return [
{
url: '/login',
config: {
title: 'admin',
templateUrl: 'app/components/login/login.html'
}
}, {
url: '/',
config: {
templateUrl: 'app/components/dashboard/dashboard.html',
title: 'Dashboard'
}
}
];
};
this.$get = ['configuration', function (configuration) {
var service = {
getRoutes: getRoutes(),
configuration: configuration.PARTIAL_PATH
};
return service;
}];
app.config(['$routeProvider', 'routeConfigProvider', function ($routeProvider, routeConfigProvider) {
//Unable to get the configuration value
console.log(routeConfigProvider.configuration);
//Console is returning as "undefined"
routeConfigProvider.getRoutes().forEach(function(r) {
$routeProvider.when(r.url, r.config);
});
$routeProvider.otherwise({ redirectTo: '/' });
}
]);
})();
Created a plunkr demo : http://plnkr.co/edit/2TIqgxMxBJEPbnk2Wk6D?p=preview
(Regarding your last comment, with the plnkr)
The result is expected.
At config time (within app.config() ), you access raw providers, as you defined them, which allows you to call "private" methods or fields (testItem1) and to configure it for run time use. "private" because they won't be accessible at run time.
At run time (within app.run() and the rest of your app), when you ask for a dependency for which you wrote a provider, the angular injector hands you the result of the $get method of your provider, not the provider itself, so you can't access the "private" function.
This page was my path to enlightenment : AngularJS: Service vs provider vs factory
I think you may be over complicating the route stuff. You may have a very good reason for it but as I do not know it may I suggest keeping it simple with something more like this:
MyApp.config(function ($routeProvider) {
$routeProvider.when('/home', {
templateUrl: 'home.html',
controller: 'HomeController',
activeTab: 'home'
})
};
MyApp.controller('HomeController', function ($route) {
console.log($route.current.activeTab);
});
I would be interested in knowing why you may not able to use this routing pattern or purposely chose something different.
I think it has to do with the way you are creating your initial module. Try this:
var app = angular.module('app', []);
app.constant('configuration', {
PARTIAL_PATH: "/app/components/partials"
});
var routeServiceModule = angular.module('routeService', ['app']);