Calling a rest service only once in angularjs directive - angularjs

We have menu's that can be displayed based on the role. I made the directive for this feature. But the problem is everytime I call the directive its calling rest service. I want to be able to call it only once and save the features that can be accessed for the current role and decide either hide it or show. Below is currently what I have:
var app = angular.module('myApp', []);
app.service('authService', function(){
var user = {};
user.role = 'guest';
return{
getFeature: function(){
//rest service call
return featureList;
},
}
});
app.directive('restrict', function(authService){
return{
restrict: 'A',
prioriry: 100000,
scope: false,
compile: function(element, attr, linker){
var accessDenied = true;
var featureList = authService.getFeature();
featureList.then(function(result){
featureList = result;
var attributes = attr.access.split(" ");
for(var i in featureList){
if(featureList[i] == attributes[0]){
accessDenied = false;
}
}
if(accessDenied){
element.children().remove();
element.remove();
}
});
}
}
});
Is there anyway I can call rest service only once not with every directive call?

One possibility is to cache the result from the service call, like so
app.service('authService', function($q) {
var user = {};
var cache;
user.role = 'guest';
return {
getFeature: function() {
if (cache) {
return $q.when(cache);
} else {
//rest service call
return featureList.then(function(val) {
cache = val;
return val;
});
}
}
}
});
You'll only make the call once (as long as the result is truthy), after that you'll just be returning the cache.
Might want to consider how to invalidate the cache at some point, depending on the apps needs.
Edit
As per comments, previous solution would allow multiple calls while waiting for the promise to resolve.
This approach will always return one singular promise, which itself does the single rest call.
app.service('authService', function($http) {
var user = {};
var prom;
user.role = 'guest';
return {
getFeature: function() {
if (!prom) {
prom = $http.get(/rest/).then(function(result) {
return result.data;
});
}
return prom;
}
}
});

Related

How to update angularjs.module.value with array and retrieving it through service?

I'm trying to update a global angularjs module.value in one controller with an array, and then retrieving that global array through in a service. But the array doesn't exist.
app.js
app.factory('featureClaims', function($q) {
var featureClaims = {};
featureClaims.init = function() {
featureClaims.claims = [];
}
featureClaims.get = function() {
return $q.when(featureClaims.claims);
}
featureClaims.set = function(data) {
featureClaims.claims = data;
return $q.when(featureClaims.claims); // I'm using the $q library to return a promise.
}
return featureClaims;
});
loginController
let loginController = function($scope, loginService, toastrObj, featureClaims) {
$scope.login = function(){
featureClaims.init();
featureClaims.set(result.data.FeatureClaims); // updating ok here
}
}
app.controller("loginController", ["$scope", 'loginService', 'toastrObj', 'featureClaims',loginController]);
home service
let homeService= function(featureClaims) { // featureClaims.claims is null
return{
validateUser: function(expectedClaim) {
if(expectedClaim !== ""){
featureClaims.get().then(function(data){
return data.includes(expectedClaim); // data is return as undefined
})
}
return false;
}
}
};
app.factory('homeService',['featureClaims', homeService]);
I don't think you can use a value service this way. From this post the author states: "Note: Make sure that you never overwrite the value service/object as a whole otherwise your assignment is lost. Always reassign the property values of the value object. The following assignment is wrong and does not lead to the expected behavior"
Instead why don't you convert your value to a factory like so:
app.factory('featureClaims', function($q) {
var featureClaims = {};
featureClaims.init = function() {
featureClaims.claims = [];
}
featureClaims.get = function() {
return $q.when(featureClaims.claims);
}
featureClaims.set = function(data) {
featureClaims.claims = data;
return $q.when(featureClaims.claims); // I'm using the $q library to return a promise.
}
return featureClaims;
});
In your controller:
featureClaims.init();
// you need to wait for the promise to resolve with 'then'
featureClaims.set(['foo', 'bar']).then(function(response) {
console.log(response); // logs ["foo", "bar"]
});
featureClaims.get().then(function(response) {
console.log(response); // logs ["foo", "bar"]
});
Tested and working. You will want to create a get method that simply returns the data instead of setting it first.

Reuse data from $http in factory

