I have an AngularJs application working with components and several modules. I created a plunker example to present my problem here.
I have my NavbarComponent where I declared my Controller where I inject my service called NavbarService.
In the NavbarService, I inject a factory resource to make my Rest call, once this call is made I'm trying to made some treatment on the response before returning it back to the controller, in this example I just apply a simple filter on it, but it doesn't work. If I omit my treatment and return only the categories, the code works and you can visualize a list of two.
I can make my treatment in the controller but this is a bad practice 'cause I believe it should be done in the Service, secondly since it's an asynchronous response I must do something like this to make it work, which is really really ugly:
navbarService.getCategories().$promise.then(function (response) {
console.log("controller", response[0].category);
vm.categories = categoryFilter(response[0].category);
}, function (error) {
console.log("an error occured");
});
Can someone please guide me through this, I'm out of solutions. Thank you
Another simple way is to pass a callback function to service from you component like this
'use strict';
angular.module('navbar').component('appNavbar', {
templateUrl: "navbar.template.html",
controller: [ 'navbarService', function appNavbarController(navbarService) {
var vm = this;
navbarService.getCategories(function(data){
// this will be called when service will get the response and filter function has filtered the response
vm.categories = data;
});
}]
});
Now service should be like this
'use strict';
angular.module('navbar').service('navbarService', ['categoryResourceService', 'categoryFilter', function(categoryResourceService, categoryFilter) {
var vm = this;
vm.getCategories = function(callback) {
categoryResourceService.query(function(response) {
console.log("[NavbarService] response:", response);
callback(categoryFilter(response));
}, function(error) {
console.log("[NavbarService] error:", error);
});
//return vm.categories;
}
}]);
Filter will be like this
'use strict';
angular.module('navbar').filter('category', function() {
return function(categories) {
var categoryIds = ['World'];
var result = [];
angular.forEach(categoryIds, function (categoryId) {
angular.forEach(categories, function (category) {
if (category.name == categoryId) {
console.log("Match");
result.push(category);
}
});
});
return result;
};
});
Your filter should be like this and it should be called in transformResponse in $resource query instead of service, i hope this will help you
'use strict';
angular.module('navbar').filter('category', function() {
return function(categories) {
var categoryIds = ['World'];
var result = [];
angular.forEach(categoryIds, function (categoryId) {
angular.forEach(categories, function (category) {
if (category.name == categoryId) {
console.log("Match");
result.push(category);
}
});
});
return result;
};
});
Your categoryResource.service should be like this
angular.module('shared').factory('categoryResourceService',
['$resource','categoryFilter', function($resource, categoryFilter) {
var provider = "categories.json";
var params = {
id: '#id'
};
return $resource(provider, params, {
query: {
isArray: true,
method: 'GET',
params: {},
transformResponse: function(categories) {
var results = categoryFilter(angular.fromJson(categories));
console.log("[categoryResourceService] filtered response:", results);
return results;
}
}
});
}]);
navbar.service should be like this simply
'use strict';
angular.module('navbar')
.service('navbarService', [ 'categoryResourceService', function (categoryResourceService) {
var vm = this;
vm.getCategories = function(){
vm.categories = categoryResourceService.query(function(response){
console.log("[NavbarService] response:", response);
}, function(error){
console.log("[NavbarService] error:", error);
});
return vm.categories;
}
}]);
And components like this
'use strict';
angular.module('navbar').component('appNavbar', {
templateUrl: "navbar.template.html",
controller: [ 'navbarService', function appNavbarController(navbarService) {
var vm = this;
vm.categories = navbarService.getCategories();
}]
});
Related
I'm working on some legacy code that uses angularjs 1.x for a web frontend. I need to create a modal dialog that will make a RESTful call to the backend when the modal is opened and wait for the data to be returned before rendering the view.
I was able to figure out most of what I needed to do, but there is one thing I still can't wrap my head around. My understanding was that I needed to use 'resolve' to define a function that would return a $promise to the controller. When I put a breakpoint inside my controller though, the parameter is an object containing the promise, the resolution status, and finally my actual data.
I can pull the data I need out of this object, but it feels like I shouldn't have to do that. My controller doesn't care about the promise itself; just the data that got returned. Is there some way to structure this so only the data gets sent to the controller or is this just how angular modals are expected to behave?
A sample of my code:
$scope.openTerritorySelect = function () {
var modalInstance = $modal.open({
animation: true,
templateUrl: 'prospect/detail/selectTerritoriesModal.tpl.html',
controller: function($scope, $modalInstance, availableReps){
$scope.reps = availableReps;
$scope.ok=function()
{
$modalInstance.close();
};
$scope.cancel=function()
{
$modalInstance.dismiss('cancel');
};
},
resolve: {
availableReps: function () {
return Prospect.getRelatedReps({}, function (data, header) {
$scope.busy = false;
return data.result;
}, function (response) {
$scope.busy = false;
if (response.status === 404) {
$rootScope.navError = "Could not get reps";
$location.path("/naverror");
}
}).$promise;
}
}
});
modalInstance.result.then(function (selectedReps) {
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
};
The 'Prospect' service class:
angular.module('customer.prospect', [ "ngResource" ]).factory('Prospect', [ 'contextRoute', '$resource', function(contextRoute, $resource) {
return {
getRelatedReps : function(args, success, fail) {
return this.payload.getRelatedReps(args, success, fail);
},
payload : $resource(contextRoute + '/api/v1/prospects/:id', {
}, {
'getRelatedReps' : {
url : contextRoute + '/api/v1/prospects/territories/reps',
method : 'GET',
isArray : false
}
})
};
} ]);
You could simplify things a great deal by making the REST request before you even open the modal. Would you even want to open the modal if the request were to fail?
$scope.openTerritorySelect = function () {
Prospect.getRelatedReps({}, function (data, header) {
$scope.busy = false;
var modalInstance = $modal.open({
animation: true,
templateUrl: 'prospect/detail/selectTerritoriesModal.tpl.html',
controller: function($scope, $modalInstance, availableReps){
$scope.reps = availableReps;
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
},
resolve: {
availableReps: function () {
return data.result;
}
});
modalInstance.result.then(function (selectedReps) {},
function () {
console.log('Modal dismissed at: ' + new Date());
});
}, function (response) {
$scope.busy = false;
if (response.status === 404) {
$rootScope.navError = "Could not get reps";
$location.path("/naverror");
}
});
};
I'm new to angularjs. In my webapp I'm trying to work with user contacts as follows.
SERVICE
app.service('Contacts', function ($http,$timeout,$q) {
return {
getData: function() {
var defer = $q.defer();
$http.get('../ListContacts')
.success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
}
});
ContactsController, OtherControllers
$scope.contactsBook = {};
...
Contacts.getData().then(function(data) {
$scope.contactsBook = data;
});
I found the above method somewhere in SO itself. I used it because I don't want to use separate module for Contacts.
I can get data at page load. I can update my contacts at server through ajax posts (from ContactsController). Now I only need a way to update(/refresh) the list automatically in all controllers. How can I achieve that.
I found these three links related but being a newbie I'm unable to figure my way out.
While it is understandable that you may not want to update your current architecture, it may be necessary to adjust your calls slightly if you want to be able to easily share data between controllers via a service.
One flexible approach is to store the data in your service and register watchers in each controller. This allows you to call the service update from one controller (the Contacts controller) and have the change be reflected in all consuming controllers. Note the service is mocked.
You can find the working plunker example here.
Contacts Service:
var app = angular.module('app', []);
app.service('contactsService', function ($http) {
var contacts = [];
return {
loadData: function() {
var mockGet = $q.defer();
var data = [
{ id: 1, name: 'Jack' },
{ id: 2, name: 'Jill' }
];
contacts = data;
mockGet.resolve(contacts);
return mockGet.promise;
},
retrieveNewData: function() {
var mockGet = $q.defer();
var data = [
{ id: 1, name: 'Jack' },
{ id: 2, name: 'Jill' },
{ id: 3, name: 'Bob' },
{ id: 4, name: 'Susan' }
];
contacts = data;
mockGet.resolve(contacts);
return mockGet.promise;
},
getContacts: function () {
return contacts;
}
}
});
Contacts Controller:
app.controller('ContactsCtrl', ['$scope', 'contactsService',
function ($scope, contactsService) {
var vm = this;
vm.contacts = [];
vm.loadData = loadData;
vm.retrieveNewData = retrieveNewData;
$scope.$watch(angular.bind(contactsService, function () {
return contactsService.getContacts();
}), function (newVal) {
vm.contacts = newVal;
});
function loadData() {
contactsService.loadData();
}
function retrieveNewData() {
contactsService.retrieveNewData();
}
}
]);
Other Controller:
app.controller('OtherCtrl', ['$scope', 'contactsService',
function($scope, contactsService) {
var vm = this;
vm.contacts = [];
$scope.$watch(angular.bind(contactsService, function () {
return contactsService.getContacts();
}), function (newVal) {
vm.contacts = newVal;
});
}
]);
You can do
Contacts.getData().then(function(data) {
$scope.contactsBook = data;
$scope.$emit('contacts:updated', data);
});
And then, where you need to notify the controller about the update:
$rootScope.$on('contacts:updated', function(e, contacts) {
$scope.contacts = contacts;
});
Another approach
The service is holding the current contacts list
app.service('Contacts', function ($http,$timeout,$q) {
this.currentList = [];
this.getData = function() {
var defer = $q.defer();
$http.get('../ListContacts')
.success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
});
In your controller:
Contacts.getData().then(function(data) {
$scope.contactsBook = data;
Contacts.currentList = data;
});
In other controller:
controller('AnotherController', function($scope, Contacts) {
$scope.contacts = Contacts.currentList;
});
If you are going to return an object literal you will need to turn your .service() into a .factory() module . In this case I'll be using a service module .
Example
Your service .
app.service('Contacts', function ($http,$timeout,$q) {
var Contacts = this;
contacts.getData = function() {
var defer = $q.defer();
$http.get('../ListContacts')
.success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
}
return Contacts;
});
You will then need to inject this server into your ContactsController .
app.controller('ContactsController', function(Contacts){
$scope.data = null;
$scope.init = function(){
Contacts.getData().then(function(response){
$scope.data = response;
})
}
})
now data can be used in dom
Example
<li ng-repeat="x in data">{{x.name}}</li>
I'm listing users with:
/api/users/
I'd like to list users who are admins by calling:
/api/users/admins
As trivial as it may seem, I can't find a way to do this.
I do not know which programming language you are using, but I'm going to give you an example using PHP Laravel, and AngularJS.
API
Route::get('/api/users', function ()
{
$users = App\User::all();
return $users;
});
Route::get('/api/users/admin', function ()
{
$users = App\User::where('admin', true)->get();
return $users;
});
FRONT
angular.module('app', [])
.service('api', ['$http', function ($http) {
function getUsers() {
return $http.get('/api/users');
}
function getAdminUsers() {
return $http.get('/api/users/admin');
}
this.getUsers = getUsers;
this.getAdminUsers = getAdminUsers;
}])
.controller('UserCtrl', ['$scope', 'api', function ($scope, api) {
$scope.users = [];
$scope.adminUsers = [];
api.getUsers()
.then(function success(response) {
$scope.users = response.data;
}, function error(response) {
});
api.getAdminUsers()
.then(function success(response) {
$scope.adminUsers = response.data;
}, function error(response) {
});
}]);
Sorry about the lack of details in my question. I was actually asking the question about the angular-restmod module.
Here's what I did in the end:
module.factory('CustomMethods', ['restmod', 'RMUtils', function CustomMethodsMixin(restmod, RMUtils) {
return restmod.mixin(function() {
this.define('Model.$customCollection', function(_url, params) {
var original = this;
return this.$collection(params, {
$urlFor: function() {
return RMUtils.joinUrl(original.$url(), _url);
}
});
});
return this;
});
}]);
And expose all my api to it:
restmodProvider.rebase('CustomMethods')
I have the following controllers:
angular.module('app').controller('ParentCtrl', function(Service) {
var list = []
var init = function () {
Service.query().success(function() {
list = Service.getList();
});
}
});
angular.module('app').controller('ChildCtrl', function(Service) {
var list = []
var init = function () {
list = Service.getList();
}
});
angular.module('app').factory('Service', function($http) {
list = []
return {
query : function() {
$http.get('/path/to/my/api').success(function(data){
list = data
})
},
getList: function() {
return list;
}
}
});
My HTML is as follows:
<div ng-controller="ParentCtrl as parent">
<div ng-controller="ChildCtrl as child">
</div>
</div>
So basically when I receive the AJAX request I want both the controllers to get updated with the data
The best way would be to return the promise from $http.get:
angular.module('app').factory('Service', function($http) {
var promise;
return {
getList: function() {
if (promise) {
return promise;
}
promise = $http.get('/path/to/my/api');
return promise;
}
}
});
angular.module('app').controller('ParentCtrl', function(Service) {
var list = [];
var init = function () {
Service.getList().then(function(response) {
list = response.data;
});
}
});
angular.module('app').controller('ChildCtrl', function(Service) {
var list = [];
var init = function () {
Service.getList().then(function(response) {
list = response.data;
});
}
});
You can broadcast custom message to rootScope and Your controllers will get this message and handle it.
angular.module('app').controller('ParentCtrl', function($rootScope, $scope, Service) {
$scope.list = [];
$scope.$on('Service:list', function(event, data){
$scope.list = data;
});
});
angular.module('app').controller('ChildCtrl', function($rootScope, $scope, Service) {
$scope.list = [];
$scope.$on('Service:list', function(event, data){
$scope.list = data;
});
Service.query(); // run once, get in both controllers
});
angular.module('app').factory('Service', function($rootScope, $http) {
var list;
return {
query : function() {
$http.get('/path/to/my/api').success(function(data){
list = data;
$rootScope.$broadcast('Service:list', list);
})
},
getList: function() {
return list;
}
}
});
You could handle it in many ways. One simple way is to cache the promise and return it.
Example:-
angular.module('app').factory('Service', function($http, $q) {
var listPromise;
return {
getList: function() {
//If promise is already present then
return listPromise || (listPromise = $http.get('/path/to/my/api').then(function(response){
return response.data;
})
//Use the below 2 blocks (catch and finally) only if you need.
//Invalidate in case of error
.catch(function(error){
listPromise = null;
return $q.reject(error);
})
//Use finally to clean up promise only if you only need to avoid simultaneous request to the server and do not want to cache the data for ever.
.finally(function(){
listPromise = null;
}));
}
}
});
and in controller:
angular.module('app').controller('ParentCtrl', function(Service) {
var list = [];
var init = function () {
Service.getList().then(function(data) {
list = data;
});
}
});
angular.module('app').controller('ChildCtrl', function(Service) {
var list = [];
var init = function () {
Service.getList().then(function(data) {
list = data;
});
}
});
Caching a promise will make sure that it really does not matter who makes the first call and you always make the same service call to get the data and service will manage data caching via promise and prevent any duplicate calls.
Another practice is to implement a one-way data flow using flux pattern. Where you create stores that maintains data and it will make ajax call via a dispatcher and emits event to the subscribers of change event. There is an angular library (flux-angular) that can be used as well to implement this pattern. This will really help synchronize data between multiple components regardless of whether they are parent/child or siblings and regardless of who makes the first call etc..
I'm newbie in angularjs and I'm trying to create new provider. This is my code:
myApp.provider('$Data', function() {
this.URL = 'http://maps.googleapis.com/maps/api/geocode/json?address=Singapore, SG, Singapore, 153 Bukit Batok Street 1&sensor=true';
this.$get = $get;
$get.$inject = ['$http', '$q'];
function $get($http, $q) {
var that = this;
return {
isConnected: function() {
var bIsConnected = 'Default';
$http({method: 'GET', url:that.URL}).then(function (data) {
bIsConnected = 'Yes';
alert('Run this code!');
}, function (data) {
bIsConnected = 'No';
});
return bIsConnected;
}
}
}
});
Jsfiddle demo:
http://jsfiddle.net/0udm9/9dPsb/6/
After I run $Data.isConnected(), the result is always 'Default' although browser show the alert box. I think it's from success function is not of $get. And I have to use provider, not service or factory for this case. Can I do anything to fix this issue?
Thanks,
You have to use promise in your code.
DEMO
Provider:
isConnected: function() {
var deferred = $q.defer();
$http.get(that.url).then(function(res) {
deferred.resolve('Yes');
console.log('example:success', res);
}, function(err) {
deferred.resolve('No');
console.log('example:error', err);
});
return deferred.promise;
}
Controller:
$Data.isConnected().then(function(data) {
$scope.data = data;
});
// UPD
You must use objects if you need to use return values with async code.
DEMO
// UPD 2
FRESH DEMO LINK