Angular modal controller testing error - angularjs

I have a hard time trying to test a modal controller (created using Angular UI Bootstrap).
I dumbed down the test code as much as I could but I am still getting an error.
Here's the modal controller (part of it):
var controllersModule = angular.module('forge.geomanagement.controllers');
controllersModule.controller('EditGeofenceModalController', function ($timeout, $scope: , $modalInstance, forgeGeoTriggerService, $rootScope, geofence, triggerID) {
var searchAddressInput: HTMLInputElement;
//make a copy of geofence obj passed into modal
$scope.geofence = {
FriendlyName: geofence.FriendlyName,
Coords: angular.copy(geofence.Boundary),
GeoTags: angular.copy(geofence.GeoTags)
};
$scope.goefenceID = triggerID;
var gCLength = $scope.geofence.Coords.length;
//wrap it in timeout function to paint the map after its container is rendered
$timeout(function () {
$scope.geofenceMap = new google.maps.Map(document.getElementById('map_canvas'), $scope.mapOptions);
//autocomplete functionality
searchAddressInput = <HTMLInputElement>document.getElementById('pac-input');
$scope.autocomplete = new google.maps.places.Autocomplete(searchAddressInput, $scope.mapOptions);
$scope.autocomplete.bindTo('bounds', $scope.geofenceMap); //set autocomplete suggestion bounds to map's current viewport
//bind autocomplete to the map
google.maps.event.addListener($scope.autocomplete, 'place_changed', function () {
$scope.place = $scope.autocomplete.getPlace();
$scope.geofenceMap.panTo($scope.place.geometry.location);
$scope.geofenceMap.setZoom(12);
$scope.model.searchAddress = $scope.place.formatted_address;
$scope.$digest();
});
//GEOFENCE FUNCTIONALITY
forgeGeoTriggerService.GeofenceCreator($scope.geofenceMap, $scope.geofence.Coords);
//show geofence in edit mode
forgeGeoTriggerService.ShowGeofence($scope.geofenceMap, $scope.geofence.Coords);
$scope.$on("polygonPath.updated", function (event, geofenceCoords) {
$scope.$apply(function () {
$scope.geofence.Coords = geofenceCoords;
});
});
//clear geofence area btn
$scope.clearGeofenceArea = function () {
forgeGeoTriggerService.ClearGeofenceArea();
$scope.geofence.Coords.length = 0; // clear geofence array
};
}, 0);
$scope.cancel = function () {
$modalInstance.close()
};
$scope.saveGeofence = function () {
forgeGeoTriggerService.EditGeofence($scope.geofence, $scope.goefenceID)
.then(function (data) {
$scope.successMessage = 'Geofence Updated Successfully'
$rootScope.$broadcast('geotrigger.edited');
$timeout(function () {
$modalInstance.close();
}, 2000);
}, function (data) {
$scope.errorMessage = 'There was an error when updating geofence. Please try again.';
});
}
});
This is modal controller test
describe("forge.geomanagement.GeoApp", function () {
var scope, controller, modalInstance, timeout, forgeGeoTriggerService, window = {},
geofencemock, geofence, triggerID;
beforeEach(module('forge.geomanagement.GeoApp'));
describe("Controller: EditGeofenceModalController", function () {
beforeEach(inject(function ($controller, $rootScope, $timeout, _forgeGeoTriggerService_) {
scope = $rootScope.$new();
timeout = $timeout;
modalInstance = {
close: jasmine.createSpy('modalInstance.close'),
dismiss: jasmine.createSpy('modalInstance.dismiss'),
result: {
then: jasmine.createSpy('modalInstance.result.then')
}
}
geofencemock = {
FriendlyName: 'mock geofence',
Coords: [
{
"lat": 53.5598889724547,
"lng": -6.36953830718994
},
{
"lat": 53.463525599115,
"lng": -6.53707981109619
},
{
"lat": 53.3685818160803,
"lng": -6.46841526031494
},
{
"lat": 53.384966558115,
"lng": -5.75430393218994
},
{
"lat": 53.5598889724547,
"lng": -6.34756565093994
},
{
"lat": 53.5598889724547,
"lng": -6.36953830718994
}
],
GeoTags: ['tag1','tag2','tag3']
}
triggerIDmock = 1;
forgeGeoTriggerService = _forgeGeoTriggerService_;
controller = $controller("EditGeofenceModalController", {
$scope: scope,
$timeout: timeout,
$modalInstance: modalInstance,
forgeGeoTriggerService: forgeGeoTriggerService,
geofence: geofencemock,
triggerID: triggerIDmock
});
}));
it('2 is 2', function () {
expect(2).toBe(2);
})
it("geofence should be defined", function () {
expect(geofencemock).toBeDefined();
});
it("should contain reference to forgeGeoTriggerService", function () {
expect(forgeGeoTriggerService).not.toBeNull();
});
it("$modalInstance obj should be defined when modal is open", function () {
expect(modalInstance).toBeDefined();
});
it("cancel function should close edit geofence modal", function () {
scope.cancel();
expect(modalInstance.close).toHaveBeenCalled();
});
});
});
But when I try to run it I get the error: "Cannot read property length of undefined" that corresponds to $scope.geofence.Coords property - an array that is successfully copied over to modal from parent controller. As you can see, I also created a geofencemock object and tried to use it in a very simple test but it looks like it's not being picked up. I would really appreciate some input, cause I have already spent couple of hours trying to fix it or find a solution online, but to no avail.
Thanks.

