Correct syntax when calling an AngularJS javascript function - javascript

I have a JS function in an AngularJS JS file defined below and I'm calling it.
What would be the correct syntax when calling this function within the JS file itself because I need to do a hard refresh on a grid.
FUNCTION
viewModel.getGridData = function (ajaxUrl, searchValues)
{
$http.post(ajaxUrl, { searchCriteria: searchValues })
.success(function (data)
{
viewModel.gridOptions.data = data.kvs;
});
};
CALL TO FUNCTION
viewModel.getGridData(ajaxUrl, searchValues);

You should consider making "getGridData" a service function that returns a promise using Angular's internal $http service. This pattern has the benefit of allowing you to reuse your ajax requests in other places within your application and is considered by many to be a best practice.
myApp.factory('SearchService', function($http) {
return {
getGridData: function(url, valueObj) {
return $http.post(url, valueObj);
}
}
});
The promise would resolve with the resolution of its internal ajax call, so you would invoke the factory function in your controller (don't forget to inject it as a dependency!) with
SearchService.getGridData(url, valueObject).then(function(result) {
//you will have access to the result of the Ajax call inside this callback function only.
//Be sure to bind it for use in other places in your application!
console.log(result);
})
Relevant reads:
$http docs
angular services docs

Related

Manage angularjs controller init with karma tests

Ive seen such questions and they all say 'extract logic out into a service and mock the service'. Simple, except i have as much as possible.
So when the controller inits i have a call to $scope.getWidgets() this method calls widgetService to get a list of widgets for the user, it will then display a toast notification using notifyService. Should widgetService reject its promise or the user have no widgets a call to notifyService is made. This is all done when the controller inits.
$scope.getWidgets = function () {
widgetService.getWidgets()
.then(function (widgets) {
if (widgets.length === 0) {
notifyService.notify('no widgets');
}
$scope.widgets = widgets;
})
.catch(function (e) {
notifyService.notify('oh noes');
});
}
//called at bottom of controller
$scope.getWidgets();
Now all my tests so for have not had need to run any digest cycles, run running promises etc. The method im trying to test calls a service to save a widget. Should it succeed or fail it will again call notifyService to send a notification to the user. I'm testing that the notification is triggered using karma's spyOn and also testing some flags are set correctly. There's no real logic, thats done in the service.
$scope.saveWidget = function (widget) {
widgetService.saveWidget(widget)
.then(function () {
notifyService.notify('all dandy');
//set some scope flags, no logic
})
.catch(function (e) {
notifyService.notify('oh noes');
//set some more scope flags, no logic
});
}
So now i have have to run the digest cycle to trigger my promises. The Trouble is my mock for widgetService.getWidgets() returns a promise. So when i run the digest first it resolves the promise registered in my init, which calls the notify service, which activates my spyon, concluding my test with false data.
Here are my tests, (please forgive the big code block, ive tried to strip it down as much as possible).
describe('tests', function () {
var scope, controlelr, _mockWidgetService, _mockNotifyService;
beforeEach(function () {
//Init the service mocks, angular.noop for methods etc
_mockWidgetService = init();
_mockNotifyService = init();
});
beforeEach(function () {
//Regisetr hooks to pull in my mocked services
angular.mock.module('myModule');
angular.mock.module(function($provide) {
$provide.value('widgetService', _mockWidgetService);
$provide.value('notifyService', _mockNotifyService);
})
});
angular.mock.inject(function ($controller, $rootScope, widgetService, notifyService) {
//create scope and inject services to create controller
scope = $rootScope.$new();
//Trouble is $scope.getWidgets() is called now.
controller = $controller('testController', {
$scope: scope,
widgetService: widgetService,
notifyService: notifyService
});
});
describe('getWidgets', function() {
it('myTest', function () {
//I cannow manipulate mock services moreso if needed just for this test
spyOn(_mockNotifyService, 'notfy');
//Call save widget
scope.saveWidget();
scope.$digest();
expect(_mockNotifyService.notify).toHaveBeenCalledWith(//values);
});
});
});
So as you can see im struggling, i could mock all the mocks required for the init code, but id rather only mock the services i need for that test. Plus i don't like the idea of the init code running every single test potentially causing false errors (it has its own tests).
Any advise or solutions to such use cases would greatly appreciated.
I have found a potential solution that seems to work. I'm not entirely sure its best practice so if there are better options id be very grateful to hear them.
Essentially instead of mocking up my widgetService.getWidgets with a resolved promise before every test i mock it up with a deferred promise which never resolves. Idea being for tests that do not require it the init code will never run as the promise never resolves.
Tests that do require it to run just overwrite the mock and have widgetService.getWidgets return what is required.
That did mean some small changes, the controller had to be instantiated manually for each test (using beforeEach where available) instead of at the top of the very first describe).

AngularJS Service - Make $resource return data right away instead of a promise so clients don't have to use .then()

This is a simple problem but seems tricky due to asynchronous nature of promises.
With in my data service, I want to ensure that data is retrieved back from the server before moving on to next step as other functions/clients depend on that data.
I don't want clients to register callbacks or use .then() and certainly not use $timeout when requesting the data.
How can I make sure that the controller and service that depend on my Data service get the data right away when requested? I've explained my problem using the code below. I would very much like to continue on my current approach if possible.
AngularJS Version: 1.4+
My Current Approach
//Data Service
App.factory('HealthyFoodService', function($resource, $q) {
var fruitsPromise = $resource('api/food/fruits', {}).query().$promise;
var veggiesPromise = $resource('api/food/veggies',{}).query().$promise;
var fruitsData, veggiesData;
//Ensure $q populates the data before moving on to next statement.
$q.all([fruitsPromise, veggiesPromise]).then(function(data) {
fruitsData = data[0];
veggiesData = data[1];
}
function getCitrusFruits() {
var citrusFruits;
var allFrutis = fruitsData;
//code breaks here because fruitsData is still undefined when called from a controller or another service.
//some logic for this function
return citrusFruits;
}
function getLeafyVeggies() {
var leafyVeggies;
var allVeggies = veggiesData;
//code breaks here because veggieData is still undefined when called from a controller or another service.
//some logic for this function
return leafyVeggies;
}
function getLeafyVeggyByName(name) {
//this function is called from other services and controllers.
var leafyVeggies = getLeafyVeggies();
return leafyVeggies[name];
}
return {
getCitrusFruits: getCitrusFrutis,
getLeafyVeggies: getLeafyVeggies,
getLeafyVeggyByName: getLeafyVeggyByName
});
Below are the two clients. One is a controller and another is a service. They both need the data right away as following statements depend on the returned data.
//Controller
App.controller('LeafyVeggieController', function(HealthyFoodService) {
//Ideally I just'like to do something like below instead of calling `.then()` and registering callbacks.
var leafyVeggies = FoodService.getLeafyVeggies();
//leafyVeggies is undefined because data is not available yet;
});
//Another service depending on HealthyFoodService- similar scenario
App.factory('LeafyVeggieReportService', function(HealthyFoodService) {
function generateLeafyVeggieReport() {
var name = 'spinach';
var veggieInfo = HealthyFoodService.getLeafyVeggieByName(spinach);
//veggieInfo is undefined
//logic that need data.
});
My Previous Approach
Below is how I had it partially working before but I wasn't happy about using .then() everytime I needed the data.(Even with in the same service)
App.factory('HealthyFoodService', function($resource, $q) {
//resource variables;
function getLeafyVeggies() {
return $q.all([veggiesPromise]).then(function(data) {
//logic
return leafyVeggies;
});
}
function getLeafyVeggieByName() {
var leafyVeggies = getLeafyVeggies().then(function(data) {
return data;
}
//some logic
//still causes issues when called from another service because above call doesn't get the data right away.
}
return {
getLeafyVeggies: getLeafyVeggies,
getLeafyVeggieByName: getLeafyVeggieByName
}
//controller
App.controller('LeafyVeggieController', function(HealthyFoodService) {
var leafyVeggies = HealthyFoodService.getLeafyVeggies().then(function(data) {
return data;
});
//controller related logic
});
Update
I'm using ui-router as well, so I'm aware that I can use resolve:{} in $stateProvider to inject the data directly into the controller. The puzzle is how to get the data when I make a request from another service or from another function with in the same service without having to use .then().
Solution
Using $q.all([]) in my client services that depend on my Data service has done the trick for me. I have used $q.all([]) whenever I'm in a situation where I need all the data to be present before start processing the logic.
I still have to use .then() on my clients, but by using $q.all([]), I can still slightly simulate a synchronous flow without breaking any asynchronous principles.
I don't think this is possible. There is inherent latency in network operations that you need to wait for. Not doing so results in the application continuing before the data is available.
That being said, most of the native model binding operations will implicitly wait on promises, so there would be no need to .then if no further manipulation of the data is necessary before passing to the view. You can also use the transformResponse method of ngResource to help with this.
Another option might be to shift the complexity to the resolve methods in the route config. In that case you would handle the .then in the resolve and pass the resolved data to your controller. This will keep your controllers cleaner, but still requires you resolve the promises in the route config.
Try having the service hold your data and have the controller reference that data, instead of trying to pass it to your controller scope. Resolve the promise inside the service like so:
App.factory("HealthyFoodService", function($resource,$q) {
var service = {};
service.data = {
fruitsData: [],
veggiesData: []
}
$resource('api/food/fruits', {}).query().$promise.then(function(data) {
$service.data.fruitsData = data[0];
$service.data.veggiesData = data[1];
})
service.getCitrusFruits = function() {
var citrusFruits;
// Perform some logic on service.data.fruitsData
return citrusFruits;
}
return service;
})
In you controller, you can talk to the service like so:
App.controller("FruitController", function($scope, HealthyFoodService) {
// Now you can access the service data directly via $scope.hfs.fruitsData
$scope.hfs = HealthyFoodService.data;
// Or we can create a local copy of the data using the getCitrusFruits function
$scope.citrusFruits = HealthyFoodService.getCitrusFruits();
})

AngularJS don’t get JSON data

I want to get data from a JSON file by using $http.get with AngularJS.
I use the "new way" to write AngularJS scripts (link).
PLUNKER
Nothing happens, Firebug tells me nothing.
I think the problem is in my activate() function code, I don’t really understood the promises.
function activate() {
return $http.get('test.json').then(function(data) {
vm.result = data;
return vm.result;
});
}
Have you an idea about this ?
I see a couple of problems.
First, in your plunker code you have:
controller.$inject = ["$http"];
function controller() {
You are missing the $http parameter in your controller function signature.
function controller($http) {
Once I fixed that, I found your index.html page was binding to {{c.value}}, but your controller never defines a value property. Maybe this should be {{c.result}}? If I make these changes I get a visible result.
You're not able to return at that point in your then callback. Simply return the $http call itself, of which will be a promise, and resolve it. Also, your console should be telling you something like $http is undefined since you did not inject this service properly. Observe the following...
function activate() {
return $http.get('test.json')
}
[...]
activate().then(function(response) {
vm.result = response.data;
});
Plunker - demo
Side note - you'll likely want to wrap activate() into an reusable service to keep this logic out of our controllers, so we'll instead inject the defined service into controller instead of $http directly.
Plunker - demo with this logic wrapped in a simple service

How to make data available in all scopes in Angularjs

I wish to fetch data from the server-side using $http and and make it available to the all routes and controllers in my app.
Javascript code sample
myApp.factory('menuService', function($http){
$http.get('/pages').success(function(data){
return data; //list of pages
});
});
myApp.run(function($rootScope, menuService){
$rootScope.menu = menuService;
});
HTML code sample
<ul >
<li ng-repeat="page in menu">{{page.title}}</li>
</ul>
This code actually returns the data but does not print it on my html page. Please can someone help? Thanks
You are inverting the differed promise pattern.
Instead your code should be:
myApp.factory('menuService', function($http){
return $http.get('/pages');
});
myApp.run(function($rootScope, menuService){
menuService.then(function(data) {
$rootScope.menu = data;
})
});
You may be able to benefit from setting up your menuService a bit different. Try the following...
myApp.factory('menuService', function($http) {
function getData() {
return $http.get('/pages');
}
return {
'getData' : getData
}
});
Now we have a function wrapped in our $http call in a getData() function, we can now easily leverage then() to resolve the promise returned by getData() in .run(), ensuring we get a resolved value and $rootScope.menu gets assigned the values we want. This new setup on menuService now sets the landscape to add other functions at later times, which we'll likely want.
myApp.run(function($rootScope, menuService) {
menuService.getData().then(function(response) {
$rootScope.menu = response;
})
});
Check out the $http docs for a better understanding on asynchronous behavior

Does javascript have the equivalent of `pthread_once`

I'm trying to initialize a variable in javascript (specifically, I want to use a remote template with the jQuery template plugin) and then have multiple asynchronous callbacks wait for it to be initialized before proceeding. What I really want is to be able to link to the remote template via a <script type="text/x-jquery-tmpl" src="/my/remote_template"> tag, but barring that I could get away with the javascript equivalent of a pthread_once.
Ideally, the api would look something like:
$.once(function_to_be_called_once, function_to_be_called_after_first)
And used like:
var remote_template = "";
function init_remote_template() {
remote_template = $.get( {
url: "/my/remote/template",
async: false
});
}
$.once(init_remote_template, function () {
// Add initial things using remote template.
});
And then later, elsewhere:
$.get({
url: "/something/that/requires/an/asynchronous/callback",
success: function () {
$.once(init_remote_template, function () {
// Do something using remote template.
}
}
});
Does such a thing exist?
Looks like jQuery's promises can help you out here:
var templatePromise = $.get({
url: "/my/remote/template"
});
templatePromise.done(function(template) {
// Add initial things using remote template.
});
and elsewhere you can do:
$.get({
url: "/something/that/requires/an/asynchronous/callback",
success: function () {
templatePromise.done(function(template) {
// Do more things using remote template.
});
}
});
Usually $.get (and $.ajax, etc) are used with success: and error: callbacks in the initial invocation, but they also return a promise object that acts just like a $.Deferred, documented here: http://api.jquery.com/category/deferred-object/ which allows you to do what you're asking. For error handling, you can use templatePromise.fail(...) or simply add error: ... to the initial $.get.
In general it's best to avoid synchronous AJAX calls because most browsers' interfaces will block while the HTTP request is being processed.
If I understand correctly, jQuery will do what you want of it by way of Deferreds/promises.
You can even generalise the remote template fetcher by
using a js plain object in which to cache any number of templates
renaming the function and passing a url to it, get_remote_template(url)
js :
var template_cache = {};//generalised template cache
//generalised template fetcher
function get_remote_template(url) {
var dfrd = $.Deferred();
if(!template_cache[url]) {
$.get(url).done(function(tpl) {
template_cache[url] = tpl; //we use the url as a key.
dfrd.resolve(tpl);
});
}
else {
dfrd.resolve(template_cache[url]);
}
return dfrd.promise();
}
Then :
var url1 = "/my/remote/template"; //the url of a particular template
get_remote_template(url1).done(function(tpl) {
// Add initial things using tpl.
});
And, earlier or later :
$.get({
url: "/something/that/requires/an/asynchronous/callback",
success: function(data) {
init_remote_template(url1).done(function (tpl) {
// Do something using tpl (and data).
});
}
});
Note how get_remote_template() returns a promise. If the requested template is already cached, the promise is returned ready-resolved. If the template is not yet in cache (ie. it needs to be downloaded from the server), then the promise will be resolved some short time later. Either way, the fact that a promise is returned allows a .done() command to be chained, and for the appropriate template to be accessed and used.
EDIT
Taking #SophieAlpert's points on board, this version caches the promise associated with a tpl rather than the tpl itself.
var template_cache = {};//generalised cache of promises associated with templates.
//generalised template fetcher
function get_remote_template(url) {
if(!template_cache[url]) {
template_cache[url] = $.get(url);
}
return template_cache[url];//this is a promise
}
This version of get_remote_template() would be used in the same way as above.

Categories

Resources