Set and use variables in Angular - angularjs

I created an Angular controller to list ebooks as follows:
function EBookListController() {
var vm = this;
vm.ebooks = [];
vm.catalogSlug = null;
vm.pagination = { pageNumber: 1, pageSize: 9 };
vm.init = function (catalogSlug) {
vm.catalogSlug = catalogSlug;
load(vm.pageNumber, vm.pageSize);
};
vm.load = function (pageNumber, pageSize) {
// Call service to load ebooks
};
}
Should I define pagination default values in vm.pagination initialization or in init function?
Should load function have pageNumber and pageSize parameters or just use vm.pagination values?
Basically I am not sure if the functions should use the vm parameters values or always take them as arguments ...

Keep the pageSize a scope variable which can be accessed by page rendering functions.
It is enough to set the catalogSlug inside the init() function,
var vm = this;
vm.ebooks = [];
vm.pageSize = 9;
vm.init = function (catalogSlug) {
vm.catalogSlug = catalogSlug;
vm.load(1);
};
vm.load = function(pageNumber) {
// Call service to load ebooks
// Render the pageNumber and use vm.pageSize to set it's size.
};

Related

Singleton JS Object in Angular Service

I am trying to add methods to an Object's protoype, which will be used in a singleton service and will be initiated only once when the service is created.
angular
.module('app.steps')
.factory('stepsService', stepsService);
stepsService.$inject = [];
/* #ngInject */
function stepsService() {
var steps = new Steps(1,3);
function Steps(current_step, total_steps) {
this.c_step = current_step;
this.t_step = total_steps;
}
Steps.prototype = {
addSteps: function (num) {
this.c_step += num;
},
setLastStep: function () {
this.lastStep = this.c_step = this.t_step;
}
};
var service = {
steps: steps
};
return service;
}
My problem is that although the object is created and initiated successfully, the methods are not there.
What is missing?
As mentioned in the comments, var steps = new Steps(1,3); should be after Steps.prototype = {....}

2 way binding issues with directives, controllers and services

