I am building an app using Ionic Framework 7 AngularJS
$ ionic info
Your system information:
Cordova CLI: 6.2.0
Gulp version: CLI version 3.9.1
Gulp local:
Ionic Framework Version: 1.2.4
Ionic CLI Version: 1.7.16
Ionic App Lib Version: 0.7.3
OS: Distributor ID: LinuxMint Description: Linux Mint 17.1 Rebecca
Node Version: v0.12.2
The page I'm building retrieve a list of church services and displays them in a master detail list. So I put the $http code in a Service as per the following code:
services.js :
.service('ServicesService', ['$http', '$sce', function($http, $sce){
var services = [];
$http.get('http://demo0194057.mockable.io/services').then(function(response) {
services = response.data;
window.q = services; //checking value of services var
}, function(error) {
console.error("Error loading Services Endpoint! " + error);
});
return {
getAllServices: function() {
console.log('from inside return obj ' + services);
window.p = services; // checking value of services var
return services;
}
}
}])
controller.js :
.controller('servicesCtrl', function($scope, $http, ServicesService, InfoService) {
$scope.services = [];
$scope.services = ServicesService.getAllServices();
window.r = $scope.services;
$scope.doRefresh = function() {
$http.get('http://demo0194057.mockable.io/services').success(function(response) {
$scope.services = response;
})
.finally(function() {
// stop the ion-refresher from spinning
$scope.$broadcast('scroll.refreshComplete');
});
}
})
So my problem is that the service is returning an empty array instead of an array of objects from the JSON REST API. I put some debugging variables in the code and I can see that from the controller, the service is returning an empty array (var window.r). From within the service, window.p is also empty, however window.q has the correct object data which means that the API call is working fine. I can't figure out where that data is getting lost though, such that it's not being successfully returned from the service.
Please help
try it like this:
Service
.service('ServicesService', ['$http', '$sce', function($http, $sce){
var services = [];
return {
getAllServices: function() {
return $http.get('http://demo0194057.mockable.io/services').then(function(response) {
services = response.data;
return services;
}, function(error) {
console.error("Error loading Services Endpoint! " + error);
});
}
}
}]);
Controller
.controller('servicesCtrl', function($scope, $http, ServicesService, InfoService) {
$scope.services = [];
$scope.doRefresh = function() {
ServicesService.getAllServices().then(function (response) {
$scope.services = response;
});
});
});
In your service you need to do the $http.get request inside the body of getAllServices method. Otherwise, the get request's then clause gets executed after the call to getAllServices is made by the controller which is why the services variable isn't initialized at that point.
$http.get('http://demo0194057.mockable.io/services').then(function(response) {
services = response.data;
window.q = services; //checking value of services var
}, function(error) {
console.error("Error loading Services Endpoint! " + error);
});
This is async function. So services = response.data will be executed somtimes later.
$scope.services = ServicesService.getAllServices(); This will be call just after angular will rise and probably before your async get wil be executed.
So. If you wont to get your services you have to wrap your ServicesService.getAllServices(); as a promise where you will wait your first request. But it seems there are some architecture problems... Try to push your code away for a while and redisign it.
Related
This is more of a writing clean code/ optimizing existing code.
I am writing my Angular Services to fetch data from backend like this
angular.module('myApp').service('Auth', ['$http', '$q', 'Config', function($http, $q, Config) {
this.getUser = function() {
return $http.get(Config.apiurl + '/auth/user')
.then(function(response) {
return response.data;
}, function(error) {
return $q.reject(error.data);
});
};
}]);
Now in this, I am calling getUser function n number of times from the Database.
Now the question is, is it okay to call this service to get n times redundant data or I should it be saved somewhere say rootscope to be accessed later? Or storing in root scope would be bad practice and I should consider some other option or nothing at all?
Would like to get some views on Angular Community here.
Here is a sample example on how to use factory for sharing data across the application.
Lets create a factory which can be used in entire application across all controllers to store data and access them.
Advantages with factory is you can create objects in it and intialise them any where in the controllers or we can set the defult values by intialising them in the factory itself.
Factory
app.factory('SharedData',['$http','$rootScope',function($http,$rootScope){
var SharedData = {}; // create factory object...
SharedData.appName ='My App';
return SharedData;
}]);
Service
app.service('Auth', ['$http', '$q', 'SharedData', function($http, $q,SharedData) {
this.getUser = function() {
return $http.get('user.json')
.then(function(response) {
this.user = response.data;
SharedData.userData = this.user; // inject in the service and create a object in factory ob ject to store user data..
return response.data;
}, function(error) {
return $q.reject(error.data);
});
};
}]);
Controller
var app = angular.module("app", []);
app.controller("testController", ["$scope",'SharedData','Auth',
function($scope,SharedData,Auth) {
$scope.user ={};
// do a service call via service and check the shared data which is factory object ...
var user = Auth.getUser().then(function(res){
console.log(SharedData);
$scope.user = SharedData.userData;// assigning to scope.
});
}]);
In HTML
<body ng-app='app'>
<div class="media-list" ng-controller="testController">
<pre> {{user | json}}</pre>
</div>
</body>
Instead of rootScope just use a local variable of user in your service that can be accessed from anywhere in your code and so you doesn't have to call the api every time.
angular.module('metaiotAdmin').service('Auth', ['$http', '$q', 'Config', function($http, $q, Config) {
this.getUser = function() {
if(this.user){
return this.user;
}
else{
return $http.get(Config.apiurl + '/auth/user')
.then(function(response) {
this.user = response.data;
return response.data;
}, function(error) {
return $q.reject(error.data);
});
}
};
}]);
Hope it helps.
You don't have to, $http already caches your request for you, if the same request is applied in case you set the cache config option to true.
$http.get('/hello', { cache: true})
.then(onResponse)
or you can either set it for every request, by using either an interceptor or override the http instance in the $httpProvider, to apply the effect for every http request.
app.module('app.module')
factory('MyHttpInterceptor', function() {
return {
request : function(config) {
config.cache = true;
return config;
},
// rest of implementation of the interceptor
}
});
app.module('app.module')
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('MyHttpInterceptor');
// ... rest of the configuration
}]);
Or :
app.module('app.module')
.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.cache = true;
// ...
}]);
see :
Angular doc for caching
I'm having trouble properly injecting a bunch of modules in a project. This is the project I’m following
https://thinkster.io/django-angularjs-tutorial
When i try to make a new post - angular throws the below error
Error: Authentication.getAuthenticatedAccount(...) is undefined
submit#http://localhost:8000/static/javascripts/posts/controllers/new-post.controller.js:31:21
$parseFunctionCall#http://localhost:8000/static/bower_components/angular/angular.js:12474:15
This is where the problem seems to occur
function NewPostController($rootScope, $scope, Authentication, Snackbar, Posts) {
var vm = this;
vm.submit = submit;
function submit() {
$rootScope.$broadcast('post.created', {
content: vm.content,
author: {
username: Authentication.getAuthenticatedAccount().username
}
});
$scope.closeThisDialog();
Posts.create(vm.content).then(createPostSuccessFn, createPostErrorFn);
function createPostSuccessFn(data, status, headers, config) {
Snackbar.show('Success! Post created.');
}
function createPostErrorFn(data, status, headers, config) {
$rootScope.$broadcast('post.created.error');
Snackbar.error(data.error);
}
}
}
But I can see that the correct module is being used in the code.
this is my new-post.controller.js file where i've injected the Authentication dependency
angular
.module('thinkster.posts.controllers')
.controller('NewPostController', NewPostController);
NewPostController.$inject = ['$rootScope', '$scope', 'Authentication', 'Snackbar', 'Posts'];
this is a snippet my posts.module.js file
angular
.module('thinkster.posts', [
'thinkster.posts.controllers',
'thinkster.posts.directives',
'thinkster.posts.services'
]);
angular
.module('thinkster.posts.controllers', []);
this is a snippet of the authentication service module
angular
.module('thinkster.authentication.services')
.factory('Authentication',Authentication);
Authentication.$inject = ['$cookies','$http'];
function Authentication($cookies,$http){
var Authentication = {
getAuthenticatedAccount: getAuthenticatedAccount,
isAuthenticated: isAuthenticated,
register:register,
login : login,
logout:logout,
setAuthenticatedAccount: setAuthenticatedAccount,
unAuthenticate: unAuthenticate
};
return Authentication;
function getAuthenticatedAccount(){
if (!$cookies.authenticatedAccount){
return;
}
return JSON.parse($cookies.authenticatedAccount);
}
And a snippet of the authentication module
angular
.module('thinkster.authentication',[
'thinkster.authentication.controllers',
'thinkster.authentication.services'
]);
-finally, the below thinkster module
angular
.module('thinkster', [
'thinkster.config',
'thinkster.routes',
'thinkster.authentication',
'thinkster.layout',
'thinkster.posts',
'thinkster.utils'
]);
the authentication service works fine since I’m able to login and logout of the project. Am i looking in the wrong place for the error?
The code snippets are missing the NewPostController definition. Without seeing that code, I would guess that the Authentication object may not be passed into the function.
function NewPostController($rootScope, $scope, Authentication, Snackbar, Posts) {
}
You can see what methods are available on your Authentication object with the following code in NewPostController:
for (var key in Authentication) {
console.log(typeof Authentication[key], key);
}
You should see "function getAuthenticatedAccount" if it's available on the object.
I have an app that when you select a project, it goes into the project section where it needs to load all the information and data about a project asynchronously.
I wanted to store all the data in a singleton service so I can access the data in all the project's subsections(project header, project footer, main menu, etc)
If user clicks a different project, it will need to re-initialize with different URL parameter (in this case, project_id).
app.factory('ProjectService', function($http, project_id) {
var SERVICE = {
async: function() {
var promise = $http.get('SOME URL' + project_id).then(function(response) {
return response.data;
});
return promise;
}
};
return SERVICE;
});
What is the best way to achieve this and how can I reinitialize the service with different URL parameters when user clicks a button?
Check working demo: JSFiddle
First of all, using a factory may be more suitable for your case.
You need to play with the deferred/promise manually. If the requested id is already loaded, resolve the deferred object immediately. Otherwise, send a HTTP request (in the demo I just used an public API providing fake data) and fetch the project information.
app.factory('ProjectFactory', ['$http', '$q', function ($http, $q) {
var myProject;
return {
project: function (id) {
var deferred = $q.defer();
// If the requested id is fetched already, just resolve
if (!id || (myProject && myProject.id === id)) {
console.log('get from cache');
deferred.resolve(myProject);
} else {
console.log('sending request...');
$http.get('http://jsonplaceholder.typicode.com/posts/' + id).success(function (response) {
myProject = response;
deferred.resolve(myProject);
}).error(function (response) {
deferred.reject(response);
});
}
return deferred.promise;
}
};
}]);
To use this factory:
app.controller('JoyCtrl', ['$scope', '$timeout', 'ProjectFactory', function ($scope, $timeout, ProjectFactory) {
ProjectFactory.project(1).then(function (project) {
$scope.project = project;
ProjectFactory.project(1).then(function (project) {
});
}, function (reason) {
console.log('Failed: ' + reason);
});
}]);
For your reference: $http, $q
I am trying to unit test angularjs with QUnit but get the error messages: $httpBackend.whenGET is not a function, $httpBackend.when is not a function. I have included angular mocks and angular breeze service (http://www.breezejs.com/documentation/breeze-angular-service) which uses the angular q library for promises and httpbackend instead of $.ajax for data transmission. I am still unable to mock any of the calls to the server. Some sample code:
var $httpBackend,
injector;
var SPAModule = angular.module("spa");
injector = angular.injector(['ng', 'spa']);
$httpBackend = injector.get("$httpBackend");
SPAModule.config(function ($provide) {
$provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
});
test("WHEN the controller is called THEN it should be created with the correct data on the scope", function () {
'use strict';
// Given
$httpBackend.whenGET("/Breeze/Data/Jobs").respond({ data: jobData });
$httpBackend.whenGET("/Breeze/Data/Metadata").respond({});
var routeParams = { id: "b" },
// When
controller = injector.get('$controller')(toriga.propertyController, {
$scope: theScope,
$window: windowMock,
$location: locationMock,
$routeParams: routeParams
}),
$rootScope = injector.get('$rootScope');
$httpBackend.flush();
$rootScope.$apply(); // forces results of promise to be executed
// Then
notEqual(controller, null, 'controller was created properly');
strictEqual(theScope.pageTitle, "Property", "pageTitle was set on the scope");
notEqual(theScope.job, null, "Job set on the scope");
ok(toastrMock.warning.notCalled, "No warning messages were displayed");
ok(toastrMock.error.notCalled, "No error messages were displayed");
});
This code used to work fine when I was not using breeze but now I have switched I can't seem to get it to work and the documentation is poor on how to get this working. Any help would be appreciated.
I can't tell all of the details of your tests. I can offer some comfort that it does work .. and pretty much as you'd expect.
Here is an extract from the test/specs/lookups.spec in the "Zza-Node-Mongo" sample (it's in github) in which I replay through the $httpBackend mock a (subset of) the server’s response to a Breeze client request for "lookup" reference entities.
I'm using Jasmine instead of QUnit but I hope you get the picture.
// simplified for presentation here but materially sufficient
describe("when lookups service receives valid lookups data", function () {
var $httpBackend, flush$q, lookups
var lookupsUrlRe = /breeze\/zza\/Lookups\?/; // RegEx of the lookups endpoint
beforeEach(module('app'));
beforeEach(inject(function(_$httpBackend_, $rootScope, _lookups_) {
$httpBackend = _$httpBackend_;
flush$q = function() { $rootScope.$apply(); };
lookups = _lookups_;
}));
beforeEach(function () {
$httpBackend.expectGET(lookupsUrlRe).respond(validLookupsResponse.data);
lookups.ready(); // THIS TRIGGERS CALL TO FETCHLOOKUPS
$httpBackend.flush();
});
it("doesn't bomb", function () {
expect(true).toBe(true);
});
it("'ready()' invokes success callback", function () {
var success = jasmine.createSpy('success');
lookups.ready(success);
flush$q(); // NOTE NEED TO FLUSH $Q IN THIS TEST
expect(success).toHaveBeenCalled();
})
it("has OrderStatus.Pending", function () {
expect(lookups.OrderStatus && lookups.OrderStatus.Pending).toBeDefined();
});
... more tests ...
});
The "lookups" service (app/services/lookups.js) calls breeze to fetch lookups data from the server.
function fetchLookups() {
return breeze.EntityQuery.from('Lookups')
.using(manager).execute()
.then(function () {
logger.info("Lookups loaded from server.");
extendService(manager)
})
.catch(function (error) {
error = util.filterHttpError(error);
logger.error(error.message, "lookups initialization failed");
throw error; // so downstream fail handlers hear it too
});
}
As you might imagine, this is a pretty deep integration test that starts with a service consumed by a ViewModel and goes all the way through the Breeze Angular Service through $http just about to the network boundary before being intercepted by $httpBackend.
How can I use angular-resources.js to read in a JSON file through a service?
I am working on a very basic Angular app for testing purposes and am just trying to read in data from JSON file right now. I am placing this code in a service so I can more easily swap it out when we move a server based data store.
My App and App.controller declaration are as follows:
'use strict';
// create module for custom directives
var App = angular.module('App', ['jsonService']);
// controller business logic
App.controller('AppCtrl', function AppCtrl($scope, JsonService) {
console.log("marker 1");
if (!$scope.jsonData) {
console.log("marker 2");
JsonService.getData(function (d) {
console.log(d);
$scope.jsonData = d;
$scope.records = d.length;
});
} else {
console.log("I have data already... " + $scope.jsonData);
}
console.log($scope.jsonData);
});
My JsonService is defined as the follow, at the moment:
'use strict';
angular.module('jsonService', ['ngResource'])
.factory('JsonService', function($resource, $filter) {
// define the remote service using Angular's $resource module
var service = $resource('/data/ProcessModeling-Resources.json', {});
var JsonService = {
// calls $resource.query() to retrieve the remote data.
getData : function getData(callback) {
console.log("marker 3");
service.query(function (data) {
console.log("marker 4");
});
}
};
return JsonService;
});
The console output I am getting follows:
marker 1 app.js:8
marker 2 app.js:11
marker 3 services.js:13
undefined app.js:21
TypeError: Object #<Resource> has no method 'push'
at copy (http://127.0.0.1:8000/lib/angular.js:556:21)
at new Resource (http://127.0.0.1:8000/lib/angular-resource.js:330:9)
at http://127.0.0.1:8000/lib/angular-resource.js:386:32
at forEach (http://127.0.0.1:8000/lib/angular.js:117:20)
at http://127.0.0.1:8000/lib/angular-resource.js:385:19
at wrappedCallback (http://127.0.0.1:8000/lib/angular.js:6650:59)
at http://127.0.0.1:8000/lib/angular.js:6687:26
at Object.Scope.$eval (http://127.0.0.1:8000/lib/angular.js:7840:28)
at Object.Scope.$digest (http://127.0.0.1:8000/lib/angular.js:7707:25)
at Object.Scope.$apply (http://127.0.0.1:8000/lib/angular.js:7926:24) angular.js:5582
I'm receiving my error when I attempt to call my service.query(function (data) { }, which (if I'm understanding correctly) should be pulling my JSON file in.
I've been using AngularJS Cats App as an example for pulling data.
I'd follow #pkozlowski's advice and make sure the response is an array. Anyway, here's an example that loads data from a JSON file similar to what you describe in your comments. It uses ngResource and can help you put things together: http://plnkr.co/edit/Ofq7Md8udEnIhAPF1NgL?p=preview
The service
angular.module('jsonService', ['ngResource'])
.factory('JsonService', function($resource) {
return $resource('cats.json',{ }, {
getData: {method:'GET', isArray: false}
});
});
Notice that isArray is set to false.
Your app and controller
var app = angular.module('app', ['jsonService']);
app.controller('ctrl', function($scope, JsonService){
JsonService.getData(function(data){
$scope.name = data.name;
$scope.children = data.children;
});
});
getData is actually not needed since the Resource class gives you some useful convenience methods such a get, you can just do this
angular.module('jsonService', ['ngResource'])
.factory('JsonService', function($resource) {
return $resource('cats.json');
});
app.controller('ctrl', function($scope, JsonService){
JsonService.get(function(data){
$scope.name = data.name;
$scope.children = data.children;
});
});