You're setting $scope.geofence.Coords from geofence.Boundary:
$scope.geofence = {
FriendlyName: geofence.FriendlyName,
Coords: angular.copy(geofence.Boundary),
GeoTags: angular.copy(geofence.GeoTags)
};
But you're mocking geofence with Coords directly:
geofencemock = {
FriendlyName: 'mock geofence',
Coords: [
{
"lat": 53.5598889724547,
"lng": -6.36953830718994
},
Change the latter to be geofencemock.Boundary and you should be fine.

Ok, I got it working. The error "Cannot read property 'lat' of undefined" was related not to the coordinates in geofence mock object but to the geofence.Center.lat property I was using in my controller with geofence.Center.lng to position the center of the map.
Let me explain: we get the polygon details from the server, then we pass them into edit modal window (Angular UI Bootstrap):
forgeGeoTriggerService.GetGeoFence(geotriggerID)
.then(function (geofenceData) {
$scope.modalInstance = $modal.open({
windowClass: 'popup-geofence-modal',
templateUrl: TemplateUrlProvider.GetUrl('GeofenceModal'),
controller: 'EditGeofenceModalController',
resolve: {//pass geofenceData from server to geofence obj inside modal
geofence: function () {
return geofenceData;
},
triggerID: function () {
return geotriggerID
}
}
});
}, function (error) {
$scope.errorMessage = 'There was an error when trying to fetch geofencene details. Please try again later.';
});
Then in EditGeofenceModalController we make use of the geofence object passed from the parent controller above
'use strict';
var controllersModule = angular.module('forge.geomanagement.controllers');
controllersModule.controller('EditGeofenceModalController', function ($timeout, $scope, $modalInstance, forgeGeoTriggerService, $rootScope, geofence, triggerID) {
var searchAddressInput: HTMLInputElement;
//make a copy of geofence obj passed into modal
$scope.geofence = {
FriendlyName: geofence.FriendlyName,
Coords: angular.copy(geofence.Boundary),
GeoTags: angular.copy(geofence.GeoTags)
};
$scope.goefenceID = triggerID;
var gCLength = $scope.geofence.Coords.length;
//if first and last coords are the same - remove the last one
if ($scope.geofence.Coords[0].lat === $scope.geofence.Coords[gCLength - 1].lat
&& $scope.geofence.Coords[0].lng === $scope.geofence.Coords[gCLength - 1].lng) {
$scope.geofence.Coords.pop();
}
//!!!!!!!set the map center to geofence.Center
var geofenceCenter: google.maps.LatLng = new google.maps.LatLng(
geofence.Center.lat, geofence.Center.lng
);
Pay attention to the comment line with exclamation marks. This is where I set the center of the map. The geofence object returned from the server has a Center property - an obj with lat and lng properties. Once I changed the Coords to Boundary in my geofencemock obj in test as #rayners suggested, it was still missing the Center property. Setting it like that in the test file fixed the problem and my tests passed:
geofencemock = {
FriendlyName: 'mock geofence',
Boundary: [
{
"lat": 53.5598889724547,
"lng": -6.36953830718994
},
{
"lat": 53.463525599115,
"lng": -6.53707981109619
},
{
"lat": 53.3685818160803,
"lng": -6.46841526031494
},
{
"lat": 53.384966558115,
"lng": -5.75430393218994
},
{
"lat": 53.5598889724547,
"lng": -6.34756565093994
},
{
"lat": 53.5598889724547,
"lng": -6.36953830718994
}
],
GeoTags: ['tag1', 'tag2', 'tag3'],
Center: {
"lat": 53.46769593973309,
"lng": -6.2952017905716735
}
}

Related

Dynamic id name

I got this template:
<ion-view view-title="MAP" name="tab-map">
<ion-content has-tabs="true" style="text-align:center;">
<div style="width:100%;height:400px;" ng-attr-id="{{'canvas_map_'+place.id}}"></div>
</ion-content>
</ion-view>
and I'm trying to set div's content dynamically by passing through controller:
angular.module('helloDynamicMap', ['ionic'])
.config(function ($stateProvider, $urlRouterProvider, $ionicConfigProvider) {
$ionicConfigProvider.navBar.alignTitle('center');
$ionicConfigProvider.tabs.position('bottom');
$ionicConfigProvider.backButton.text('').icon('ion-chevron-left');
$ionicConfigProvider.backButton.previousTitleText(false);
$stateProvider
.state('main', {
url: "/main",
controller: "PlacesCtrl",
//abstract: true,
templateUrl: 'templates/main.html',
resolve: {
resultPlaces: function (findPlace, $stateParams) {
return findPlace.all();
}
}
})
.state('place', {
url: "/place/:placeId",
//abstract: true,
templateUrl: 'templates/place-tabs.html',
controller: 'PlaceCtrl',
resolve: {
resultPlace: function (findPlace, $stateParams) {
return findPlace.get($stateParams.placeId);
}
}
})
.state('place.details', {
url: '/details',
views: {
'tab-details': {
templateUrl: 'templates/details.html'
},
}
})
.state('place.map', {
url: '/map',
views: {
'tab-map': {
templateUrl: 'templates/map.html' //,
//controller: "MapCtrl"
}
}
});
$urlRouterProvider.otherwise("/main");
})
.controller('AppCtrl', function ($scope, $state, $rootScope) {
// button back
$scope.goBack = function () {
console.log("back button");
$state.go("main");
}
})
.controller('PlacesCtrl', function ($scope, $rootScope, $state, resultPlaces) {
// Get places
$scope.places = resultPlaces;
// Button back
$scope.goTabs = function () {
$state.go("tab.map");
}
})
.controller('PlaceCtrl', function ($scope, $rootScope, $state, resultPlace) {
// Load place's data in scope's model
$scope.place = resultPlace;
var div = document.getElementById("canvas_map_" + resultPlace.id);
console.log("div", div);
// Create Map
var map;
document.addEventListener("deviceready", function () {
var div = document.getElementById("canvas_map_" + resultPlace.id);
// Initialize the map view
map = plugin.google.maps.Map.getMap(div);
// Wait until the map is ready status.
map.addEventListener(plugin.google.maps.event.MAP_READY, onMapReady);
}, false);
function onMapReady() {
var button = document.getElementById("button_" + resultPlace.id);
button.addEventListener("click", onBtnClicked, false);
}
function onBtnClicked() {
map.showDialog();
}
})
.factory('findPlace', function ($q) {
// list all places
var places = [];
places = [{
id: "1",
name: "Place A",
details: "details for Place A",
latitude: "28.472143",
longitude: "-81.469856"
}, {
id: "2",
name: "Place B",
details: "details for Place B",
latitude: "",
longitude: ""
}, {
id: "3",
name: "Place C",
details: "details for Place C",
latitude: "",
longitude: ""
}];
return {
all: function () {
var dfd = $q.defer();
// resolve promise
dfd.resolve(places);
// return promise
return dfd.promise;
},
get: function (placeId) {
for (var i = 0; i < places.length; i++) {
if (places[i].id == parseInt(placeId)) {
return places[i];
}
}
return null;
}
}
})
This code works perfectly when I use static id name as "canvas_map_01", but doesn't works when I tries to set it through scope variables.
The main question is how to set and handle div elements through dynamic id name:
// Load place's data in scope's model
$scope.place = resultPlace;
var div = document.getElementById("canvas_map_" + resultPlace.id);
console.log("div", div);
My full code is on Github, please somebody can tell me what am I doing wrong?
-Change your html from
ng-attr-id="{{'canvas_map_'+place.id}}"
To
id="{{'canvas_map_'+place.id}}"
-Change your code from
var div = document.getElementById("canvas_map_" + resultPlace.id);
To
var div = angular.element("#canvas_map_" + resultPlace.id);
Than it will work fine

AngularJS Testing with Mocha and Sinon - Mocking Firebase Dependencies

I am trying to skip going to Firebase for my data in my tests, and return some simple results instead. I do not want to test that the Firebase code works, but that my factories work in returning data.
I have the following factories:
// Get Firebase Reference
angular.module('MyApp').factory('FireBaseData', function(FIREBASE) {
var FireBaseReference = new Firebase(FIREBASE.URL); // FIREBASE.URL = xxx.firebaseio.com
return FireBaseReference;
})
// Get the Firebase Array of Team Records
angular.module('MyApp').factory('AllTeams', ["FireBaseData", "$firebaseArray",
function(FireBaseData, $firebaseArray) {
return $firebaseArray(FireBaseData.child('Teams'));
}
]);
I have created mocks that replace the individual functions, and my tests will use these.
'use strict';
var $MockFirebaseArray = function(ArrayWithData) {
return ArrayWithData;
};
var $MockFirebaseObject = function(ObjectWithData) {
return ObjectWithData;
};
var MockFirebaseData = function() {
return {
child: function(StringValue) {
return "";
}
};
};
Tests with the mocks:
'use strict';
describe('Firebase Mocks', function() {
var TestArray = [
{ 'aString': 'alpha', 'aNumber': 1, 'aBoolean': false },
{ 'aString': 'bravo', 'aNumber': 2, 'aBoolean': true },
{ 'aString': 'charlie', 'aNumber': 3, 'aBoolean': true },
{ 'aString': 'delta', 'aNumber': 4, 'aBoolean': true },
{ 'aString': 'echo', 'aNumber': 5 }
];
describe('MockFirebaseData', function() {
var TestFirebase = MockFirebaseData();
it('should return empty text ("") from FireBaseData', function() {
assert.equal('', TestFirebase.child('SomeNode'));
});
});
describe('$MockFirebaseArray', function() {
it('should have the data array passed', function() {
var TestData = $MockFirebaseArray(TestArray);
assert.equal(TestArray.length, TestData.length);
});
});
describe('$MockFirebaseObject', function() {
it('should have the data object passed', function() {
var TestData = $MockFirebaseObject(TestArray[0]);
assert.equal(TestArray[0].length, TestData.length);
assert.deepEqual(TestArray[0], TestData);
});
});
});
This shows that the Mocks are working to return data, which is what I want to stay away from actually accessing Firebase. Now, when I try to use my factory in a test, I am getting errors.
Test the Factory:
describe('Teams Module', function() {
beforeEach(module('MyApp')); // Load My Application
describe('AllTeams Array', function() {
// Create Test Data
var TeamData = [
{ "Key": 1, "Name":"Team 1", "Logo": "Team1.jpg" },
{ "Key": 3, "Name":"Team 3", "Logo": "Team3.jpg" },
{ "Key": 2, "Name":"Team 2", "Logo": "Team2.jpg" },
];
beforeEach(function () {
module(function($provide) {
var MockData = MockFirebaseData();
$provide.value('FireBaseData', MockData);
$provide.value('$firebaseArray', $MockFirebaseArray(TeamData));
});
});
it('can get an instance of AllTeams factory', inject(function(AllTeams) {
assert.isDefined(AllTeams);
}));
});
});
Error returned:
PhantomJS 1.9.8 (Windows 7 0.0.0)
Teams Module
AllTeams Array
can get an instance of AllTeams factory FAILED
TypeError: '[object Object],[object Object],[object Object]' is not a function (evaluating '$firebaseArray(FireBaseData.child('Teams'))')
at app/Team.js:9
Instead of:
$provide.value('$firebaseArray', $MockFirebaseArray(TeamData));
try this:
$provide.value('$firebaseArray', $MockFirebaseArray);
I believe this is what you were intending to do in the first place. When injected, your factory will then be able to call $firebaseArray as a function.

Ionic + ngCordova + background geolocation + DeviceReady issue

I'm trying to get the background geolocation plugin to work in my app; however, the page only sometimes loads on my device when I use the deviceready function. From my googling, it seems that I should be using $ionicPlatform.ready instead, but $cordovaBackgroundGeolocation is undefined when I try to do that. Similarly, the device is always undefined when I try to do anything with it.
I also tried manually bootstrapping angular, that didn't work; and I tried simply running the function without putting it inside deviceready or $ionicPlatform.ready or anything; that didn't work either.
The code in question:
Controller:
// Define the angular module
angular.module('testApp.controllers', ['ionic', 'ngCordova.plugins.geolocation', 'ngCordova.plugins.backgroundGeolocation'])
.controller('MapCtrl', ['$scope', '$ionicPopup', '$cordovaGeolocation', '$cordovaBackgroundGeolocation', '$timeout', '$http', '$ionicPlatform',
function ($scope, $ionicPopup, $cordovaGeolocation, $cordovaBackgroundGeolocation, $timeout, $http, $ionicPlatform) {
$scope.loaded = false;
var posOptions = { timeout: 5000, enableHighAccuracy: true, maximumAge: 5000 };
$cordovaGeolocation.getCurrentPosition(posOptions)
.then(function (location) {
$scope.currentLat = location.coords.latitude;
$scope.currentLong = location.coords.longitude;
$scope.loaded = true;
});
$ionicPlatform.ready(function() {
var bgGeo = $cordovaBackgroundGeolocation;
// BackgroundGeoLocation is highly configurable.
bgGeo.configure({
url: 'http://www.my_api_url_here/',
params: {
deviceId: "testApp",
"location": {
"latitude": "38.896339999999995",
"longitude": "-77.08521460000001"
}
},
desiredAccuracy: 10,
stationaryRadius: 20,
distanceFilter: 30,
notificationTitle: 'TestTitle', // <-- android only, customize the title of the notification
notificationText: 'TestText', // <-- android only, customize the text of the notification
activityType: 'OtherNavigation',
debug: true, // <-- enable this hear sounds for background-geolocation life-cycle.
stopOnTerminate: false // <-- enable this to clear background location settings when the app terminates
});
bgGeo.start();
});
}])
Directive:
.directive('bgeo', ['$cordovaGeolocation', '$cordovaBackgroundGeolocation', '$http',
function ($cordovaGeolocation, $cordovaBackgroundGeolocation, $http) {
return {
scope: {
lat: '=',
lng: '='
},
link: function (scope) {
console.log("directive: ", scope.lat, scope.lng);
myLatLng = new google.maps.LatLng(scope.lat, scope.lng);
mapOptions = {
zoom: 16,
center: myLatLng
};
map = new google.maps.Map(document.getElementById('map'), mapOptions);
marker = new google.maps.Marker({
position: myLatLng,
map: map,
draggable: false,
icon: 'small-orange-pin.png'
});
}
}
}])
Template:
<ion-scroll zooming="true" direction="xy" style="width:90%">
<div ng-if="loaded" bgeo lat="currentLat" lng="currentLong">
<div id="map" style="width: 600px; height: 500px;"></div>
</div>
</ion-scroll>
app.js run method:
.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.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if(window.StatusBar) {
StatusBar.styleDefault();
}
if (window.cordova) {
if (window.plugins && window.plugins.backgroundGeoLocation) {
BackgroundGeolocation.configurePlugin(window.plugins.backgroundGeoLocation);
}
}
});
})
The full source code is up on github at https://github.com/sahiltalwar88/binding-geolocation-issue. Any help is much appreciated!
The main issue was that I had to run bower install; I was missing several packages. Once I did that, I could use the ionic ready function and onDeviceReady just fine. Then, in order to get the iOS callback functions working, I had to update my syntax to work with ngCordova (which uses Q and promises) rather than callback functions, as the examples showed.
Here's the structure of my final code:
$ionicPlatform.ready(function() {
if(window.StatusBar) {
StatusBar.styleDefault();
}
$location.path('/app');
$rootScope.$digest();
$rootScope.deviceReady = false;
document.addEventListener('deviceready', function () {
if(window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
window.cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
var bgGeo = $cordovaBackgroundGeolocation;
var deviceId = $cordovaDevice.getUUID();
var addVisitUrl = 'your-url-goes-here';
$rootScope.deviceId = deviceId;
$rootScope.deviceReady = true;
var posOptions = { timeout: 5000, enableHighAccuracy: true, maximumAge: 5000 };
$cordovaGeolocation.getCurrentPosition(posOptions)
.then(function (location) {
$rootScope.currentLat = location.coords.latitude;
$rootScope.currentLong = location.coords.longitude;
var yourAjaxCallback = function(response) {
bgGeo.finish();
};
var callbackFn = function(location) {
var data = {
deviceId: deviceId,
"location": {
"latitude": location.latitude,
"longitude": location.longitude
}
};
$http.post(addVisitUrl, data);
// Other code goes here
yourAjaxCallback.call(this);
};
var failureFn = function(error) {
alert('Background Geolocation Error: ' + error);
// Other code goes here
};
bgGeo.configure({
url: addVisitUrl,
params: {
deviceId: deviceId,
"location": {
"latitude": $rootScope.currentLat,
"longitude": $rootScope.currentLong
}
},
desiredAccuracy: 10,
stationaryRadius: 10,
distanceFilter: 10,
activityType: 'OtherNavigation',
debug: true,
stopOnTerminate: false
})
.then(callbackFn, failureFn, callbackFn);
bgGeo.start();
});
$rootScope.$digest();
});
});
})