I understand that the appropriate method to share data between controllers in Angular.js is by using Factories or Services.
app.controller('Controller1', function($scope, DataService) {
DataService.getValues().then(
function(results) {
// on success
console.log('getValues onsuccess');
});
});
app.controller('Controller2', function($scope, DataService) {
DataService.getValues().then(
function(results) {
// on success
console.log('getValues onsuccess');
});
});
app.factory('DataService', function($http) {
var getValues = function() {
console.log('making http request');
return $http.get("/api/getValues");
};
return {
getValues: getValues
}
});
I have two controllers calling the same method in a factory twice
and this is perfectly fine and everything is working as it should. My only concer is that it seems a bit unecessary to make the same request twice? Would the use of $broadcast be a better approach?
Or could i structure my code differenty so that the service is called only once?
You could store the results of the request in the factory and retrieve those instead.
app.factory('DataService', function($http) {
var values;
var requestValues = function() {
console.log('making http request');
$http.get("/api/getValues").then(
function(results){
values = results;
});
};
var getValues = function() {
return values;
};
return {
requestValues : requestValues,
getValues: getValues
}
});
If your data is somekind of static and may not change very often over time you could do something like:
app.factory('DataService', function($http) {
self = this;
this.isLoaded = false;
this.results;
this.getValues = function() {
console.log('making http request');
$http.get("/api/getValues").then(
function(results) {
// on success
console.log('getValues onsuccess');
self.isLoaded = true
this.results = results;
return results;
})
);
};
})
And in the controller:
app.controller('Controller2', function($scope, DataService) {
if(!DataService.isLoaded){
results = DataService.getValues()
}else{
results = DataService.results;
}
});
You should consider caching in your DataService. Add a variable to hold the result from the http service and a time-stamp variable to store the time it was retrieved.
If a second call to the service is within a preset time period (lets say, 5 seconds), then http call is not made and data from the cache is returned.
app.factory('DataService', function($http) {
var cachedValue = null;
var lastGet = null;
var getValues = function() {
var timeNow = new Date();
if (cachedValue == null || ((timeNow - lastGet) < 5000)) {
console.log('making http request');
lastGet = timeNow;
cachedValue = $http.get("/api/getValues");
} else console.log('returning cached value');
return cachedValue;
};
return {
getValues: getValues
}
});

How can I invoke a function after two or more $scope events have been received?