This is bugging me a bit.
I have a service that handles logo panels and a function that is used to navigate between the different panels.
When getPanels is invoked it sets the currentPanel, index and length on the service when all promises have completed (see $q.all in the getPanels method):
.service('ConfiguratorLogoService', ['$q', 'UploadService', 'LogoService', 'ArrayService', 'SvgService', function ($q, uploadService, logoService, arrayService, helper) {
// Private function to build a file array
var _buildFileArray = function (panels, files) {
//--- Omitted for brevity ---//
};
// Create our service
var service = {
// Create our arrays
panels: [],
files: [],
currentPanel: null,
index: 0,
length: 0,
// Get our panels
getPanels: function (container, garmentId) {
// Create a deferred promise
var deferred = $q.defer();
// Create our arrays
var panels = []
files = [],
promises = [];
// If we have a container
if (container) {
// Get the containers children
var children = container.children()
// Loop through our panel's children
for (var i = 0; i < children.length; i++) {
// Get the current child
var child = angular.element(children[i]),
childId = child.attr('id'),
childTitle = helper.extractText(childId, ':', 1);
// Create our item
var panel = {
id: childId,
title: childTitle
};
// Try to get our item
promises.push(logoService.get(garmentId, panel.id).then(function (response) {
// If we have any data
if (response) {
// Add the file to our array
files.push(response);
}
}));
// Add our child to the array
panels.push(panel);
}
}
// After all the promises have been handled
$q.all(promises).then(function () {
// Get our files
service.files = _buildFileArray(panels, files);
service.panels = panels;
service.currentPanel = panels[0];
service.length = panels.length;
// Resolve our promise
deferred.resolve({
files: service.files,
panels: panels
});
});
// Return our promise
return deferred.promise;
},
// Get our next panel
navigateNext: function () {
// Create a deferred promise
var deferred = $q.defer();
// Get the next index or reset if we reached the end of our list
service.index = service.index === (service.length - 1) ? 0 : service.index += 1;
// Set our active panel
service.currentPanel = service.panels[service.index];
console.log(service.index);
// Resolve our promise
deferred.resolve();
// Return our promise
return deferred.promise;
},
// Get our previous panel
navigatePrevious: function () {
// Get the previous index or set to the end of our list
service.index = service.index === 0 ? service.length - 1 : service.index -= 1;
// Set our active panel
service.currentPanel = service.panels[service.index];
},
// Removes the file from azure
remove: function (index) {
//--- Omitted for brevity ---//
}
};
// Return our service
return service;
}])
which is fine, it works and the first panel is selected.
So, I have a controller, which is attached to a directive. The controller looks like this:
.controller('ConfiguratorLogosDirectiveController', ['ConfiguratorLogoService', 'RowService', function (service, rowService) {
var self = this;
// Set our current panel
self.currentPanel = service.currentPanel;
self.index = service.index;
self.length = service.length;
// Initialization
self.init = function (container, garmentId) {
// Get our panels
return service.getPanels(container, garmentId).then(function (response) {
self.panels = response.panels;
self.files = response.files;
// If we have any panels
if (self.panels.length) {
// Set our current panel
self.currentPanel = service.currentPanel;
self.index = service.index;
self.length = service.length;
}
// Return our response
return response;
})
};
// Map our service functions
self.upload = service.upload;
self.next = service.navigateNext;
self.previous = service.navigatePrevious;
self.remove = service.remove;
}])
As you can see, when I get my panels, I set the currentPanel, index and length on the controller itself which I didn't think I would have to do because when the controller is invoked, it already has a reference to the service values. I figured 2 way binding would come into play and when the service values update, the controller would update too.
Anyway, I update the values after the getPanels method completes successfully. In my directive I have this:
// Invoke on controller load
controller.init(container, scope.garmentId).then(function (response) {
// Map our properties
scope.panels = controller.panels;
scope.files = controller.files;
scope.currentPanel = controller.currentPanel;
scope.index = controller.index;
scope.length = controller.length;
});
which again works fine. In my template I can see the first panel and it looks fine.
So, then came the next step which was my navigate functions. I started with next which I have modified for testing purposes so I can output the controller.index as well as the console.log in the service navigation function.
The directive function looks like this:
scope.next = function () {
controller.next().then(function () {
console.log(controller.index);
});
};
When this method is invoked, I can see in my console that the service increases the index by 1 but the controller still shows 0 which means that 2 way binding is not working.
I am about to update my method in the controller to push the currentPanel and index to the controller, but before I do I thought I would ask here first.
So, does anyone know why my 2 way binding isn't working?
So my current workaround works, but I just don't like it.
In my directive I have done this:
scope.next = function () {
controller.next().then(function () {
console.log(controller.index);
scope.currentPanel = controller.currentPanel;
scope.index = controller.index;
scope.length = controller.length;
});
}
and in the directive controller I have done this:
self.next = function () {
// Try to navigate forward
return service.navigateNext().then(function () {
// Set our current panel
self.currentPanel = service.currentPanel;
self.index = service.index;
self.length = service.length;
console.log(self.index);
});
}
and in my service, it looks the same as before:
// Get our next panel
navigateNext: function () {
// Create a deferred promise
var deferred = $q.defer();
// Get the next index or reset if we reached the end of our list
service.index = service.index === (service.length - 1) ? 0 : service.index += 1;
// Set our active panel
service.currentPanel = service.panels[service.index];
console.log(service.index);
// Resolve our promise
deferred.resolve();
// Return our promise
return deferred.promise;
},
This works, but surely this is not the way it should work.
I have figured it out thanks to this article.
I just had to create an object in my directive and bind the values to that.
Doing that fixed the issues.

Angular service not assigning value to empty object