Restangular: first call returns array but subsequent calls return object

I have a AngularJS factory 'UserSrvc'. This is responsible for calling a RESTful back end to get and create user accounts using Restangular:
(function () {
'use strict';
angular
.module('myapp')
.factory('UserSrvc', UserSrvc);
function UserSrvc(Restangular) {
return {
getAllUsers: getAllUsers,
getUser: getUser,
saveUser: saveUser
};
/////////////////////
function getAllUsers(){
return Restangular.all('users').getList();
}
function getUser(user){
return Restangular.setFullResponse(true).one('users', user).get();
}
function saveUser(user) {
return Restangular.all('users').post(user);
}
};
})();
My User controller then has functions for initializing the data for loading in to Angular UI Grid as well as functions for saving a user and getting user data:
(function () {
'use strict';
var controllerId = 'UserCtrl';
// Define the controller on the module
// Inject the dependencies.
// Point to the controller definition function.
angular
.module('myapp')
.controller(controllerId, UserCtrl, ['UserSrvc', 'ngDialog', '$log', 'toaster']);
function UserCtrl(UserSrvc, ngDialog, $log, toaster){
// Using the 'Controller As' syntax, so we assign to the vm variable (for view model).
var vm = this;
var allUsers = [];
// Bindable properties and functions are placed on vm.
vm.activate = activate;
vm.allUsers = {};
vm.toggleForm = false;
vm.saveUser = saveUser;
vm.gridOptions = {
data: allUsers,
enableSorting: true,
enableColumnResizing: true,
enableGridMenu: true,
showGridFooter: true,
showColumnFooter: true,
enableFiltering: true,
columnDefs: [
{name: 'firstName', field: 'First'},
{name: 'lastName', field: 'Last'},
{name: 'login', field: 'Login'},
{name: 'email', field: 'Email'}
]
};
activate();
function activate() {
return getUsers().then(function() {
// User Controller is now activated
$log.info('UserCtrl activated');
});
}
function refreshUserTable() {
return UserSrvc.getAllUsers()
.then(function(data) {
// User table refresh
vm.gridOptions.data = data.data;
$log.info('User table data refreshed.', vm.gridOptions.data);
});
}
function getUsers() {
return UserSrvc.getAllUsers()
.then(function (data) {
$log.debug('data: ', data);
vm.gridOptions.data = data;
//allUsers = data;
$log.debug('allUsers: ', vm.gridOptions.data);
return vm.gridOptions.data;
},
function(response) {
$log.debug("Failed to get users, error with status code", response.status);
});
}
function saveUser(vm) {
var new_user = {
"user": {
"First": vm.user.firstname,
"Last": vm.user.surname,
"Login": vm.user.username,
"Password": vm.user.password,
"Email": vm.user.email
}
};
//$log.debug('The user to be saved: ', user);
return UserSrvc.saveUser(new_user)
.then(function (data) {
$log.debug('The user to be saved: ', new_user);
$log.debug('response: ', data);
// Refresh the table
refreshUserTable(vm);
// Reset the user form
resetForm();
// Close the form
vm.toggleForm = !vm.toggleForm;
// Success toast
toaster.pop("success","User saved", "User '" + new_user.user.Login + "' successfully created");
return data;
},
function(response) {
$log.debug("Failed to save user, error with status code", response.status);
toaster.pop("error", "Unable to save user", "Failed to save user, error with status code " + response.status);
});
}
}
})();
On the first call to UserSrvc.getAllUsers() in the getUsers() function the data parameter from the .then(function(data) returns an array like so:
[
{
"Last": "Jobs",
"Email": "test#example.com",
"Login": "jobs",
"id": 1,
"First": "Steve"
}
]
However, subsequent calls made by refreshUserTable() to the same UserSrvc.getAllUsers(), the data parameter from .then(function(data)) returns an object like so:
{
"data": [
{
"Last": "Jobs",
"Email": "test#example.com",
"Login": "jobs",
"id": 1,
"First": "Steve"
}
]
}
To get it to work I need to pull the data array from the data object by doing data.data.
Why is it that subsequent calls made by the refreshUserTable() return an object and not an array? My suspicion is that it has something to do with the way in which I'm using Restangular or is there something glaringly obvious I've missed?
Ideally I'd like to get rid of the refreshUserTable() function and just use the getAllUsers() to refresh the table.
you set setFullResponse to true which extend your response object. You confused because Restangular uses same property key with you data.
If you want to use full response specifically on one method just use withConfig method of Restangular.
Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setFullResponse(true);
});