For example, let's assume I need to run a function after receiving two events "eventA" and "eventB". What I usually do is to declare for each event a boolean variable, set the variable to true when the event is received, and ask if the other variable is true to run the function:
var a = false,
b = false;
$scope.$on("eventA", function(){
a = true;
if (b)
performTask();
});
$scope.$on("eventB", function(){
b = true;
if (a)
performTask();
});
var performTask = function() {
/* do something... */
};
This gets more complex if there are three or more events. Is there a design pattern to handle these cases?
You can use $q promises.
var dfdATask= $q.defer();
var dfdBTask= $q.defer();
$scope.$on("eventA", function(){
// whatever this function does
dfdATask.resolve(true);//or pass a value
});
$scope.$on("eventB", function(){
//whatever this function does
dfdBTask.resolve(true);//or pass a value
});
$q.all([dfdATask.promise, dfdBTask.promise]).then(function(){
//be sure to pass in an array of promises
//perform task
})
So theory wise if you only want to execute this magical action after you've received these two events have been called at least once then you probably want to use promises.
app.controller('ExampleOneController', [
'$log',
'$scope',
'$q',
'$rootScope',
function ($log, $scope, $q, $rootScope) {
$scope.anotherAction1FiredCount = 0;
var aDeferred = $q.defer(),
bDeferred = $q.defer();
$scope.$on('e-1-a', function () {
$log.log('Fired e-1-a');
aDeferred.resolve();
});
$scope.$on('e-1-b', function () {
$log.log('Fired e-1-b');
bDeferred.resolve();
});
$q.all([aDeferred.promise, bDeferred.promise]).then(function () {
$log.log('Fired another action 1!');
$scope.anotherAction1 = 'Hello World 1!';
$scope.anotherAction1FiredCount++;
});
}
]);
That being said usually I want to execute everytime two things happen so I tend to 'reset' my promises.
app.controller('ExampleTwoController', [
'$log',
'$scope',
'$q',
function ($log, $scope, $q) {
$scope.anotherAction2FiredCount = 0;
var aDeferred = $q.defer(),
bDeferred = $q.defer();
$scope.$on('e-2-a', function () {
$log.log('Fired e-2-a');
aDeferred.resolve();
});
$scope.$on('e-2-b', function () {
$log.log('Fired e-2-b');
bDeferred.resolve();
});
var wait = function () {
$q.all([aDeferred.promise, bDeferred.promise]).then(function () {
$log.log('Fired another action 2!');
$scope.anotherAction2 = 'Hello World 2!';
$scope.anotherAction2FiredCount++;
aDeferred = $q.defer();
bDeferred = $q.defer();
wait();
});
};
wait();
}
]);
Here's the working plunker!
Promises are life.
active polling using $scope.$watch:
One way to do this:
var a = false, b = false;
$scope.$on("eventA", function(){ a = true; });
$scope.$on("eventB", function(){ b = true; });
$scope.$watch(
function() { return a && b; },
function(newval, oldval) {
if (newval) { performTask(); }
}
);
one step further:
var events = { a: false, b: false };
$scope.$on("eventA", function(){ events.a = true; });
$scope.$on("eventB", function(){ events.b = true; });
$scope.$watch(
function() {
var result = true;
for (var key in events) {
result = result && events[key];
}
return result;
},
function(newval, oldval) {
if (newval) { performTask(); }
}
);
http://plnkr.co/edit/5NrOhTwblMCCCoKncVAW?p=preview
Be sure to read the developer guide and check the "Scope $watch Performance Considerations" section.
regular callback:
var events = { a: false, b: false };
function checkIfPerfomTask() {
for (var key in events) {
if (!events[key]) { return; }
}
performTask();
}
$scope.$on("eventA", function(){ events.a = true; checkIfPerfomTask(); });
$scope.$on("eventB", function(){ events.b = true; checkIfPerfomTask(); });
http://plnkr.co/edit/5NrOhTwblMCCCoKncVAW?p=preview
with one promise, $q.defer():
var events = { a: false, b: false };
var shouldPerform = $q.defer();
function checkIfPerfomTask() {
for (var key in events) {
if (!events[key]) { return; }
}
shouldPerform.resolve();
}
$scope.$on("eventA", function(){ events.a = true; checkIfPerfomTask(); });
$scope.$on("eventB", function(){ events.b = true; checkIfPerfomTask(); });
shouldPerform.promise.then(performTask);
http://plnkr.co/edit/5NrOhTwblMCCCoKncVAW?p=preview
with multiple promises...
Already been covered by multiple answers.
Promises are meant for your use case. But since you mentioned you were looking for a design pattern, I'll put down one way to do this using the Observer pattern.
You can check out this live Plunkr: http://plnkr.co/edit/1Oqn2TAGTr7NLYZd9ax1?p=preview
Has an angularjs service that handles the logic for tracking events and calling the final action.
The controller simply defines your events, the final event and registers them with your service.
app.controller('MainCtrl', function($scope, EventService) {
var events = [];
... //define events
EventService.registerEvents(events);
EventService.registerEventsCallback(finalEvent); //the observer
});
The service makes this work by removing a called event from the events list upon first execution.
app.factory('EventService', function(){
var events = [];
var finalEvent;
var eventsCallback = function(){
if(!events.length){
finalEvent();
}
}
var resolveEvent= function(event){
var eventIndex = events.indexOf(event);
if(eventIndex>=0){
events.splice(eventIndex,1);
}
}
return{
registerEvents: function(eventsList){
events = angular.copy(eventsList);
},
registerEventsCallback: function(event){
finalEvent = event;
},
publishEvent: function(event){
event();
resolveEvent(event);
eventsCallback();
}
}
});

Jasmine + AngularJS: Global services causes Unexpected GET request

