I have a service which I would like to use across multiple controllers. The services is defined like this:
app.factory('Data', ['$http',
function($http) {
var Data = this;
var theProduct = {};
return {
product: function() {
return theProduct;
},
getProduct: function(ext_id) {
return $http.post('get_product', {
product_id: ext_id
}).success(function(data) {
theProduct = data;
});
}
}
}
]);
I have a form, that uses the ProductFormController to retrieve product data when it's submitted. That controller looks like this:
app.controller('ProductFormController', ['$scope', '$http', 'Data',
function($scope, $http, Data) {
/*
* Get a product and all of it's data from the server
*/
$scope.getProduct = function() {
Data.getProduct($scope.extProductId).success(function(data) {
console.log(data);
});
}
}
]);
Then, I have an AppController, which should display certain sections when a product object exists in the Data service.
<div class="row" id="productInfo" ng-show="product().id" ng-controller="AppController">/div>
Within AppController, I have this line:
$scope.product = Data.product;
I would like the productInfo div to show whenever a product object exists in the Data service, but it seems that the variable never gets updated. I've seen this question, but do not believe the accepted answer actually answers the question:
Angular: share asynchronous service data between controllers
Related
I'm an angular newb. I've followed this tutorial http://www.bradoncode.com/tutorials/learn-mean-stack-tutorial/
and have slightly modified things (I have items instead of categories).
Basically I've created a MEAN app, with two models, users and items. I would like to add a property to the items model called "owner" which stores the username of the user who created the model. This isn't too hard, but a problem arises if the user changes their username - the "owner" property doesn't change in the item model instances created by the user. From what I gather there are ways of syncing data between views/models/controllers, but how would I sync data between different model instances of different models. Any help is appreciated, thanks.
Here is item.client.controller.js
'use strict';
// Items controller
angular.module('items').controller('ItemsController', ['$scope','$stateParams', '$location', 'Authentication', 'Items',
function($scope, $stateParams, $location, Authentication, Items) {
$scope.authentication = Authentication;
$scope.currentPage = 1;
$scope.pageSize = 10;
$scope.offset = 0;
// Page changed handler
$scope.pageChanged = function() {
$scope.offset = ($scope.currentPage - 1) * $scope.pageSize;
};
// Create new Item
$scope.create = function() {
// Create new Item object
var item = new Items ({
name: this.name,
description: this.description,
owner: $scope.authentication.user.username
});
Here is items.client.services.js
'use strict';
//Items service used to communicate Items REST endpoints
angular.module('items').factory('Items', ['$resource',
function($resource) {
return $resource('items/:itemId', { itemId: '#_id'
}, {
update: {
method: 'PUT'
}
});
}
]);
And users.client.services.js
'use strict';
// Users service used for communicating with the users REST endpoint
angular.module('users').factory('Users', ['$resource',
function($resource) {
return $resource('users', {}, {
update: {
method: 'PUT'
}
});
}
]);
I have the following factory:
angularModule
.factory('ArticleCategoryService', function ($http, $q) {
// Service logic
// ...
var categories = [];
var _getCategories = $http.get('/api/articles/category').success(function (_categories) {
categories = _categories;
});
// .error( function (data, status, headers, config) {
// });
// Public API here
return {
getCategories: function () {
var deferred = $q.defer();
deferred.resolve(_getCategories);
return deferred.promise;
}
};
});
and this is the section that calls this service in the controller:
// Calls the getCategories function from the ArticleCategory Service,
// Will return a promise
ArticleCategoryService.getCategories()
.then(function (categoriesResult) {
$scope.categories = categoriesResult.data;
}, function (err) {
console.log(err);
});
This works but there will be a GET call to the server every time user comes back to this view/state and the categories object that belongs to the factory is never used.
I'm trying to make it so that it will return the categories variable in the factory singleton, and have it initialize on site load (or from first GET call).
But if I just return categories when user calls getCategories, it will return nothing since we need time for the $http call.
Check if categories is defined, and resolve the promise with the variable rather than the GET request if it is:
return {
getCategories: function () {
var deferred = $q.defer();
if (categories.length > 0) {
deferred.resolve(categories);
} else {
deferred.resolve(_getCategories);
}
return deferred.promise;
}
};
I'm doing exactly same thing on my app. I have a main module and a main controller within it that wraps any other controllers, so its scope is persistent over views.
In this main controller you could assign the factory's getCategory() data to a scope variable and then you could use that in your entire app, because the scope will be inherited to child scopes.
angularMainModule
.factory('ArticleCategoryService', function ($http) {
var ArticleCategoryServiceMethods = {};
//fn: getCategories
ArticleCategoryServiceMethods.getCategories = function(fromWhere){
return $http.get(fromWhere)
.then(function(res){
return res.data;
}, function(err){
return err.status;
});
}
return ArticleCategoryServiceMethods;
}
angularMainModule
.controller('MAINCTRL', function($scope, ArticleCategoryService) {
//get all categories
$scope.categories = ArticleCategoryService.getCategories('/api/articles/category');
//... the rest of the main ctrl code ... //
}
... when you define the main module, make sure you inject the rest of your modules in it
var angularMainModule = angular.module('angularMainModule', [
'ngRoute',
'ngTouch',
'ngAnimate',
//< ng - etc >,
'Module1',
'Module2',
//< module - n >
]);
...and the markup (i'm bootstrapping my angular app manually, but you could add the ng-app="angularMainModule" attribute on the html tag if you're doing it that way):
<html ng-controller="MAINCTRL">
<!-- head data -->
<body>
<div id="page" ng-view></div>
If you want to make sure data is loaded before your app opens the main page, you could add that service call in the routeProvider block of your app (on the default route), so when the MAINCTRL will be loaded the data will be already there, ready to be assigned.
angularModule
.factory('ArticleCategoryService', function ($http) {
// Service logic
// ...
var categories = [];
$http.get('/api/articles/category').success(function (_categories) {
categories = _categories;
});
// Public API here
return {
categories: categories
};
});
angularModule
.controller('ControllerMain', function($scope, ArticleCategoryService) {
$scope.categories = ArticleCategoryService.categories;
});
I am trying to set http request data in one controller and let the data be used in multiple controller. I have something like
My services
angular.module('myApp').service('testService', ['Product','$q',
function(Product, $q) {
var products, targetProduct;
var deferred = $q.defer();
Product.query({
Id: 123
}, function(products) {
targetProduct = products[0];
deferred.resolve(products);
})
var getTargetProduct = function() {
var deferredtwo = $q.defer();
// return deferredtwo.promise;
deferred.promise.then(function(){
deferredtwo.resolve(targetProduct);
})
return deferredtwo.promise;
}
var setTargetProduct = function(targetProduct) {
targetProduct = targetProduct
}
return {
setTargetProduct: setTargetProduct,
getTargetProduct: getTargetProduct,
productPromise : deferred.promise
};
}
]);
nav controller
testService.productPromise.then(function(products){
$scope.products= products;
$scope.targetProduct = products[0];
})
//when user click the project ng-click = setTargetProduct(product);
$scope.setTargetProduct = function(targetProduct) {
testService.setTargetProduct(targetProduct)
}
product detail controller
testService.getTargetProduct().then(function(targetProduct) {
// works when page first loads
// but I don't know how to update the targetProduct when user select different
//targetProduct which means they trigger setTargetProduct() method
$scope.targetProduct = targetProduct;
})
As I stated above, I am not sure how to update the targetProduct in product detail controller when user pick another targetProduct. Can anyone help me about this? Thanks a lot!
As a matter of style, the function getTargetProduct doesn't need all this boilerplate code with promises. You want to return a simple promise wrapping your local data targetProduct. The function can be much cleaner :
var getTargetProduct = function() {
return $q.when(targetProduct);
}
Note: In the following, for convenience purpose, I will refer to your service testService by the name productService, and I will refer to your controller navController by the name ProductController
The controller NavController (gets the products as follows :
productService.getProducts().then(function(products) {
$scope.products = products;
}
When the user sets a target product (unchanged) :
$scope.setTargetProduct = function(targetProduct) {
testService.setTargetProduct(targetProduct)
}
Solution 1: nested controllers
If ProductDetailController is a nested controller of ProductController, the data targetProduct is shared without any logic from your part.
Solution 2: controllers not linked by a parent-child relationship
If the two controllers are not linked by a parent-child relationship, you can use $broadcast for broadcasting an updateTargetProduct event, and $on for handling that event.
In the controller from which we set the target product, we will find :
$rootScope.$broadcast('updateTargetProduct', targetProduct);
Note : $broadcast will broadcast the event from the rootscope down to the child scopes.
And in ProductDetailController, we will listen for this event :
$scope.$on('updateTargetProduct', function(event, data) {
// play with the received data
}
maybe your situation is not same as like me , but i made a service for my own $http call
var myService = angular.module('apix',[]);
myService.service('api',function( $http ){
this.http = function( method , path , data ){
return $http({
method: method,
url: path,
headers: {
'Content-Type' : 'application/x-www-form-urlencoded'
},
data : jQuery.param(data)
});
}
});
and used to call this as like
api.http('POST','your_path', data).success(function(result){ });
angular.module('myApp', [])
.factory('ipFactory', ['$http',
function($http) {
var service = {
getIp: function() {
return $http.get('http://ip.jsontest.com/', {
cache: true
})
.then(function(data) {
return data.data.ip;
});
}
}
return service;
}
])
.controller('ControllerOne', ['$scope', 'ipFactory',
function($scope, ipFactory) {
ipFactory.getIp()
.then(function(ip) {
$scope.ipAddress = ip;
});
}
])
.controller('ControllerTwo', ['$scope', 'ipFactory',
function($scope, ipFactory) {
ipFactory.getIp()
.then(function(ip) {
$scope.ipAddress = ip;
});
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp">
<div ng-controller="ControllerOne">
{{ipAddress}}
</div>
<div ng-controller="ControllerTwo">
{{ipAddress}}
</div>
</body>
I'm struggling to figure out how to do this. Hope anyone can help :)
I have multiple controllers in my Angular app. Like titleCtrl and SettingsCtrl
I have a service which holds a variable like this:
var myVar = {title: 'test', settings: {color: 'black', font: 'verdana'}};
I'm making a $http.get request to update the "myVar" variable from the server.
The question is, how do I update the $scope.title in titleCtrl and $scope.settings in SettingsCtrl AFTER the http request has finished? I know how to do it in a single controller, but how do I update the $scopes in multiple controllers?
Use a watch on that variable in the service. When its updated, then update your values in controller scope. Here's an example:
Inside your controller, you can watch a var myVar on YourService and when it changes, update a variable called myVarInController with the value it changed to.
$scope.$watch(
// This function returns the value being watched.
function() {
return YourService.myVar;
},
// This is the change listener, called when the value returned above changes
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
$scope.myVarInController = newValue;
}
}
);
Just in you service create a object when you get data from you server copy it to that object, so all your controllers can reference to that object.
Please see here http://plnkr.co/edit/j25GJLTHlzTEVS8HNqcA?p=preview
JS:
var app = angular.module('plunker', []);
app.service('dataSer', function($http) {
var obj = {};
getData = function() {
$http.get("test.json").then(function(response) {
angular.copy(response.data, obj);
});
}
return {
obj: obj,
getData: getData
};
});
app.controller('MainCtrl', function($scope, dataSer) {
$scope.data = dataSer;
$scope.get = function() {
$scope.data.getData()
}
});
app.controller('SecondCtrl', function($scope, dataSer) {
$scope.data = dataSer;
});
HTML:
<div ng-controller="MainCtrl">
<button ng-click="get()">get data</button>
<p>Fist Controller:
<br/>{{ data.obj.title}}</p>
</div>
<div ng-controller="SecondCtrl">
<p>Second Controller:
<br/>{{data.obj.settings}}</p>
</div>
Use both factory and service to pass value to two controllers. This is the only way to pass value
angular.module('mulipleCtrlApp', [])
.service('shareService', function () {
return {
title: 'test',
settings: {
color: 'black',
font: 'verdana'
}
};
})
.controller('titleCtrl', function ($scope, shareService) {
$scope.myVar = shareService;
$scope.testchange = function () {
$scope.myVar.title = 'Completed test';
};
})
.controller('settingCtrl', function ($scope, shareService) {
$scope.myVar = shareService;
});
Egghead Link
Jsfiddler Link example
Make your service return promise object.
Then in controller you can define a success call back to fetch title in one and settings in
another controller once the promise is resolved.
Code to use promises
In your service class:
var factory = {};
var factory.fetchData = function () {
return $http({method: 'GET', url: '/someUrl'});
}
return factory;
In controller 1:
$scope.getData = function(){
factory.fetchData().success(response){
$scope.title = response.title;
}
}
Similarly you can update controller 2, to set settings data.
I've found a better and easier maintainable solution in my opinion. Simply do the following to achieve to-way data-binding between one (or more) controller(s) with a service:
Lets assume you fetch (i.e. $http) and store data in your service (serviceName) in the variable serviceData.
In your controller reference the service like this to achieve to-way data-binding:
$scope.data = serviceName
In your view/html bind to the data properties like this:
<input ng-model="data.serviceData.title">
Thats it! :) When your serviceData variable updates the view/scope does as well. This will work with multiple controllers.
I'm trying to create a simple blog website using AngularJS. I'm just starting out, so what I'm thinking my not be the best way to do this, so any alternative suggestions are welcome.
I have a controller.js file with two blog controllers. One to display a list of blog posts, and the other that displays the post content by including an HTML file.
controller.js
myAppControllers.controller('BlogListCtrl', ['$scope', '$http', function ($scope, $http) {
$http.get('articles/articles.json').success(function (articles) {
$scope.articles = articles;
});
}]);
myAppControllers.controller('BlogPostCtrl', ['$scope', '$routeParams', function ($scope, $routeParams) {
$scope.includeFile = 'articles/' + $routeParams.blogPostId + '.html';
}]);
articles.json
[
{
"id": "test-article-one",
"title": "Test Article one",
"author": "Gareth Lewis",
"datePosted": "2015-06-23",
"summary": "This is a test summary"
},
{
"id": "test-article-two",
"title": "Test article two",
"author": "Gareth Lewis",
"datePosted": "2015-06-23",
"summary": "This is a test for article two"
}
]
app.js
when('/blog', {
templateUrl: 'partials/blog-articles.html',
controller: 'BlogListCtrl'
}).
when('/blog/:blogPostId', {
templateUrl: 'partials/blog-post.html',
controller: 'BlogPostCtrl'
}).
blog-post.html
<ng-include src="'partials/header.html'"></ng-include>
<!-- Want to add title, author, datePosted information here... -->
<article class="content">
<ng-include src="includeFile"></ng-include>
</article>
This blog listings work fine. When I click into a blog post, it also serves up the content from the HTML file OK as well. However, I want to be able to reuse the title, author and datePosted properties from the selected article in the blog-post.html partial view. What's the best way to do this? Would I need to pass them to the Controller somehow to then pass to the view? I don't really want to pass these as routeParams. Or would I need to do a $http.get on articles.json and iterate through to find the selected article and then pass the property values back to the view?
Thanks for the help.
You said that suggestions are welcome, so here it goes.
1 - Transport all your Blog logic to a service;
2 - Provide the data on resolving routes. This is a better approach to handle errors during the load time, 404s, and so on. You can provide a listener to $routeChangeError and deal with it there;
3 - On the service declared below, you have the methods to call your data and a method to retrieve the list cached on the service:
// services.js
myAppServices
.service('BlogService', ['$http', '$q', function ($http, $q) {
var api = {},
currentData = {
list: [],
article: {}
};
api.getSaved = function () {
return currentData;
};
api.listArticles = function () {
var deferred = $q.defer(),
backup = angular.copy(currentData.list);
$http.get('articles/articles.json')
.then(function (response) {
currentData.list = response;
deferred.resolve(response);
}, function () {
currentData.list = backup;
deferred.reject(reason);
});
return deferred.promise;
};
api.getArticle = function (id) {
var deferred = $q.defer(),
backup = angular.copy(currentData.article),
path = 'articles/' + id + '.html';
$http.get(path, {
cache: true
})
.then(function (response) {
currentData.article = {
path: path,
response: response
};
deferred.resolve(currentData.article);
}, function (reason) {
currentData.article = backup;
deferred.reject(currentData.article);
});
return deferred.promise;
};
return api;
}]);
The BlogService.getSaved() will retrieve the stored data, made after each call.
I've made a method to call the ng-include path too, so you can verify if it exists, with cache === true, the browser will keep a copy of it, when calling it again on the view. A copy of the response of the blog article is made too, so you can access its path and the response whenever you need.
On the controllers below, they were adaptated to supply the current needs:
// controller.js
myAppControllers
.controller('BlogListCtrl', ['$scope', 'articles',
function ($scope, articles) {
$scope.articles = articles;
/* OTHER STUFF HERE */
}
])
.controller('BlogPostCtrl', ['$routeParams', '$scope', 'article' 'BlogService',
function ($routeParams, $scope, article, BlogService) {
// On `article` dependency, you have both the original response
// and the path formed. If you want to use any of it.
$scope.includeFile = article.path;
// To get the current stored data (if any):
$scope.articles = BlogService.getSaved().list;
// Traverse the array to get your current article:
$scope.article = $scope.articles.filter(function (item) {
return item.id === $routeParams.id;
});
/* OTHER STUFF HERE */
}
]);
And the route declarations were changed to load the data when resolving the routes.
// app.js
$routeProvider
.when('/blog', {
templateUrl: 'partials/blog-articles.html',
controller: 'BlogListCtrl',
resolve: {
articles: ['BlogService', '$routeParams', function (BlogService, $routeParams) {
return BlogService.listArticles();
}]
}
})
.when('/blog/:id', {
templateUrl: 'partials/blog-post.html',
controller: 'BlogPostCtrl',
resolve: {
article: ['BlogService', '$routeParams', function (BlogService, $routeParams) {
return BlogService.getArticle($routeParams.blogPostId);
}]
}
})
This is maybe a common question in angular. What you have to understand is that Scope is defined per controller... In order to share data across controller you still have the option to use $scope.$parent or $rootScope to link controllers but I would use those carefully.
It is better to use Angular Services which are based on singleton patterns therefore you can use them to share information between controllers and I think it will be a better approach.
I found that this has been previously discussed and here are some good examples:
AngularJS Service Passing Data Between Controllers
You can use a global scope to set this data, or you can use service to communicate between the controllers. There is a lot of ways to resolve this problem read a little bit more about services in the link bellow and see if you can find how to resolve your problem.
AngularJS: Service vs provider vs factory