Angular UI Modal, avoid modal scope change reflects in parent scope

I am building an Angular app where I want to control which apps in app list I want to show on home page. There is a section called "Manage Apps" where I can manage visible apps..
http://plnkr.co/edit/RPFvv0ZUB2OSctIQM8pQ?p=preview
The plunkr above explains what I want to achieve..
I have passed list of apps in json from parent scope to modal instance. I want to make changes to one field there which is IsPublished.
Now the problem is, whenever I make changes in isPublished field in Modal, it immediately gets reflected in parent scope. You can see apps being filtered in parent scope behind overlay..
I want to avoid it. I want to reflect the changes to parent scope only when I hit save / ok button.
is there any way to do so?
You need a deep copy of a source use angular.copy.The changes directly reflected to main screen because you bind $scope.apps with $scope.items and hence both are refrencing to the same location.
angular.module('ui.bootstrap.demo', ['ui.bootstrap']);
angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $modal, $log) {
$scope.items = ['item1', 'item2', 'item3'];
$scope.apps = [
{
"FileSystemObjectType":0,
"Id":1,
"ContentTypeId":"0x01008178C725CC128447AD122168CA03E484",
"Title":"First App",
"AppUrl":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"http://www.google.com",
"Url":"http://www.google.com"
},
"AppIcon":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"https://unsplash.it/150/?random",
"Url":"https://unsplash.it/150/?random"
},
"CanDelete":false,
"IsPublished":false,
"Priority":null,
"ID":1,
"Modified":"2015-03-04T15:44:36Z",
"Created":"2015-02-26T05:24:00Z",
"AuthorId":9,
"EditorId":9,
"OData__UIVersionString":"1.0",
"Attachments":false,
"GUID":"5a3e620d-461c-4663-8837-36bfd2967dad"
},
{
"FileSystemObjectType":0,
"Id":2,
"ContentTypeId":"0x01008178C725CC128447AD122168CA03E484",
"Title":"App 2",
"AppUrl":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"http://microsoft.com",
"Url":"http://microsoft.com"
},
"AppIcon":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"https://unsplash.it/150/?random",
"Url":"https://unsplash.it/150/?random"
},
"CanDelete":true,
"IsPublished":false,
"Priority":null,
"ID":2,
"Modified":"2015-03-04T15:44:36Z",
"Created":"2015-02-26T05:25:11Z",
"AuthorId":9,
"EditorId":9,
"OData__UIVersionString":"1.0",
"Attachments":false,
"GUID":"e919eb66-0f2b-4ed4-aad9-3b64400bf09a"
},
{
"FileSystemObjectType":0,
"Id":3,
"ContentTypeId":"0x01008178C725CC128447AD122168CA03E484",
"Title":"App 3",
"AppUrl":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"http://google.com",
"Url":"http://google.com"
},
"AppIcon":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"https://unsplash.it/150/?random",
"Url":"https://unsplash.it/150/?random"
},
"CanDelete":true,
"IsPublished":true,
"Priority":0,
"ID":3,
"Modified":"2015-03-04T15:44:36Z",
"Created":"2015-02-26T08:06:23Z",
"AuthorId":9,
"EditorId":9,
"OData__UIVersionString":"1.0",
"Attachments":false,
"GUID":"07a63d11-fe94-4fc2-95fc-b7ddf16f160a"
},
{
"FileSystemObjectType":0,
"Id":4,
"ContentTypeId":"0x01008178C725CC128447AD122168CA03E484",
"Title":"Test1",
"AppUrl":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"http://www.attini.com",
"Url":"http://www.attini.com"
},
"AppIcon":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"https://unsplash.it/150/?random",
"Url":"https://unsplash.it/150/?random"
},
"CanDelete":true,
"IsPublished":true,
"Priority":1,
"ID":4,
"Modified":"2015-03-04T15:44:36Z",
"Created":"2015-02-27T03:58:37Z",
"AuthorId":9,
"EditorId":9,
"OData__UIVersionString":"1.0",
"Attachments":false,
"GUID":"9375eff9-4101-4c1f-ad85-bedc484b355f"
}
];
$scope.open = function (size) {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $scope.apps;
}
}
});
modalInstance.result.then(function (items) {
$scope.apps = angular.copy(items);
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
});
// Please note that $modalInstance represents a modal window (instance) dependency.
// It is not the same as the $modal service used above.
angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($scope, $modalInstance, items) {
$scope.items = angular.copy(items);
$scope.selected = {
item: $scope.items[0]
};
$scope.ok = function () {
$modalInstance.close($scope.items);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});
Working Plunker

Resources