I have a service:
storeApp.service('currentCustomer',function($http) {
this.customerID = 0;
this.customerInfo = {}
this.customerAttributes = {}
this.getCustomerInfo = function () {
if (this.customerID != 0) {
$http.get('/customers/' + this.customerID).
then(function (result) {
this.customerInfo = result.data[0]
})
}
}
and a controller:
storeApp.controller('storeList',function($scope,$http,currentCustomer) {
$scope.changeCust = function changeCust(id) {
currentCustomer.customerID = id;
currentCustomer.getCustomerInfo()
console.log("After Change customer:")
console.log(currentCustomer)
}
$scope.selectedStore = currentCustomer
});
If I try to access selectedStore.customerID, I get values.
If I try to access selectedStore.customerInfo, I get an empty array, even though when i put console logging in to check the values, it says they are assigned.
Any ideas what I'm doing wrong? Thanks everyone.
You are manually assigning a value to CustomerId, and your service method is assigning a value to customerInfo. Except this in the service method, is not the same as this in the service. You should instantiate a var self = this; reference inside the service and use this value in all your object manipulation. eg: self.customerInfo = ....
Your reference for this has been changed inside function. first store this reference in some variable and then assign properties, some prefer to use the word self but I prefer service
storeApp.service('currentCustomer',function($http) {
var service = this;
service.customerID = 0;
service.customerInfo = {}
service.customerAttributes = {}
service.getCustomerInfo = function () {
if (service.customerID != 0) {
$http.get('/customers/' + this.customerID).
then(function (result) {
service.customerInfo = result.data[0]
});
}
}

Mocking class with protractor

I am using angular with ngMaterial. I am using md-toast that comes with it. The problem is that the toasts are using timeout instead of interval and Protractor doesn't like them.
I just want to remove the md-toast calls from my tests. All my md-toast calls are on this class:
# Coffeescript
class MyAppController extends MyAppBase
showToast: (msg, type) ->
#rootScope.customToastMessage =
toast_type: type
messageString: msg
#rootScope.appctrl.openCustomToastMessage()
It is possible to change the showToast method in Protractor tests?
You should look into Protractor's addMockModule()
http://angular.github.io/protractor/#/api?view=Protractor.prototype.addMockModule
(some more info: http://eitanp461.blogspot.be/2014/01/advanced-protractor-features.html)
Example mock:
var MdToastMock = function () {
this.show = function (toast) {
this.position = toast._position;
this.content = toast._content;
};
this.simple = function () {
var self = this;
self.content = function (content) {
self._content = content;
return self;
};
self._position = '';
self.position = function (position) {
self._position = position;
return self;
};
return this;
};
spyOn(this, 'show').and.callThrough();
};
return MdToastMock;

ng-if only works when referencing var direct from service, instead of var in controller scope

I am trying to understand why my ng-if statement doesn't work when I reference a local variable in my controller that is assigned to a value from a service, but it works properly if assigned directly to the value from that service.
For example, this works:
<div class="map" ng-if="interactiveMap.mapService.esriLoaded">
<esri-map id="map1"
map-options="interactiveMap.mapOptions"
load="interactiveMap.load"
register-as="interactiveMap">
</esri-map>
</div>
with the following controller:
angular.module('tamcApp')
.controller('InteractivemapCtrl', function (map, config) {
var self = this;
self.map = {};
self.mapService = map;
self.mapOptions = {
basemap: 'mcgiStreet',
extent: config.globals.initialExtent,
sliderStyle: 'small'
};
self.load = function(){
map.getMap('interactiveMap').then(function(thisMap) {
console.log(thisMap);
self.map = thisMap;
});
};
});
But if I were to assign the "esriLoaded" var to a local var in the scope, like this:
<div class="map" ng-if="interactiveMap.esriLoaded">
<esri-map id="map1"
map-options="interactiveMap.mapOptions"
load="interactiveMap.load"
register-as="interactiveMap">
</esri-map>
</div>
Controller here:
angular.module('tamcApp')
.controller('InteractivemapCtrl', function (map, config) {
var self = this;
self.map = {};
self.esriLoaded = map.esriLoaded;
self.mapOptions = {
basemap: 'mcgiStreet',
extent: config.globals.initialExtent,
sliderStyle: 'small'
};
self.load = function(){
map.getMap('interactiveMap').then(function(thisMap) {
console.log(thisMap);
self.map = thisMap;
});
};
});
Then it doesn't work. The value for "esriLoaded" is always false (which is the default value for esriLoaded). It's like it isn't updating the value of self.ersiLoaded when the value gets updated in the "map" service. Here is the code for the "map" service, just in case folks need it to answer this question.
angular.module('tamcApp')
.service('map', function (config, esriLoader, esriRegistry, esriMapUtils) {
// AngularJS will instantiate a singleton by calling "new" on this function
var self = this;
self.esriLoaded = false;
self.lazyload = function() {
// Make a call to load Esri JSAPI resources.
// A promise is provided for when the resources have finished loading.
esriLoader.bootstrap({
url: config.globals.esriJS
}).then(function() {
// Set Loaded to be true
self.esriLoaded = true;
// DEFINE CUSTOM BASEMAP USED BY ALL MAPS
esriMapUtils.addCustomBasemap('mcgiStreet', {
urls: ['http://myhost.com/arcgis/rest/services/BaseMap/StreetMap/MapServer'],
title: 'MCGI Street Map',
thumbnailurl: ''
});
});
};
if (!self.esriLoaded) {
self.lazyload();
}
self.getMap = function(id){
return esriRegistry.get(id);
};
});
That is actually not because of angular, but because of JavaScript. map.esriLoaded is a boolean value, a primitive and thus not an object, which leads to your local self.esriLoaded not becoming a reference (as only objects can be referenced), but just a plain copy of the boolean value contained in map.esriLoaded.
A short example to make it more clear:
//Primitive
var a = 5; //primitive
var b = a; //b just copies the value of a
a = 6; //This will change a, but not b
conosle.log(b); //will print 5
//Object
var a = { someValue: 5 }; //a is now a reference to that object
var b = a; //b also becomes a reference to the object above
a.someValue = 1337; //will change the object a is referencing, thus also
//changing the object b is referencing, as its the same object
console.log(b.someValue); //will print 1337

Resources