I have a service, $language, that gets called in app config (so before every Spec runs). The method called, $language.update(), triggers $translate.use() (which in turn triggers an $http.get()). This causes an Unexpected request: GET /<lang>/i18n.
I've tried a few different things to resolve this, but each seems to cause a new problem:
Globally mock the $translate service
// not inside a describe()
beforeEach(function() {
module(function($provide) {
$provide.value('$translate', {
get: function() { return false; },
storage: function() { return false; },
storageKey: function() {
return {
get: function() { return false; },
set: function() { return false; }
};
},
use: function() { return false; }
});
});
});
But something tries to call $translate(), so I tried making the mock a function returning an object, but that didn't work either.
Mocking the GET request via $httpBackend
// not inside a describe()
beforeEach(function() {
// this already existed to avoid another problem caused by $translate
module('MyApp', function config($translateProvider, $anotherProvider) {
// …
});
// new
inject(function($httpBackend) {
$httpBackend.when('GET', '/<lang>/i18n').respond({});
});
});
But then it complains Injector already created, can not register a module! (order of module and inject doesn't seem to matter).
I thought of globally mocking my $language service, but then I would not be able to test it in its own Spec.
Ideally I'd prefer to globally mock $translate as it seems to cause one problem after another.
The problem was that $translate is a provider; therefore a provider needs to be $provide'd:
// Outside of a describe so it's treated as global
beforeEach(function() {
module('MyModule', function config($providerA, $provide) {
// …
$provide.provider('$translate', function() {
var store = {};
this.get = function() { return false; };
this.preferredLanguage = function() { return false; };
this.storage = function() { return false; };
this.translations = function() { return {}; };
this.$get = ['$q', function($q) {
var $translate = function(key) {
var deferred = $q.defer(); deferred.resolve(key); return deferred.promise;
};
$translate.addPair = function(key, val) { store[key] = val; };
$translate.isPostCompilingEnabled = function() { return false; };
$translate.preferredLanguage = function() { return false; };
$translate.storage = function() { return false; };
$translate.storageKey = function() { return true; };
$translate.use = function() { return false; };
return $translate;
}];
});
});
});

Angular: Rewriting function to use promise

I'm using an Angular factory that retrieves data from a feed and does some data manipulation on it.
I'd like to block my app from rendering the first view until this data preparation is done. My understanding is that I need to use promises for this, and then in a controller use .then to call functions that can be run as soon as the promise resolves.
From looking at examples I'm finding it very difficult to implement a promise in my factory. Specifically I'm not sure where to put the defers and resolves. Could anyone weigh in on what would be the best way to implement one?
Here is my working factory without promise:
angular.module('MyApp.DataHandler', []) // So Modular, much name
.factory('DataHandler', function ($rootScope, $state, StorageHandler) {
var obj = {
InitData : function() {
StorageHandler.defaultConfig = {clientName:'test_feed'};
StorageHandler.prepData = function(data) {
var i = 0;
var maps = StorageHandler.dataMap;
i = data.line_up.length;
while(i--) {
// Do loads of string manipulations here
}
return data;
}
// Check for localdata
if(typeof StorageHandler.handle('localdata.favorites') == 'undefined') {
StorageHandler.handle('localdata.favorites',[]);
}
},
};
return obj;
});
Here's what I tried from looking at examples:
angular.module('MyApp.DataHandler', []) // So Modular, much name
.factory('DataHandler', function ($rootScope, $q, $state, StorageHandler) {
var obj = {
InitData : function() {
var d = $q.defer(); // Set defer
StorageHandler.defaultConfig = {clientName:'test_feed'};
StorageHandler.prepData = function(data) {
var i = 0;
var maps = StorageHandler.dataMap;
i = data.line_up.length;
while(i--) {
// Do loads of string manipulations here
}
return data;
}
// Check for localdata
if(typeof StorageHandler.handle('localdata.favorites') == 'undefined') {
StorageHandler.handle('localdata.favorites',[]);
}
return d.promise; // Return promise
},
};
return obj;
});
But nothing is shown in console when I use this in my controller:
DataHandler.InitData()
.then(function () {
// Successful
console.log('success');
},
function () {
// failure
console.log('failure');
})
.then(function () {
// Like a Finally Clause
console.log('done');
});
Any thoughts?
Like Florian mentioned. Your asynchronous call is not obvious in the code you've shown.
Here is the gist of what you want:
angular.module("myApp",[]).factory("myFactory",function($http,$q){
return {
//$http.get returns a promise.
//which is latched onto and chained in the controller
initData: function(){
return $http.get("myurl").then(function(response){
var data = response.data;
//Do All your things...
return data;
},function(err){
//do stuff with the error..
return $q.reject(err);
//OR throw err;
//as mentioned below returning a new rejected promise is a slight anti-pattern,
//However, a practical use case could be that it would suppress logging,
//and allow specific throw/logging control where the service is implemented (controller)
});
}
}
}).controller("myCtrl",function(myFactory,$scope){
myFactory.initData().then(function(data){
$scope.myData = data;
},function(err){
//error loudly
$scope.error = err.message
})['finally'](function(){
//done.
});
});

Resources