I have the following code:
.service('loginModal', function($rootScope, $uibModal) {
function updateUserData(user, data) {
Object.keys(data).forEach(function(key) {
user.facebook[key] = data[key];
});
return user.$update();
}
return function() {
var instance = $uibModal.open({
templateUrl: 'tpls/modals/login.html',
controller: function($scope, $uibModalInstance, facebookService, UserService) {
function updateUserData(user, data) {
Object.keys(data).forEach(function(key) {
user.facebook[key] = data[key];
});
return user.$update();
}
$scope.login = function() {
facebookService.login().then(function(response) {
var authResponse = facebookService.getAuthResponse();
facebookService.api('/me').then(function(response) {
if (response && !response.error) {
response.picture = 'http://graph.facebook.com/' + response.id + '/picture?type=large';
UserService.query({
'facebook.id': response.id,
'facebook.token': authResponse.accessToken
}).$promise.then(function(results) {
response.token = {
value: authResponse.accessToken,
expiresIn: authResponse.expiresIn
};
if (results.length > 0)
updateUserData(results[0], response) //THIS DOES NOT FULFILL OR REJECT
.then($uibModalInstance.close, $uibModalInstance.dismiss);
else
UserService.save({
facebook: response,
local: {
username: response.email || response.id,
password: response.token.value
}
}).$promise
.then($uibModalInstance.close, $uibModalInstance.dismiss);
}, $uibModalInstance.dismiss);
} else {
$uibModalInstance.dismiss(response.error || response);
}
}, $uibModalInstance.dismiss);
}, $uibModalInstance.dismiss);
};
}
instance.opened.then(function() {
$rootScope.openModals.push(instance);
});
function removeInstanceFromModalList() {
$rootScope.openModals.splice($rootScope.openModals.indexOf(instance), 1);
}
instance.result.then(removeInstanceFromModalList, removeInstanceFromModalList);
return instance.result;
}
Basically I'm calling loginModal().then(function(user){...},function(e){...}); from wherever I want.
The part which does not work however is right after I query UserService
if (results.length > 0)
updateUserData(results[0], response) //THIS DOES NOT FULFILL OR REJECT
.then($uibModalInstance.close, $uibModalInstance.dismiss);
I've also tried debugging like this:
updateUserData(results[0], response)
.then(function(usr) {
$uibModalInstance.close(usr); //debugger reaches this statement,
//nothing happens
}, function(e) {
$uibModalInstance.dismiss(e);
});
What's wrong with my code? only backdrop clicks seem to close the dialog.
You can use the promise returned by $uibModal.open() which has the close() method attached.
You could store it in the controller $scope:
$scope.modal_instance = $uibModal.open({ ...
and then use:
$scope.modal_instance.close();
instead of $uibModalInstance.close.
Dangit - had a version issue.
Apperantly the version of angular-ui I was using was incompatible with angular#1.4.7 so I had to upgrade to 1.4.8.
Related
I have this code in services.js in in my Angular App:
.factory('Articles', function($http) {
$http.get('https://api.myjson.com/bins/4ici6').then( function(response) {
var articles = response.data.articles;
});
return {
all: function() {
return articles;
},
get: function(articleId) {
for (var i = 0; i < articles.length; i++) {
if (articles[i].id === parseInt(articleId)) {
return articles[i];
}
}
return null;
}
};
})
It doesn't work as I get this error in the console:
ReferenceError: articles is not defined
at Object.all (http://localhost:8100/js/services.js:31:14)
at new <anonymous> (http://localhost:8100/js/controllers.js:4:30)
at Object.instantiate (http://localhost:8100/lib/ionic/js/ionic.bundle.js:18015:14)
Also here is the controller.js code that refers to articles:
.controller('NewsCtrl', function($scope, Articles) {
$scope.articles = Articles.all();
})
.controller('NewsDetailCtrl', function($scope, $stateParams, Articles) {
$scope.article = Articles.get($stateParams.articleId);
$scope.posttofacebook = function (){
window.plugins.socialsharing.shareViaFacebook(null, null, $scope.article.url);
}
$scope.posttotwitter = function (){
window.plugins.socialsharing.shareViaTwitter(null, null, $scope.article.url);
}
})
What am I doing wrong here?
Because $http.get is an asynchronous call you'll have to deal with that.
Just putting it on top of your factory won't consistently work.
Try this instead:
.factory('Articles', function($http) {
return {
all: function() {
return $http.get('https://api.myjson.com/bins/4ici6').then(function(response) {
return response.data.articles;
});
},
get: function(articleId) {
//Probably best here to call an API endpoint that will return a single article with the parameter's articleId
//Something along the lines of
//$http.get('https://api.myjson.com/bins/4ic16/' + articleId).then(function(response) { //handle response});
}
};
})
Your controller should also be changed to handle the async function call:
.controller('NewsCtrl', function($scope, Articles) {
Articles.all().then(function(articles) { $scope.articles = articles });
})
You have your articles variable declared inside the callback of the http, so it won't be available outside of it. Move it outside:
.factory('Articles', function($http) {
// declaring it here makes it available in the 'all' function
var articles = [];
$http.get('https://api.myjson.com/bins/4ici6').then( function(response) {
articles = response.data.articles;
});
return {
all: function() {
return articles;
},
get: function(articleId) {
for (var i = 0; i < articles.length; i++) {
if (articles[i].id === parseInt(articleId)) {
return articles[i];
}
}
return null;
}
};
})
But because you fetch your articles asynchronously through http, it can happen that you do the Articles.all() before the articles are fetched, resulting in an empty array. Instead, I would do it like this:
.factory('Articles', function($http, $q) {
// declaring it here makes it available in the 'all' function
var articles = [];
var fetched = false;
var getAll = function() {
var deferred = $q.defer();
if (!fetched) {
$http.get('https://api.myjson.com/bins/4ici6').then( function(response) {
articles = response.data.articles;
fetched = true;
deferred.resolve(articles);
});
} else {
deferred.resolve(articles);
}
return deferred.promise;
}
return {
all: getAll,
get: function(articleId) {
var deferred = $q.defer();
getAll().then(function(articles) {
for (var i = 0; i < articles.length; i++) {
if (articles[i].id === parseInt(articleId)) {
deferred.resolve(articles[i]);
break;
}
}
// not found
return deferred.reject();
}
return deferred.promise;
}
};
})
And use it like this:
Articles.all().then(function(articles){
// now the articles are available
});
Articles.get(id).then(function(article){
// found
}, function(){
// not found
});
So, I'm trying to learn Ionic Framework, but, I've got a problem already, I'm running the Ionic Backand Starter app (like an example app) and I've got two different results when testing it.
Ripple: When I run it from VS on Ripple, it works perfectly fine, the Database is how it is supposed to be, everything is running fine.
Device: When I run it from VS on my Android Device (Samsung Galaxy S5 Mini, without root), the application has a problem when loading the Backand Database. It looks completely empty.
Im going to leave prints of the 2 trials and also my Controller.js, App.js and Services.js, also, I'm leaving the github project link, in case you want more detailed stuff.
GitHub Project:
GitHub Backand Ionic Starter Project
Prints:
Device: http://prntscr.com/a3iq45
Ripple: http://prntscr.com/a3iqgd
CODES:
App.js:
// Ionic template App
// angular.module is a global place for creating, registering and retrieving Angular modules
// 'SimpleRESTIonic' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
angular.module('SimpleRESTIonic', ['ionic', 'backand', 'SimpleRESTIonic.controllers', 'SimpleRESTIonic.services'])
.run(function ($ionicPlatform) {
$ionicPlatform.ready(function () {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
cordova.plugins.Keyboard.disableScroll(true);
}
if (window.StatusBar) {
// org.apache.cordova.statusbar required
StatusBar.styleLightContent();
}
});
})
.config(function (BackandProvider, $stateProvider, $urlRouterProvider, $httpProvider) {
BackandProvider.setAppName('ionicstarter'); // change here to your app name
BackandProvider.setSignUpToken('4ce88904-75c5-412c-8365-df97d9e18a8f'); //token that enable sign up. see http://docs.backand.com/en/latest/apidocs/security/index.html#sign-up
BackandProvider.setAnonymousToken('87c37623-a2d2-42af-93df-addc65c6e9ad'); // token is for anonymous login. see http://docs.backand.com/en/latest/apidocs/security/index.html#anonymous-access
$stateProvider
// setup an abstract state for the tabs directive
.state('tab', {
url: '/tabs',
abstract: true,
templateUrl: 'templates/tabs.html'
})
.state('tab.dashboard', {
url: '/dashboard',
views: {
'tab-dashboard': {
templateUrl: 'templates/tab-dashboard.html',
controller: 'DashboardCtrl as vm'
}
}
})
.state('tab.login', {
url: '/login',
views: {
'tab-login': {
templateUrl: 'templates/tab-login.html',
controller: 'LoginCtrl as login'
}
}
});
$urlRouterProvider.otherwise('/tabs/dashboard');
$httpProvider.interceptors.push('APIInterceptor');
})
.run(function ($rootScope, $state, LoginService, Backand) {
function unauthorized() {
console.log("user is unauthorized, sending to login");
$state.go('tab.login');
}
function signout() {
LoginService.signout();
}
$rootScope.$on('unauthorized', function () {
unauthorized();
});
$rootScope.$on('$stateChangeSuccess', function (event, toState) {
if (toState.name == 'tab.login') {
signout();
}
else if (toState.name != 'tab.login' && Backand.getToken() === undefined) {
unauthorized();
}
});
})
Controller.js:
angular.module('SimpleRESTIonic.controllers', [])
.controller('LoginCtrl', function (Backand, $state, $rootScope, LoginService) {
var login = this;
function signin() {
LoginService.signin(login.email, login.password)
.then(function () {
onLogin();
}, function (error) {
console.log(error)
})
}
function anonymousLogin(){
LoginService.anonymousLogin();
onLogin();
}
function onLogin(){
$rootScope.$broadcast('authorized');
$state.go('tab.dashboard');
}
function signout() {
LoginService.signout()
.then(function () {
//$state.go('tab.login');
$rootScope.$broadcast('logout');
$state.go($state.current, {}, {reload: true});
})
}
login.signin = signin;
login.signout = signout;
login.anonymousLogin = anonymousLogin;
})
.controller('DashboardCtrl', function (ItemsModel, $rootScope) {
var vm = this;
function goToBackand() {
window.location = 'http://docs.backand.com';
}
function getAll() {
ItemsModel.all()
.then(function (result) {
vm.data = result.data.data;
});
}
function clearData(){
vm.data = null;
}
function create(object) {
ItemsModel.create(object)
.then(function (result) {
cancelCreate();
getAll();
});
}
function update(object) {
ItemsModel.update(object.id, object)
.then(function (result) {
cancelEditing();
getAll();
});
}
function deleteObject(id) {
ItemsModel.delete(id)
.then(function (result) {
cancelEditing();
getAll();
});
}
function initCreateForm() {
vm.newObject = {name: '', description: ''};
}
function setEdited(object) {
vm.edited = angular.copy(object);
vm.isEditing = true;
}
function isCurrent(id) {
return vm.edited !== null && vm.edited.id === id;
}
function cancelEditing() {
vm.edited = null;
vm.isEditing = false;
}
function cancelCreate() {
initCreateForm();
vm.isCreating = false;
}
vm.objects = [];
vm.edited = null;
vm.isEditing = false;
vm.isCreating = false;
vm.getAll = getAll;
vm.create = create;
vm.update = update;
vm.delete = deleteObject;
vm.setEdited = setEdited;
vm.isCurrent = isCurrent;
vm.cancelEditing = cancelEditing;
vm.cancelCreate = cancelCreate;
vm.goToBackand = goToBackand;
vm.isAuthorized = false;
$rootScope.$on('authorized', function () {
vm.isAuthorized = true;
getAll();
});
$rootScope.$on('logout', function () {
clearData();
});
if(!vm.isAuthorized){
$rootScope.$broadcast('logout');
}
initCreateForm();
getAll();
});
Services.js:
angular.module('SimpleRESTIonic.services', [])
.service('APIInterceptor', function ($rootScope, $q) {
var service = this;
service.responseError = function (response) {
if (response.status === 401) {
$rootScope.$broadcast('unauthorized');
}
return $q.reject(response);
};
})
.service('ItemsModel', function ($http, Backand) {
var service = this,
baseUrl = '/1/objects/',
objectName = 'items/';
function getUrl() {
return Backand.getApiUrl() + baseUrl + objectName;
}
function getUrlForId(id) {
return getUrl() + id;
}
service.all = function () {
return $http.get(getUrl());
};
service.fetch = function (id) {
return $http.get(getUrlForId(id));
};
service.create = function (object) {
return $http.post(getUrl(), object);
};
service.update = function (id, object) {
return $http.put(getUrlForId(id), object);
};
service.delete = function (id) {
return $http.delete(getUrlForId(id));
};
})
.service('LoginService', function (Backand) {
var service = this;
service.signin = function (email, password, appName) {
//call Backand for sign in
return Backand.signin(email, password);
};
service.anonymousLogin= function(){
// don't have to do anything here,
// because we set app token att app.js
}
service.signout = function () {
return Backand.signout();
};
});
Thanks!!
I wrote a page that allows me to change my password. The code works and it does everything I want it to do, so I started writing tests. Since I'm not as experienced in Angular testing this had proven to be quite difficult and I can't get passed this error:
TypeError: 'undefined' is not an object (evaluating 'plan.apply')
at /Users/denniegrondelaers/asadventure/myproject-web/src/users/controllers/userPasswordController.js:9
at /Users/denniegrondelaers/asadventure/myproject-web/test/unitTests/specs/users/controllers/userPasswordControllerSpec.js:98
The controller:
userPasswordController.js
users.controllers.controller('userPasswordController',
['$scope', 'Session', '$state', 'UserService', 'languages',
function ($scope, Session, $state, UserService, languages) {
$scope.languages = languages;
$scope.password = "";
$scope.notEqual = false;
$scope.isSuccessful = false;
$scope.changePassword = function() {
var pw = {
userId: Session.getCurrentSession().userId,
oldPassword: encrypt($scope.password.oldPassword),
newPassword: encrypt($scope.password.newPassword),
newPasswordRepeat: encrypt($scope.password.newPasswordRepeat)
};
if (pw.newPassword === pw.newPasswordRepeat) {
$scope.notEqual = false;
UserService.setNewPassword(pw).then(function(res) {
$scope.formErrors = undefined;
$scope.isSuccessful = true;
}, function (error) {
$scope.formErrors = error.data;
}
);
} else {
$scope.notEqual = true;
}
};
var encrypt = function (password) {
var encrypted = CryptoJS.md5(password);
return encrypted.toString(CryptoJS.enc.Hex);
};
}
]
);
The service:
userService.js
userService.setNewPassword = function (password) {
return $http
.put(EnvironmentConfig.endpointUrl +
"/password/change", password)
};
The test:
userPasswordControllerSpec.js
describe('Users', function () {
describe('Controllers', function () {
fdescribe('userPasswordController', function () {
var $scope,
controller,
$q,
willResolve,
mockSession,
mockState,
mockUserService,
mockLanguages;
beforeEach(function () {
module('mysite.users.controllers');
module(function ($provide) {
$provide.value('translateFilter', function (a) {
return a;
});
$provide.value('$state', function (a) {
return a;
});
});
mockSession = {
getCurrentSession: function () {
return {userId: 4};
}
};
mockState = {
params: {
id: 1
},
go: function () {
}
};
mockLanguages = {
getLanguages : function () {
var deferred = $q.defer();
deferred.resolve({
data: [{}]
});
return deferred.promise;
}
};
mockUserService = {
setNewPassword : function () {
var deferred = $q.defer();
if (willResolve) {
deferred.resolve({
data: [{}]
});
}
return deferred.promise;
}
};
inject(function (_$q_, $controller, $rootScope) {
controller = $controller;
$q = _$q_;
$scope = $rootScope.$new();
});
controller('userPasswordController', {$scope: $scope, Session: mockSession, $state: mockState,
UserService: mockUserService, languages: mockLanguages
});
willResolve = true;
});
it('should change password', function () {
spyOn(mockUserService, 'setNewPassword').and.callThrough();
spyOn(mockState, 'go').and.callThrough();
spyOn(mockSession, 'getCurrentSession').and.callFake();
expect(mockUserService.setNewPassword).not.toHaveBeenCalled();
expect($scope.isSubmitable()).not.toBeTruthy();
$scope.compareStoreSelection = function () {
return true;
};
$scope.password = {
oldPassword: "123456",
newPassword: "password",
newPasswordRepeat: "password"
};
expect($scope.isSubmitable()).toBeTruthy();
>>> $scope.changePassword(); <<< LOCATION OF ERROR, line 98
expect(mockUserService.setNewPassword).toHaveBeenCalled();
$scope.$apply();
});
});
});
});
I've marked the line that gives the code in the test.
Anybody any idea how to fix this? A colleague suggested altering my controller code, but I'd like to keep it as it is, since it seems logical that this code shouldn't be altered for testing to work, right?
Solution
Yarons' suggestion to change the mockSession.getCurrentSession.callFake to mockSession.getCurrentSession.callThrough fixed it!
I can't understand why it does not update the $scope.user_free_status when I set a user free but when I unset the parameter it works perfectly. I need to reload page in one case and not the other...
The datas fetched are stored in the localstorage.
Here is the code:
.state('app', {
url: "/app",
abstract: true,
templateUrl: "templates/menu.html",
controller: 'InitialCtrl',
resolve: {
theUserFreeStatus: function(DataService) {
return DataService.getUserFreeStatus();
}
}
})
Controller:
.controller('InitialCtrl', function($scope, $state, DataService ,FreeService, SharedService, theUserFreeStatus) {
// Showing set free but not unset or not
if (FreeService.isSetFree()) {
$scope.showSetFree = false;
$scope.showUnSetFree = true;
} else {
$scope.showSetFree = true;
$scope.showUnSetFree = true;
}
// Show the Free status set when arriving on page/app
$scope.user_free_status = theUserFreeStatus;
// Set user as Free
$scope.setFree = function(activity, tags) {
FreeService.setFree(activity, tags).success(function() {
console.log($scope.user_free_status);
$scope.user_free_status = DataService.getUserFreeStatus();
console.log($scope.user_free_status);
$scope.showSetFree = false;
$scope.showUnSetFree = true;
SharedService.goHome();
})
}
//// Free status unset
$scope.unsetFree = function() {
FreeService.unsetFree().success(function() {
$scope.user_free_status = [];
$scope.showSetFree = true;
$scope.showUnSetFree = false;
SharedService.goHome();
});
};
})
The services:
.factory('FreeService', function(WebService, $localstorage, $ionicPopup, DataService, $sanitize, CSRF_TOKEN) {
var cacheFreeStatus = function(free_status) {
$localstorage.setObject('user_free_status', free_status)
};
var uncacheFreeStatus = function() {
$localstorage.unset('user_free_status')
}
return {
setFree: function(activity, tags) {
var status = { SOME STUFF BLABLABLA };
var setFree = WebService.post('setstatus/', sanitizeStatus(status));
setFree.success(function(response) {
console.log('available' + response.flash);
cacheFreeStatus(response.status_response);
})
setFree.error(freeError)
return setFree;
},
unsetFree: function() {
var details = {OTHER STUFF};
var unsetFree = WebService.post('unsetstatus/', details);
unsetFree.success(function(response) {
console.log('unset ' + response.flash);
uncacheFreeStatus(response.status_response);
})
unsetFree.error(freeError)
return unsetFree;
},
isSetFree: function() {
return $localstorage.get('user_free_status');
}
}
})
.service('DataService', function($q, $localstorage) {
return {
activities: $localstorage.getObject('activities'),
getActivities: function() {
return this.activities;
},
user_free_status: $localstorage.getObject('user_free_status'),
getUserFreeStatus: function() {
return this.user_free_status;
}
}
})
* Local Storage Service
------------------------------------------------------*/
.factory('$localstorage', ['$window', function($window) {
return {
set: function(key, value) {
$window.localStorage[key] = value;
},
unset: function(key) {
localStorage.removeItem(key);
},
get: function(key, defaultValue) {
return $window.localStorage[key] || defaultValue;
},
setObject: function(key, value) {
$window.localStorage[key] = JSON.stringify(value);
},
getObject: function(key) {
return JSON.parse($window.localStorage[key] || '{}');
}
}
}])
When setting the user's status, the console returns that the $http call worked but an empty array for the $scope variable I try to set. Once I reload the page I can see the updates displayed. If I unset the user's status, the $scope is properly updated without need to reload the page.
The Webservice is just the $http call.
What am I missing here to have the $scope.user_free_status updated correctly without having to reload the page??
Thanks for your time!
Your data service is injected as service but you have not appended the functions to this.rather you have returned it as part of literal like u do in factory
I try to assign a property of a service object by using the $http but I have confusing results. Why this doesn't work (here is my code):
.service('config', function ($http) {
var config = {
get_host: function () {
if (online == 0) {
return offlineHost;
}
return onlineHost;
},
online: 'false',
host: 'false',
checkConnection: function () {
//this wont work;
/*
$http.get(this.host + http_url ).then(function(response) {
return response.data.ping;
});
*/
//this will work
return 'oke';
},
_load: function () {
this.host = this.get_host();
this.online = this.checkConnection();
this.url_api = this.host + http_url;
if (this.online == 1) {
this.offline_message = 'Maaf aplikasi tidak bisa terkoneksi dengan server atau anda offline';
}
}
};
//run constructor and get value;
config._load();
return config;
}) //end config class
In my controller :
var online = config.online;
alert(online) //return undefined, but the $http request on firebug is showed return value
service:
.service('config', function ($http, $q) {
var config = {
get_host: function () {
if (online == 0) {
return offlineHost;
}
return onlineHost;
},
online: 'false',
host: 'false',
checkConnection: function () {
var deferred = $q.defer();
$http.get(this.host + http_url ).then(function(response) {
$q.resolve(response.data.ping);
});
return $q.promise;
},
_load: function () {
this.host = this.get_host();
this.online = this.checkConnection();
this.url_api = this.host + http_url;
if (this.online == 1) {
this.offline_message = 'Maaf aplikasi tidak bisa terkoneksi dengan server atau anda offline';
}
}
};
//run constructor and get value;
config._load();
return config;
}) //end config class
controller:
config.online.then(function(data){
alert(data);
var online = data;
handleDate(online); // this is a predefined function to handle your data when it's downloaded
});
That's because the $http service calls are asynchronous.
The easiest way to handle this is to make your service return a promise:
return $http.get(this.host + http_url);
(return in front of $http is crucial here). Then anywhere in the code:
config.checkConnection().then(function(response) {
// there you get the response.data.ping
});
See your modified and simplified code here: http://plnkr.co/edit/cCHXCLCG3xJUwAPPlJ3x?p=preview
You can read more about that:
http://chariotsolutions.com/blog/post/angularjs-corner-using-promises-q-handle-asynchronous-calls/
http://lennybacon.com/post/2014/03/21/chaining-asynchronous-javascript-calls-with-angular-js
https://docs.angularjs.org/api/ng/service/$q