Issues with client-side caching of lookups in Angular + Breeze project - angularjs

We have a project based on John Papa's HotTowel SPA Using Angular, Breeze and UI-Bootstrap.
We are running in to problems using clientside caching to load Drop downs from SQL Lookup tables. The view loads before the data is cached resulting in empty drop downs when the view first loads. If we refresh the view, the drops downs then populate. We realize that this is sequencing and routing issue but cannot figure out how to make it work.
The issue seems to center around the use of promises and how to recognize when they return successfully. We use the prime function to load the cached data from the database at startup but the search view is loaded before the data is accessible in the getActionDomain() function.
We would appreciate any pointers or ideas.
Thanks,
JG
app.run within app.js is the starting point
(function () {
'use strict';
var serviceId = 'app';
var app = angular.module('app', [
// Angular modules
'ngAnimate', // animations
'ngRoute', // routing
'ngSanitize', // sanitizes html bindings (ex: sidebar.js)
// Custom modules
'common', // common functions, logger, spinner
//'common.bootstrap', // bootstrap dialog wrapper functions
// 3rd Party Modules
'ui.bootstrap' // ui-bootstrap (ex: carousel, pagination, dialog)
]);
// Handle routing errors and success events
app.run(['$route', '$rootScope', '$location', '$http', 'Auth', 'datacontext', 'common', function ($route, $rootScope, $location, $http, Auth, datacontext, common) {
var getLogFn = common.logger.getLogFn;
var log = getLogFn(serviceId);
var logError = getLogFn(serviceId, 'error');
var logSuccess = getLogFn(serviceId, 'success');
var $q = common.$q;
//breeze.core.extendQ($rootScope, $q);
primeData();
function primeData() {
return datacontext.prime()
.then(startRouting)
.then(querySucceeded, _queryFailed, null);
function querySucceeded(data) {
log('Retrieved [Lookups] from remote data source', data, true);
return true;
}
}
function startRouting() {
$rootScope.$on('$routeChangeStart', function (event, next, current) {
$rootScope.error = null;
if ($rootScope.user) {
return true;
} else {
$rootScope.user = {};
var defered = $q.defer();
checkRouting($q, $rootScope, $location);
return defered.promise;
}
});
var checkRouting = function ($q, $rootScope, $location) {
var defered = $q.defer();
Auth.getCurrentUser()
.then(function (data) {
$rootScope.user.isInUserGroup = data.data.IsInUserGroup;
$rootScope.user.firstName = data.data.FirstName.replace(/\"/g, "");
$rootScope.user.lastName = data.data.LastName.replace(/\"/g, "");
$rootScope.user.userName = data.data.UserName.replace(/\"/g, "");
});
return defered.promise;
};
}
function _queryFailed(error) {
var msg = config.appErrorPrefix + 'Error priming data.' + error.message;
logError(msg, error);
throw error;
}
}]);
})();
The prime function is found in the datacontext.js module:
function prime() {
if (primePromise) return primePromise;
var deferred = $q.defer();
primePromise = $q.all([getLookupLists()])
.then(extendMetadata)
.then(success);
function success() {
setLookups();
dataPrimed = true;
//apps.startRouting();
log('Primed the data');
};
function extendMetadata() {
var metadataStore = manager.metadataStore;
var types = metadataStore.getEntityTypes();
types.forEach(function (type) {
if (type instanceof breeze.EntityType) {
set(type.shortName, type);
}
});
function set(resourceName, entityName) {
metadataStore.setEntityTypeForResourceName(resourceName, entityName);
}
}
deferred.promise = primePromise;
return deferred.promise;
}
function setLookups() {
service.lookupCachedData = {
actions: _getAllLocal('ActionDomain', 'sortorder'),
statusCodes: _getAllLocal('StatusDomain', 'sortorder')
}
}
function _getAllLocal(resource, ordering) {
return EntityQuery.from(resource)
.orderBy(ordering)
.using(manager)
.executeLocally();
}
function getLookupLists() {
return EntityQuery.from('Lookups')
.using(manager).execute()
.then(querySucceeded, _queryFailed);
function querySucceeded(data) {
log('Retrieved [Lookups] from remote data source', data, false);
return true;
}
}
The code is called in the search.js view controller module
function activate() {
common.activateController([getActionDomain(), getStatusDomain(), getCpuLog()], controllerId)
.then(function () { log('Activated search View', null, false); });
}
function getActionDomain() {
if (datacontext.lookupCachedData && datacontext.lookupCachedData.actions) {
vm.actions.push({ value: 0 });
datacontext.lookupCachedData.actions.forEach(function (actionItem) {
vm.actions.push(actionItem);
})
}
}
function getStatusDomain() {
if (datacontext.lookupCachedData && datacontext.lookupCachedData.statusCodes) {
vm.statusList.push({ value: 0 });
datacontext.lookupCachedData.statusCodes.forEach(function (statusItem) {
vm.statusList.push(statusItem);
})
}
}

If you want to wait that the promise is resolved before display the view you can use the resolve property in your service $routeProvider when you configure the app module.
There an example:
$routeProvider.when('/staff_profil/edit', {
templateUrl: 'app/app/assets/partials/profil-edit.html',
controller: 'ProfilEditController'
resolve: {
'currentUser':function( UserService){
return UserService.getCurrentUser();
}
});
You have to return a promise in the resolve!!
In this example, I wait to get the currentUser before display my profil-edit page. The UserService.getCurrentUser() is a promise create by the $http service in my UserService.
Moreover you can use this promise resolve in my controller ProfilEditController by injecte the 'currentUser' in my controller like if it is a service and then you can use currentUser.name
in your controller and view.
I hope this will help you!

Related

Angular Service doesn't return value

Im using angularjs in MVC.
Here My Controller.js:
//Get User By ID
$scope.GetUserById = function (UID) {
var Get = UserService.GetUserById(UID);
Get.then(function (response) {
$scope.User = response.data;
alert($scope.User.Address);
});
};
services.js:
//Get By ID
this.GetUserById = function (UID) {
debugger;
return $http.get("../api/UsersData/GetUserById?UID=" + UID);
};
When I'm using the debugger, the alert message is displayed. If I'm not debugging then it doesn't return a value.
What is the issue in my code?
How to display the value to html page?
You should get it working with few adjustments.
Service.js:
angular.module('myApp').factory('MyService', MyService);
//Avoid Minification Problems
MyService.$inject = [ '$http' ];
function MyService( $http ){
function GetUserById(UID){
return $http.get('../api/UsersData/GetUserById?UID=' + UID)
.then( function (response) {
if(response){
return response;
}
});
}
//Expose Method to External Calls
return {
GetUserById : GetUserById
}
}
Controller.js:
angular.module('myApp').controller('MyController', MyController);
MyController.$inject = [ '$scope', 'MyService' ];
function MyController( $scope, MyService ){
$scope.GetUserById = function(UID){
MyService.GetUserById(UID).then( function(response){
$scope.User = response.data;
alert($scope.User.Address);
});
}
}
Make sure what response is actually returning with a $log or using console.log in order to properly alert the address. Do this check also in the service, for instance you should check if response.address exists.
You can also use a Service instead of a Factory.

How to sync controller with Service

I'm testing the communication of angular and SQLite. I need to get the ID and NAME of the selected company from database when the User access the page. I'm using the ion-autcomplete to select the company in the CRUD page.
Service: sqlite.js
(function () {
'use strict';
angular
.module('Test')
.service('$sqliteService', $sqliteService);
$sqliteService.$inject = ['$q', '$cordovaSQLite'];
function $sqliteService($q, $cordovaSQLite) {
var self = this;
var _db;
self.db = function () {
if (!_db) {
if (window.sqlitePlugin !== undefined) {
_db = window.sqlitePlugin.openDatabase({ name: "my.db", location: 2, createFromLocation: 1 });
} else {
// For debugging in the browser
_db = window.openDatabase("my.db", "1", "Database", 200000);
}
}
return _db;
};
self.getFirstItem = function (query, parameters) {
var deferred = $q.defer();
self.executeSql(query, parameters).then(function (res) {
if (res.rows.length > 0)
return deferred.resolve(res.rows.item(0));
else
return deferred.reject("There aren't items matching");
}, function (err) {
return deferred.reject(err);
});
return deferred.promise;
};
}
})();
Factory: CompanyService.js
(function () {
'use strict';
angular
.module('Test')
.factory('CompanyService', CompanyService);
CompanyService.$inject = ['$q', '$sqliteService'];
function CompanyService($q, $sqliteService) {
return {
getId: function (Id) {
var query = "Select * FROM Company WHERE ID = ?";
var values = [Id];
return $q.when($sqliteService.getFirstItem(query, values));
}
};
}
})();
Controller: CompanyController.js
(function() {
'use strict';
angular
.module('Test')
.controller('CompanyEditController', CompanyEditController);
CompanyEditController.$inject = ['$scope', '$q', '$stateParams', '$state', '$cordovaCamera', '$cordovaImagePicker', '$ionicPopup', 'CompanyService'];
function OcorrenciaEditController($scope, $q, $stateParams , $state, $cordovaCamera, $cordovaImagePicker, $ionicPopup, CompanyService) {
var vm = $scope;
vm.modelToItemMethod = function (modelValue) {
var d = $q.defer();
CompanyService.getId(modelValue)
.then(function(data) {
console.log('My first promise succeeded', JSON.stringify(data));
$q.resolve(data);
}, function(error) {
console.log('My first promise failed', error.message);
});
return d.promise;
};
})();
Company.html
<input ion-autocomplete ng-model="company.IdCompany" type="text" name="fieldEmpresa" placeholder="Empresa" readonly="readonly" class="ion-autocomplete" autocomplete="off" max-selected-items="1" required
item-value-key="Id"
item-view-value-key="CompanyName"
items-method="getTestItems(query)"
cancel-label="Cancel"
items-removed-method="itemsRemoved()"
loader-icon="spinner"
external-model="company"
model-to-item-method="modelToItemMethod(modelValue)"/>
I don't undestand why I need to use de "$q.defer" inside the controller if i'm using inside de Factory and Service. If I don't use, controller can't return the value to ion-aucomplete. Am i missing something? Or the code is right?
You are binding this method to auto complete; as ajax call is asynchronous, you gotta return a primise. Thats the reason why you ended up using $q.defer.
If you dont want to use $q, then instead of using $q.defer , you can just do return CompanyService.getId(modalValue); in your VM.modelToItemMethod which inturn returns a deferred object.

Send path from controller to a service which will return a promise back to controller

I have a service which get data from a file(path is given by the controller) and return a promise - then another service that create a object with properties using the returned data from the last service.
My problems are:
The getDataService runs before controller so it has no path from which to fetch data => nothing in return(an error)
Provider 'GetDataService' must return a value from $get factory method.
I need to keep this structure because I'll have more controllers with different paths to give
I'm also opened to other solutions but I need to make sure that datas are loaded before the template get populated. I've tried to call SetProperties service first with getData service into it - but still getData.js is executed first
getdata Service
angular.module('myApp').factory('GetDataService',['$http', function($http) {
var getData = function(path){
return $http.get(path).then(function(result) {
return result.data;
});
};
}]);
setProperties service
angular.module('myApp').service('PageProperties',['$http', function($http) {
this.setProps = function(page, data) {
some code here
var properties = {
isCenterActive : isActive_val,
//header elements
titleClass : page,
title : data.titles[page],
//footer elements
leftLink : leftLink_val,
leftFooterClass: leftLink_val,
leftTitle: data.titles[leftLink_val],
centerLink : centerLink_val,
centerFooterClass: data.titles[centerLink_val],
centerTitle : centerTitle_val,
rightLink : rightLink_val,
rightFooterClass: rightLink_val ,
rightTitle : data.titles[rightLink_val],
}
return properties;
}
}]);
controller
angular.module('myApp', [])
.controller('meniuController', ['$http', '$stateParams', '$scope', 'GetDataService', 'PageProperties',
function($http, $stateParams, $scope, GetDataService, PageProperties){
var page = "meniu";
$scope.language = $stateParams.lang;
var path = '_global/views/services/json/' + $stateParams.lang + '_data.json';
/*PageProperties.setProps(page, path).then(function(data){
//some code here
});*/
GetDataService.getData(path).then(function(data){
$scope.props = PageProperties.setProps(page, data);
}).catch(function(){
$scope.error = 'Unable to get data';
});
}])
Thanks in advance!!
The error says your GetDataService provider (defined as factory) doesn't return anything
angular.module('myApp').factory('GetDataService',['$http', function($http) {
var getData = function(path){
return $http.get(path).then(function(result) {
return result.data;
});
};
// you need to actually return something
return { getData: getData };
}]);
Then you can make your PageProperties use GetDataService
angular
.module('myApp')
.service('PageProperties',['GetDataService', function(GetDataService) {
this.getProperties = function(path) {
return GetDataService.getData(path).then(/*transform here*/)
}

Initiate a service and inject it to all controllers

I'm using Facebook connect to login my clients.
I want to know if the user is logged in or not.
For that i use a service that checks the user's status.
My Service:
angular.module('angularFacebbokApp')
.service('myService', function myService($q, Facebook) {
return {
getFacebookStatus: function() {
var deferral = $q.defer();
deferral.resolve(Facebook.getLoginStatus(function(response) {
console.log(response);
status: response.status;
}));
return deferral.promise;
}
}
});
I use a promise to get the results and then i use the $q.when() to do additional stuff.
angular.module('angularFacebbokApp')
.controller('MainCtrl', function ($scope, $q, myService) {
console.log(myService);
$q.when(myService.getFacebookStatus())
.then(function(results) {
$scope.test = results.status;
});
});
My problem is that i need to use the $q.when in every controller.
Is there a way to get around it? So i can just inject the status to the controller?
I understand i can use the resolve if i use routes, but i don't find it the best solution.
There is no need to use $q.defer() and $q.when() at all, since the Facebook.getLoginStatus() already return a promise.
Your service could be simpified like this:
.service('myService', function myService(Facebook) {
return {
getFacebookStatus: function() {
return Facebook.getLoginStatus();
}
}
});
And in your controller:
.controller('MainCtrl', function ($scope, myService) {
myService.getFacebookStatus().then(function(results) {
$scope.test = results.status;
});
});
Hope this helps.
As services in angularjs are singleton you can create new var status to cache facebook response. After that before you make new call to Facebook from your controller you can check if user is logged in or not checking myService.status
SERVICE
angular.module('angularFacebbokApp')
.service('myService', function myService($q, Facebook) {
var _status = {};
function _getFacebookStatus() {
var deferral = $q.defer();
deferral.resolve(Facebook.getLoginStatus(function(response) {
console.log(response);
_status = response.status;
}));
return deferral.promise;
}
return {
status: _status,
getFacebookStatus: _getFacebookStatus
}
});
CONTROLLER
angular.module('angularFacebbokApp')
.controller('MainCtrl', function ($scope, $q, myService) {
console.log(myService);
//not sure how do exactly check if user is logged
if (!myService.status.islogged )
{
$q.when(myService.getFacebookStatus())
.then(function(results) {
$scope.test = results.status;
});
}
//user is logged in
else
{
$scope.test = myService.status;
}
});

Load views after services in angularjs

In my angular app I have a view, a controller and a service.
The service load resources ex:service load persons and initialize value with the result.
I want to load my view after my service finish his function to load his resources.
var myApp = angular.module('myApp',[]);
myApp.controller('PersonsCtrl', ($scope, Persons) {
$scope.persons = Persons.data;
});
myApp.factory('Persons', {
data: [[function that load resources => take time]]
});
So I want to load my controller when my service finish his initialization.
Some ideas?
Assuming you have a route provider, here's a basic example. When the promise is resolved, "personData" will be injected into your controller. There's not much info about what your service does, so I had to give something very generic.
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/persons', {
controller: 'PersonsCtrl',
templateUrl: 'persons.html',
resolve: {
personData: ['Persons', function(Persons) {
return Persons.getData();
}]
}
});
}]);
myApp.controller('PersonsCtrl', ($scope, personData) {
$scope.persons = personData;
});
myApp.factory('Persons', {
getData: function() {//function that returns a promise (like from $http or $q)};
});
Maybe try using promises, example below
var myApp = angular.module('myApp',[]);
myApp.controller('PersonsCtrl', ($scope, Persons) {
$scope.persons = Persons.getData().then(function(response){
//do whatever you want with response
});
});
myApp.factory('Persons', function ($http, $q) {
return {
getData: function () {
var def = $q.defer();
$http.get('url').
success(function (response) {
def.resolve(response);
})
return def.promise();
}
}
});

Resources