I have 3 factory functions that I would like to chain together to be used in a resolve stanza of a route:
1st function is a simple REST call to $http:
app.factory('services', ['$http', '$q', function ($http, $q) {
var serviceBase = 'services/';
var obj = {};
obj.getSelect = function (db, table, columns, keys) {
return $http.post(serviceBase + 'getSelect', {
selectDB: db,
selectTable: table,
selectColumn: columns,
selectKeys: keys
}).then(function (results) {
return results;
});
};
// more objects follow
}
It is used by the next function that simply calls services.getSelect to retrieve some records:
app.factory('myFunctions', ['services', '$q', function (services, $q) {
return {
fGetData: function () {
services.getSelect(
'myDB', // DB
'tableInDB', // Table
"*", // Columns
"" // Keys
).then(
function (retObj) {
return $q.all (retObj);
console.log('myFunctions.fGetData', retObj);
}
)
}
}
}]);
The last function calls myFunctions.fGetData. Its purpose is to return values to the resolve stanza:
app.factory("getInitData",['myFunctions','$q', function (myFunctions, $q) {
return function () {
var initData = myFunctions.fGetData();
return $q.all( {initData: results} ).then(function (results) {
return {
initDataReturn: results
};
console.log('getInitData', results);
});
}
}]);
and finally the resolve stanza:
app.config( ['$routeProvider', 'myConst', function ($routeProvider) {
$routeProvider.when(myConst.adminButtonURL, {
templateUrl: '/myTemplateURL',
controller: myControler,
resolve: {
initDataObj: function(getInitData){
return getInitData();
}
}
}
}]);
In the controller the initDataObj is returned:
app.controller('myController', function ($scope, nitDataObj {
$scope.surveyGroup = initDataObj.initData;
});
The console logs always show that 'getInitdata' always fires first and the return is a null object.
The function myFunctions.fGetData always fires first and the correct data is returned.
To miss-quote a song from He Haw: "I've searched the world over and I thought I'd find the answer (true love is lyric)", but while there have been very interesting bits of clues including
http://busypeoples.github.io/post/promises-in-angular-js/ &
http://www.dwmkerr.com/promises-in-angularjs-the-definitive-guide/
nothing has had the complete answer.
Thanks to all.
ok i think in part this has to do with the way you are using $q
$q.all take an array or an object containing promises
https://docs.angularjs.org/api/ng/service/$q
in your services factory you are resolving the promise and then returning the results
and then in myFunctions you are taking the returned value and trying to give it to $q.all which doesnt accept what you're giving it
looks like your wanting to keep sending a promise back from each factory you could do something like
app.factory('services', ['$http', function ($http) {
var serviceBase = 'services/';
var obj = {};
obj.getSelect = function (db, table, columns, keys) {
return $http.post(serviceBase + 'getSelect', {
selectDB: db,
selectTable: table,
selectColumn: columns,
selectKeys: keys
});
};
// more objects follow
}
app.factory("myFunctions", ["$q", "services", function($q, services){
return {
fGetData: function(){
var deferred = $q.defer();
services.getSelect()
.success(function(results){
// do something with the data
deferred.resolve(results);
});
return deferred.promise;
}
};
}]);
app.factory("getInitData",['myFunctions', function (myFunctions) {
return function () {
myFunctions.fGetData()
.then(function(data){
// do something with the data
});
}
}]);
Related
I want to fetch particular category by id. I have populated dummyjson data using $http method.I am unable to do this. I have pass id from my services to controller but it returns null. Here i my code
service.js:
(function() {
angular.module('myApp')
.factory('registerService', function ($http) {
var category=[];
return {
getAll:function(){
return $http.get('json/dummyJson.json').then(function(response){
category=response.data;
return category;
});
},
getUser:function(category_id)
{
for(var i=0;i<category.length;i++){
console.log(category.length);
if(category[i].id === parseInt(category_id)){
return category[i];
}
}
return null;
}
}
});
})();
controller.js:
(function() {
angular.module('myApp').controller('registrationCtrl1', function ($scope, $stateParams, registerService) {
console.log('inside registerCtrl2');
$scope.categoryName=registerService.getUser($stateParams.category_id);
console.log($stateParams.category_id);
console.log($scope.categoryName);
});
})();
You are assuming that category will have value within getUser, but that won't happen. The only place where category is populated is getAll, but since you are not calling getAll, category is not populated. Chnage your method as below and it will work:
getUser: function(category_id) {
return $http.get('json/dummyJson.json').then(function(response){
category=response.data;
for(var i=0;i<category.length;i++) {
console.log(category.length);
if(category[i].id === parseInt(category_id)){
return category[i];
}
}
return null;
});
}
Since your method returns a promise now you need to handle the promise in a callback in your controller:
angular.module('myApp').controller('registrationCtrl1', function ($scope, $stateParams, registerService) {
console.log('inside registerCtrl2');
registerService.getUser($stateParams.category_id)
.then(function (response) {
$scope.categoryName= response;
console.log($stateParams.category_id);
console.log($scope.categoryName);
});
});
I have code:
angular.module('admin', [])
.provider('users', function () {
this.users = 'default';
this.$get = function () {
var that = this;
return {
getUsers: function () {
return that.users;
}
}
};
})
.run(function (users, $http) {
users.users = $http('url'); // and others
})
.controller('test', function ($scope, users) {
$scope.users = users.getUsers();
});
I would like to intitalize data in .run() method (I can't use .config() method because it doesn't let to pass any services like $http). I found .run() method, but this code doesn't work... Data aren't saved in provider. Official documentation says:
"Execute this function after injector creation. Useful for application initialization."
I think it's best way to initialize data.
You may want to use an Angular Factory/Service for this kind of need. That is what I do. And pass that into the application. That service will be your singleton or source of truth about the dat.
angular.module('myData.services', [])
.factory('myData', ['$rootScope', '$http' , function($rootScope,$http) {
var factory = {
myData : {}
};
$http('/api/call', function(apiData) {
factory.myData = apiData;
});
return factory;
}]);
You could then use this in your controllers:
angular.module('myApp.controllers', [])
.controller('myCtrl', ['myData', '$scope', function(myData, $scope){
$scope.users = myData;
}]);
Check out the documentation on services: https://docs.angularjs.org/guide/services
Second attempt
angular.module('admin', [])
.factory('users', function ($http) {
var users = {};
var data = [];
$http.get('database.php')
.then(function (response) {
data = response.data;
});
users.getData = function () {
return data;
};
return users;
})
.controller('test', function ($scope, users) {
console.log(users.getData());
});
I would like to have data private. Empty Array returned, reponse comes with all data.
Provider configuration can be doable inside config block only, you can't do that inside run block
Though I don't find a reason to load users object while configuring app. I'd say that you should use either service/factory for this.
Code
angular.module('admin', [])
.service('users', function($http, $q) {
var users = [];
//make an get call to fetch users
function getUsers() {
return $http.get('database.php')
.then(function(response) {
data = response.data;
});
}
//will make a call if users aren't there
this.getData = function() {
// Handled below two conditions
// 1. If users aren't fetched the do an Ajax
// 2. If last ajax doesn't return a data then DO it again..
if (users.length > 0)
return $q.resolve(data); //do return data using dummy promise
return getUsers();
};
return users;
})
.controller('test', function($scope, users) {
users.getData().then(function(data){
console.log(data);
});
});
I have a factory where I wanna return a promise inside a function but my controller says everytime something like:
Provider 'personFactory' must return a value from $get factory method.
My factory:
(function () {
'use strict';
angular
.module('testApp')
.factory('personFactory', personFactory);
personFactory.$inject = ['storage'];
function personFactory(storage) {
storage.getData().then(function (response) {
return {
allData: function () {
return response
}
}
});
}
})();
My controller:
(function () {
'use strict';
angular
.module('testApp')
.controller('menuListController', menuListController);
menuListController.$inject = ['$scope', 'personFactory', '$rootScope'];
function menuListController($scope, personFactory) {
$scope.fromFactory = personFactory.allData();
console.log($scope.fromFactory)
}
})();
I think I have to double return both the functions, but can't get the syntax right to call it also in the controller if that is the problem.
Your factory definition has to return something, that's the problem:
function personFactory(storage) {
return storage.getData().then(function (response) {
return {
allData: function () {
return response
}
}
});
}
Now, I don't know if that's what you really need. I usually return a function or object in the factory that then I use.
With your approach you will invoke getData only once and receive the promise directly in your controller.
I like it, now that I think about it :)
But! Looking at the rest of your code, I think you're not expecting to get the promise after all.
You would have to do something like this anyway:
personFactory.then(function () {
$scope.fromFactory = data.allData();
});
Which again makes me think you don't need to do that allData business. Here's what I'd do:
function personFactory(storage) {
return storage.getData();
}
personFactory.then(function (response) {
$scope.fromFactory = response;
});
How does that look?
This seems so simple yet I have been banging my head against it for hours now...
I have an angular 1.2.29 module that uses ui-router to resolve a data dependency from a service and inject it into a named nested view controller. The service returns my data object exactly as desired and I can inject that returned data into my view controller and log it again with no issue.
What I am baffled by is that if I inspect the object in the console, I can see the array I am trying to access on one of the nested properties of the object I injected from the service (this is from within the context of the controller I am trying to use it in).
However, if I try and pass that array of data into another method in the same controller (in this case a d3 method to draw a graph) - I get nothing but a empty string where the array is.
I can access every other property on the object I passed into the method just fine, and so long as I don't try and access the array directly it is 'visible' to me in my logging, but when I try and pass the array directly in I get an empty string.
I have tried everything I can from making a deep copy or pushing the array into a new object and accessing it that way, trying to cast that property as an array (again into a new object), and a whole host of other random hacks I have run across all to no avail.
I feel like I am missing something incredibly trivial here...
These three logs are called on the same object from within the same method one after the other and you can see the issue.
I am trying to pass the data.datasources.tabledata.datum array to another method that expects an array, but all I can get is the empty string.
Note: 'data' is the object returned from the service resolve at the state transition and injected into the view controller.
> console.log(data);
// Expected object.
> Object {datasources: Object}
datasources: Object
tabledata: Object
datum: Array[9383]
source: "./data/BSGAM_Heads_Wells_Drains_Zones_Master.csv"
__proto__: Object
__proto__: Object
__proto__: Object
> console.log(data.datasources);
// Expected object.
> Object {tabledata: Object}
tabledata: Object
datum: Array[9383]
source: "./data/BSGAM_Heads_Wells_Drains_Zones_Master.csv"
__proto__: Object
__proto__: Object
> console.log(data.datasources.tabledata);
// String instead of array... WTH???
> Object {source: "./data/BSGAM_Heads_Wells_Drains_Zones_Master.csv", datum: ""}
> console.log(data.datasources.tabledata.datum);
// Returns NOTHING - not even undefined... just an empty log line #_#
>
UPDATE: Here is the service loading the data (it is my own)
(function() {
'use strict';
angular
.module('mcsdss.providers')
.factory('FormulationRetrieval', FormulationRetrieval);
FormulationRetrieval.$inject = ['$http'];
function FormulationRetrieval($http) {
FormulationRetrieval.getFormulation = function (target) {
var promise = $http
.get(target)
.then(function (response) {
return FormulationRetrieval.configureFormulation(response.data);
});
return promise;
};
FormulationRetrieval.configureFormulation = function (f) {
FormulationRetrieval.formulationContainer = f;
FormulationRetrieval.loadFormulationSourceData(FormulationRetrieval.formulationContainer);
FormulationRetrieval.loadFormulationGisData(FormulationRetrieval.formulationContainer);
return FormulationRetrieval.formulationContainer;
};
FormulationRetrieval.loadFormulationSourceData = function (fc) {
function parseFormulationDatasource(fd, destination) {
Papa.parse(fd, {
complete: function(results) {
destination.datum = results.data;
}
});
}
function loadData(target) {
var promise = $http
.get(target.source)
.then(function (response) {
parseFormulationDatasource(response.data, target);
});
return promise;
}
var datasources = [fc.datagridConfig.datasources.tabledata, fc.graphConfig.datasources.graphContextData];
angular.forEach(datasources, loadData);
};
FormulationRetrieval.loadFormulationGisData = function (fc) {
function loadGeodata(target) {
angular.forEach(target, function(value, key) {
var promise = $http
.get(value.source)
.then(function (response) {
value.datum = response.data;
});
return promise;
});
}
var datasources = [fc.mapConfig.datasources.geojson];
angular.forEach(datasources, loadGeodata);
};
FormulationRetrieval.getAnalysisConfig = function (fc) {
var analysisConfig = fc.analysisConfig;
return analysisConfig;
};
FormulationRetrieval.getMaufConfig = function (fc) {
var maufConfig = fc.maufConfig;
return maufConfig;
};
FormulationRetrieval.getGraphConfig = function (fc) {
var graphConfig = fc.graphConfig;
return graphConfig;
};
FormulationRetrieval.getTableConfig = function (fc) {
var tableConfig = fc.datagridConfig;
return tableConfig;
};
FormulationRetrieval.getMapConfig = function (fc) {
var mapConfig = fc.mapConfig;
return mapConfig;
};
return FormulationRetrieval;
}
})();
Here is the ui-router resolve on the state:
(function() {
'use strict';
angular
.module('analyze')
.config(DashboardRoutes);
DashboardRoutes.$inject = ['$stateProvider'];
function DashboardRoutes($stateProvider) {
// Define states.
var analyze_state = {
abstract: true,
url: '/analyze',
templateUrl: 'modules/analyze/views/analyze.client.view.html',
controller: 'AnalyzeViewController',
controllerAs: 'analyze',
data: {
title: 'Analyze'
},
resolve: {
analysisData: function(FormulationRetrieval) {
return FormulationRetrieval.getFormulation('./data/formulations/bs.formulation.json');
},
analysisConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getAnalysisConfig(analysisData);
},
maufConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getMaufConfig(analysisData);
},
tableConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getTableConfig(analysisData);
},
graphConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getGraphConfig(analysisData);
},
mapConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getMapConfig(analysisData);
}
}
};
var analyze_layout_state = {
abstract: false,
url: '',
views: {
'graph': {
templateUrl: 'modules/analyze/views/analyze.graph.client.view.html',
controller: 'GraphViewController'
},
'map': {
templateUrl: 'modules/analyze/views/analyze.map.client.view.html',
controller: 'MapViewController'
},
'filters': {
templateUrl: 'modules/analyze/views/analyze.filters.client.view.html',
controller: 'FiltersViewController',
controllerAs: 'filters'
},
'datatable': {
templateUrl: 'modules/analyze/views/analyze.datatable.client.view.html',
controller: 'DatatableViewController',
controllerAs: 'datatable'
}
}
};
// Populate provider.
$stateProvider
.state('analyze', analyze_state)
.state('analyze.layout', analyze_layout_state);
}
})();
And here is the controller receiving the injected data:
(function() {
'use strict';
angular
.module('analyze')
.controller('AnalyzeViewController', AnalyzeViewController);
AnalyzeViewController.$inject = ['$rootScope', '$scope', '$state', '$location', 'Authentication', 'httpq', 'analysisData', 'maufConfig', 'tableConfig', 'graphConfig', 'mapConfig'];
function AnalyzeViewController($rootScope, $scope, $state, $location, Authentication, $httpq, analysisData, analysisConfig, maufConfig, tableConfig, graphConfig, mapConfig) {
// This provides Authentication context.
$scope.authentication = Authentication;
$scope.currentRoute = 'Analyze';
// console.log($scope.currentRoute);
// ALL OF THESE ARE INJECTED AND APPEAR TO BE CORRECT IN CONSOLE.
// console.log(analysisData);
// console.log(analysisConfig);
// console.log(maufConfig);
// console.log(tableConfig);
// console.log(graphConfig);
// console.log(mapConfig);
// NOTE: At one point I was loading the data through promises inside the
//controller, but moved it into the state resolve for better SOC.
//Strangely the $broadcast of the exact same value done here in the finally()
//block of the $httpq method works - using the new injected data object!
//And yet, the same $broadcast on $stateChangeSuccess (which DOES send the correct
//data into the listening subscribers if I only send the entire object) sends only
//empty string if I specify the array.
// Manual data loading.
$scope.sourceFile_A = './data/BSGAM_Heads_Wells_Drains_Zones_Master.csv';
$httpq.get($scope.sourceFile_A)
.then(function(data) {
// ...removed because not used.
})
.catch(function(data, status) {
console.error('Load error', response.status, response.data);
})
.finally(function() {
// Works here using the injected resolved promise, does not work in stateChangeSuccess... WTH??
$scope.$broadcast('analysisDataLoaded', analysisData.datagridConfig.datasources.tabledata.datum);
});
$scope.$on('$stateChangeSuccess', function() {
// EXACT SAME BROADCAST AS ABOVE FAILS HERE - EMPTY STRING.
// $scope.$broadcast('analysisDataLoaded', analysisData.datagridConfig.datasources.tabledata.datum);
});
// extra code removed...
})();
Lastly the Nested View Controller (one of 4) that I am trying to get to use the data via the $broadcast from the abstract parent. I can also access all the injected objects here directly without the broadcast but I am trying to decouple as much as possible as several views need to update in sync from the broadcast.
(function() {
'use strict';
angular
.module('analyze')
.controller('DatatableViewController', DatatableViewController);
DatatableViewController.$inject = ['$scope', 'Authentication', '$filter', 'ngTableParams', 'AnalysisDataFactory', 'tableConfig'];
function DatatableViewController($scope, Authentication, $filter, ngTableParams, AnalysisDataFactory, tableConfig) {
// This provides Authentication context.
$scope.authentication = Authentication;
// Expose public API.
$scope.headerFilter = headerFilter;
$scope.datasetOrder = datasetOrder;
$scope.rowClicked = rowClicked;
$scope.decorateSiblings = decorateSiblings;
$scope.clearSiblings = clearSiblings;
$scope.updateView = updateView;
// Private members.
$scope.headerdata = [];
$scope.tabledata = [];
$scope.suf01 = 0;
$scope.suf02 = 0;
$scope.suf03 = 0;
$scope.muf = 0;
$scope.$on('analysisDataLoaded', function(event, args) {
console.log('analysisDataLoaded...', event, args);
$scope.updateView(args);
// THIS IS WHAT I WANT TO DO INSTEAD:
// $scope.updateView(tableConfig); // Not working yet.
});
function headerFilter(target) {
return target.visible;
}
function datasetOrder(key) {
angular.forEach($scope.headers, function(target) {
// console.log('key='+key);
if (target.data == key) {
if (target.visible) {
return target.order;
}
}
});
return -1;
}
function rowClicked(target) {
for (var key in target) {
if (target.hasOwnProperty(key)) {
console.log(key + ' -> ' + target[key]);
}
}
}
function decorateSiblings(target) {
// console.log('data row touched, sending emission.');
$scope.$emit('currentDatatableTarget', target);
}
function clearSiblings(target) {
// console.log('datarow cleared, sending all clear.');
$scope.$emit('clearDatatableTarget', target);
}
function updateView(data) {
// ngTable
$scope.dataTable = new ngTableParams({
page: 1,
count: 10
}, {
total: $scope.tabledata.length,
counts: [10, 25, 50, 100, 250],
defaultSort: 'asc',
getData: function($defer, params) {
$scope.data = params.sorting() ? $filter('orderBy')($scope.tabledata, params.orderBy()) : $scope.tabledata;
$scope.data = params.filter() ? $filter('filter')(data, params.filter()) : data;
$scope.data = data.slice((params.page() - 1) * params.count(), params.page() * params.count());
$defer.resolve($scope.data);
}
});
}
}
})();
Something simple in here - probably a JS gotcha that I am overlooking.... Thanks in advance for any input!!
Turns out it was from not properly deferring and resolving the promises before trying to bind to them. I still have not identified exactly where that timing is going wrong in my code, but I can now intermittently load the data as I intended and can see that it is clearly a function of async inconsistencies. The $broadcast event is working just fine (when the async data loads properly).
i'm trying to resolve more than one service to my controller, and the 2nd service is dependant on the 1st resolving first as it requires some data to be included to make the request.
Below is what I would like to do, and how I think it ** should ** work, however, I can't seem to access the data returned in the 1st resolveData request.
Any suggestions or ideas would be greatly appreciated
.when('/maps/:id', {
templateUrl: 'partials/maps/view',
controller: 'MapViewCtrl',
authenticate: true,
resolve: {
resolveData: function ($route, MapService) {
var data = MapService.showfull({id: $route.current.params.id});
return data.$promise;
},
resolveMoreData: function($route, Service, resolveData){
var returnData = Service.get({id: resolveData.id});
return returnData.$promise;
}
}
})
The values resolved in a route definition cannot be dependend on each other. They are intended to be used by the controller for that route.
See this part of the $routeProvider source for a reference:
function updateRoute() {
// ...
var locals = angular.extend({}, next.resolve);
angular.forEach(locals, function(value, key) {
locals[key] = angular.isString(value) ?
$injector.get(value) : $injector.invoke(value, null, null, key);
});
// ...
// Here, we use $q.all(), which converts array of the promises
// into a single promise. That input array contains independent
// promises.
return $q.all(locals);
}
You could fix that in the couple of ways:
Move the resolveMoreData logic into the controller
Create a single dependency (either as a resolved dependency, or a service) which would combine those two promises into one.
The second option could look like:
resolve: {
data: function ($route, MapService, Service) {
var deferred = $q.defer();
MapService
.showfull({id: $route.current.params.id})
.then(function success(data) {
return Service.get({id: data.id});
}, function error(reason) {
deferred.reject(reason);
})
.then(function success(data) {
deferred.resolve(data);
}, function error(reason) {
deferred.reject(reason);
});
return deferred.promise;
}
}
(note the code above is an example only, I haven't ran it).
you should inject the dependency in a brackets [] or using the comment
/* #ngInject */
.when('/maps/:id', {
templateUrl: 'partials/maps/view',
controller: 'MapViewCtrl',
authenticate: true,
resolve: {
resolveData: function ($route, MapService) {
var data = MapService.showfull({id: $route.current.params.id});
return data.$promise;
},
resolveMoreData: ['$route', 'Service','resolveData', function($route, Service, resolveData){
var returnData = Service.get({id: resolveData.id});
return returnData.$promise;
}]
}
})