I have a factory service to control a shopping cart and I'm will problems to sync the data with controller when I'm still configurating the shopping cart.
the process consists of analyzing whether or not the user is logged. If he is not, then spCart = 0, if he is logged, then I'll have to make other verifications. Let's say I have 2 more verifications to do:
If user has a valid address,
If the address is within a range, so the shipping cost will be 0;
If user doesn't have valid address, then spCart = 1;
If he has an address and is whitin the range,
spCart = [
'items':[], //add items here
cost: '0'
];
Or if isn't in the range:
spCart = [
'items':[], //add items here
cost: '9.99'
];
The problem is, some of these verifications needs to wait for a $http, this way I'm not able to sync the spCart value from the factory, with the controller. This is what I did:
controller:
function CartController(factCart) {
var sp = this;
factCart.initialLoad();
sp.cart = factCart.getCart();
};
factory:
function factCart (localStorageService,factMain) {
var spCart = 0;
var service = {
initialLoad: _initialLoad,
getCart: _getCart
};
return service;
function _initialLoad() {
var userData = localStorageService.cookie.get(ngApp);
if (userData) {
var func = 'load_address';
factMain.methodGet(func).then(function(res){ //$http from main factory
if(res==0) {
return spCart = [1];
} else {
_checkRange(res);
}
});
} else if (!userData) {
return spCart;
};
};
function _checkRange(data) {
spCart = [];
spCart['items'] = [];
/*
logic here
*/
if (inRange) {
spCart['costs'] = {
cost: 0;
};
return spCart;
} else {
spCart['costs'] = {
cost: 9.99;
};
return spCart;
};
};
function _getCart() {
return spCart;
};
};
The problem is, the spCart value is always 0, it doesn't change when I update it, and I don't know what I can do to solve this issue.
Note: I had this problem when adding items to the shopping cart, the view didn't updated. I solved that issue by using $apply(). But in this case, i couldn't use $apply, it said 'Cannot read property...'
I managed to make it work. I don't know if it's the best solution, so I'll keep the question opened.
But I had to keep the initial array just like I want it to be when all the verifications are OK and added an extra field to control everything.
So I'll have an array like this:
spCart = [];
spCart['address'] = {
verification: 0,
address: 'name of address',
//..etc..
};
spCart['items']= [];
//..Rest of array - for checkout only
This way the data inside the verification is being updated. It's not the best way (or at least not how i expected) because I'm exposing the whole array, instead of a single value.
So, if there is a better solution, it will be very welcome.
Related
Trying to cut down code repetition, I've set up a $firebaseArray extension as follows:
var listUsersFactory = $firebaseArray.$extend({
$$added: function (snap) {
return new Customer(snap);
},
$$updated: function (snap) {
var c = this.$getRecord(snap.key);
var updated = c.updated(snap);
return updated;
},
});
and the Customer code:
function Customer(snap) {
this.$id = snap.key;
this.updated(snap);
}
Customer.prototype = {
updated: function(snap) {
var oldData = angular.extend({}, this.data);
this.data = snap.val();
// checks and such
}
}
This works wonders when loading, showing and saving a list of customers, and I'm satisfied with it.
Now, the problem lies in retrieving a single customer and its detail page, because the Customer object isn't an extension of $fireObject and is therefore lacking a $save method
Single customer loading:
customersRef.once("value", function(snapshot) {
if(snapshot.child(uuid).exists())
{
customersFactory.customerDetails = new Customer(snapshot.child(uuid));
return deferred.resolve();
}
}
but when I call customersFactory.customerDetails.$save() I get an error
How can I extend my class so that it works for both array and single object uses?
I couldn't find a way to do this, so I ended up using the $firebaseArray and getting single records off that to pass as details, in case anyone's wondering
Before I start, I will link my codepen so you can have a look before reading this :)
I have made a very simple app to demonstrate my issue.
basically when I have multiple services sharing an array, it appears that angularjs treats the array as a primitive object which is odd.
I have to link code, so this is my controller and 2 services at the start:
angular.module('bindingApp', [])
.controller('MainController', ['MainService', 'SelectionsService', function (service, selections) {
// Map the controller to a variable
var self = this;
// Attach the model to our scope
self.models = service.models;
// Create a function that handles the changes the service in some way
self.updateSelectedItems = function () {
// Get our first item
var models = selections.selected;
// Loop through our selected items
for(var i = 0; i < models.length; i++) {
// Get our current model
var model = models[i];
// Change the properties
model.status = 'Cancelled';
model.colour = 'gray';
}
};
// Reset everything
self.reset = function () {
selections.reset();
service.reset();
console.log('models in the controller', self.models);
}
// Map our function to our service function
self.selectItem = selections.select;
// Initialize our service
service.init();
}])
.service('MainService', function () {
// Create our service
var service = {
// Create the default model (empty array)
models: [],
// Populates our model
init: function () {
// Loop from 1 to 10
for(var i = 0; i < 10; i++){
// Create some object
var model = {
status: 'Live',
colour: 'green',
selected: false
};
// Push our model to our array
service.models.push(model);
}
console.log('models in the service', service.models);
},
// Resets everything
reset: function () {
// Reset our array
service.models = [];
// Initialize
service.init();
}
};
// Return our service
return service;
})
.service('SelectionsService', function () {
// Create our service
var service = {
// Create a reference to our selected objects
selected: [],
// Our select function
select: function (e, item) {
// Create a reference to our selected items
var selected = service.selected;
// Update the selected status
item.selected = true;
// Push our item to our selected items
selected.push(item);
// Stop propagation
e.stopPropagation();
e.preventDefault();
},
// Resets our selected items
reset: function () {
service.selected = [];
}
};
// Return our service
return service;
});
I have created 2 more Codepens to try to fix the issue. The first one maps the items to an object encapsulating an array in the MainController. The second one does the same, but also the MainService has an object encapsulating the array.
Does anyone know what I can do to get this simple pen working?
OK. This doesn't have much to do with Angular. Just plain JavaScript.
Here's what happens:
The service initializes its models array. service.models is a variable that contains a reference to that array. You thus have
service.models ----> [firstArray]
Then the controller does self.models = service.models;. So it creates another reference pointing to the same array, referenced by service.models:
service.models ----> [firstArray]
^
controller.models ----|
Then you click the reset button, which calls service.reset(), which does service.models = [];. So it creates a new array and assigns it to its models variable. You thus end up with
service.models ----> []
controller.models --> [firstArray]
Note that you never make controller.models point to the new array in the service. So it still points to the first array, and the view still displays the elements of that first array.
So, either you make sure to reinitialize the controller's models variable after calling reset():
controller.models = service.models;
Or you make sure to simply remove everything from the original, first array in reset():
service.models.splice(0, service.models.length)
It looks like that you must not reset the models array by referencing a new empty array but removing all items from it.
Instead
// Reset our array
service.models = [];
use
// Reset our array
service.models.length = 0;
(was not sure what to have as a title, so if you have a better suggestion, feel free to come up with one - I will correct)
I am working on an angular application where I have some menues and a search result list. I also have a document view area.
You can sort of say that the application behaves like an e-mail application.
I have a few controllers:
DateCtrl: creates a list of dates so the users can choose which dates they want to see posts from.
SourceCtrl: Creates a list of sources so the user can choose from which sources he/she wants to see posts from.
ListCtrl: The controller populating the list. The data comes from an elastic search index. The list is updated every 10-30 seconds (trying to find the best interval) by using the $interval service.
What I have tried
Sources: I have tried to make this a filter, but a user clicks two checkboxes the list is not sorted by date, but on which checkbox the user clicked first.
If it is possible to make this work as a filter, I'd rather continue doing that.
The current code is like this, it does not do what I want:
.filter("bureauFilter", function(filterService) {
return function(input) {
var selectedFilter = filterService.getFilters();
if (selectedFilter.length === 0) {
return input;
}
var out = [];
if (selectedFilter) {
for (var f = 0; f < selectedFilter.length; f++) {
for (var i = 0; i < input.length; i++) {
var myDate = input[i]._source.versioncreated;
var changedDate = dateFromString(myDate);
input[i]._source.sort = new Date(changedDate).getTime();
if (input[i]._source.copyrightholder === selectedFilter[f]) {
out.push(input[i]);
}
}
}
// return out;
// we need to sort the out array
var returnArray = out.sort(function(a,b) {
return new Date(b.versioncreated).getTime() - new Date(a.versioncreated).getTime();
});
return returnArray;
} else {
return input;
}
}
})
Date: I have found it in production that this cannot be used as a filter. The list of posts shows the latest 1000 posts, which is only a third of all posts arriving each day. So this has to be changed to a date-search.
I am trying something like this:
.service('elasticService', ['es', 'searchService', function (es, searchService) {
var esSearch = function (searchService) {
if (searchService.field === "versioncreated") {
// doing some code
} else {
// doing some other type of search
}
and a search service:
.service('searchService', function () {
var selectedField = "";
var selectedValue = "";
var setFieldAndValue = function (field, value) {
selectedField = field;
selectedValue = value;
};
var getFieldAndValue = function () {
return {
"field": selectedField,
"value": selectedValue
}
};
return {
setFieldAndValue: setFieldAndValue,
getFieldAndValue: getFieldAndValue
};
})
What I want to achieve is this:
When no dates or sources are clicked the whole list shall be shown.
When Source or Date are clicked it shall get the posts based on these selections.
I cannot use filter on Date as the application receives some 3000 posts a day and so I have to query elastic search to get the posts for the selected date.
Up until now I have put the elastic-search in the listController, but I am now refactoring so the es-search happens in a service. This so the listController will receive the correct post based on the selections the user has done.
Question is: What is the best pattern or method to use when trying to achieve this?
Where your data is coming from is pretty irrelevant, it's for you to do the hook up with your data source.
With regards to how to render a list:
The view would be:
<div ng-controller='MyController as myCtrl'>
<form>
<input name='searchText' ng-model='myCtrl.searchText'>
</form>
<ul>
<li ng-repeat='item in myCtrl.list | filter:myCtrl.searchText' ng-bind='item'></li>
</ul>
<button ng-click='myCtrl.doSomethingOnClick()'>
</div>
controller would be:
myApp.controller('MyController', ['ElasticSearchService',function(ElasticSearchService) {
var self = this;
self.searchText = '';
ElasticSearchService.getInitialList().then(function(list) {
self.list = list;
});
self.doSomethingOnClick = function() {
ElasticSearchService.updateList(self.searchText).then(function(list) {
self.list = list;
});
}
}]);
service would be:
myApp.service('ElasticSearchService', ['$q', function($q) {
var obj = {};
obj.getInitialList = function() {
var defer = $q.defer();
// do some elastic search stuff here
// on success
defer.resolve(esdata);
// on failure
defer.reject();
return defer.promise();
};
obj.updateList = function(param) {
var defer = $q.defer();
// do some elastic search stuff here
// on success
defer.resolve(esdata);
// on failure
defer.reject();
return defer.promise();
};
return obj;
}]);
This code has NOT been tested but gives you an outline of how you should approach this. $q is used because promises allow things to be dealt with asynchronously.
I would like to understand how to have a nice organisation in my angular project.
[see code below]
Does it makes sense to have the getFireList function into the Factory ? Or should i put it into the controller ?
Does the "class" Fire makes sense ? Should i remove it ? Should i move it to the controller ? Should i move it the the factory ?
If you see anything wrong in this code i'm really interested to learn more.
For now, i've got this :
A class "Fire" to create new object of type Fire.
function Fire (p_power) {
// ATTRIBUTES
this.id = null;
this.power = p_power;
this.position = {
x: null,
y: null
}
// GETTERS/SETTERS
// id
this.getId = function() {
return this.id;
}
this.setId = function(p_id) {
this.id = p_id;
}
// power
this.getPower = function() {
return this.power;
}
this.setPower = function(p_power) {
this.power = p_power;
}
// position
this.getPosition = function() {
return this.position;
}
this.setPosition = function(p_position) {
this.position = p_position;
}
// METHODS
this.increasePower = function(p_plus) {
this.power += p_plus;
}
this.decreasePower = function(p_minus) {
this.power -= p_minus;
}
}
A controller
simuApp.controller('FireController', function($scope, FireFactory) {
// ...
});
And a factory
simuApp.factory('FireFactory', function() {
return {
fire_list: [],
getFireList : function() {
return $http.get(site_url+'fire/fireList').
then(
function(success) {
var data = success.data;
var fires = [];
var fire_tmp;
for (i=0 ; i<data.length ; i++) {
fire_tmp = new Fire( data[i].power );
fire_tmp.setId( data[i].idFire );
fires.push( fire_tmp );
}
fire_list = fires;
return fire_list;
}, function(err) {
// ...
}
);
}
}
});
Thanks for your help.
First, let's get the terminology right. .factory is a method to register a function that generates an instance of the service - hence "factory". What it generates, though, is a singleton service instance.
So, the service you create would be more properly named as FireSvc (as opposed to FireFactory), whereas the function that creates it could have the word "factory" in it (although, in the case below, that function name is not really needed - it could just be an anonymous function):
.factory("FireSvc", function FireSvcFactory(){
});
It is a good practice to use a Service to abstract away any domain/business logic from the controller. Keep the controller thin, responsible only to define the ViewModel, and react to events by changing the ViewModel or invoking functions on the Model.
So, having FireSvc.getFireList() makes sense.
Now, whether the list is a collection of plain objects, or instances of Fire is completely independent of Angular and is entirely up to you. In any case, it is too broad of a topic to discuss in a SO answer.
I've had a requirement recently to implement a UI for managing a many-many relationship. Ward Bell kindly provided this plunker showing how to implement using 1-m-1 with Angular and Breeze.
My app's design is based largely (especially the datacontext and the local storage) is based largely on John Papa's recent Pluralsight courses.
In my app, BusUnit = Hero and Dimension = Power (in reference to Ward's example.
Everything seems to be working well when I force the app to fetch data from the server, in that my updates to a business unit's dimensions reflect correctly. The problem I'm facing now is when I navigate away from the page and back again (which gets data from local storage). In this case:
if I previously added a new dimension to a business unit, everything is ok, but
if i previously marked a business unit's dimension for deletion and the save, the dimension still appears for the business unit in question.
this is the controller code that initially gets business units and their dimensions:
function getdboardStructure() {
var busUnitsPromise = datacontextSvc.busUnits.getByDboardConfigId(vm.dboardConfig.id);
var dimensionsPromise = datacontextSvc.dimensions.getByDboardConfigId(vm.dboardConfig.id);
$q.all([busUnitsPromise, dimensionsPromise])
.then(function (values) {
vm.busUnits = values[0];
vm.dims = values[1];
createBusUnitVms();
//vm.currentBusUnitVm = vm.busUnitVms[0]; // not required as using accordion instead of drop-down
vm.hasChanges = false;
});
}
this is the code in my controller that prepares for the save:
function applyBusUnitDimensionSelections(busUnitVm) {
var busUnit = busUnitVm.busUnit;
var mapVms = busUnitVm.dimensionMapVms;
var dimensionHash = createBusUnitDimensionHash(busUnit);
mapVms.forEach(function (mapVm) {
var map = dimensionHash[mapVm.dimension.id];
if (mapVm.selected) {
if (!map) {
datacontextSvc.busUnits.addBusUnitDimension(busUnit, mapVm.dimension)
.then(function () {
});
}
} else {
if (map) {
datacontextSvc.markDeleted(map);
}
}
});
}
this is the code in my controller that executes the save:
function save() {
if (!canSave()) {
return $q.when(null);
}
vm.isSaving = true;
vm.busUnitVms.forEach(applyBusUnitDimensionSelections);
return datacontextSvc.save().then(function (saveResult) {
vm.isSaving = false;
trapSavedDboardConfigId(saveResult); // not relevant to use case
}, function (error) {
vm.isSaving = false;
});
}
this is the code in my repository that add a new busUnitDimension entity:
function addBusUnitDimension(busUnit, dimension) {
var newBusUnitDimension = this.em.createEntity(busUnitDimension);
newBusUnitDimension.busUnitId = busUnit.id;
newBusUnitDimension.dimensionId = dimension.id;
return this.$q.when(newBusUnitDimension);
}
this is my datacontext code for marking an item deleted:
function markDeleted(entity) {
return entity.entityAspect.setDeleted();
}
and finally this is the repository code to get business units and their join table entities:
function getByDboardConfigId(dboardConfigId, forceRefresh) {
var self = this;
var predicate = pred.create('dboardConfigId', '==', dboardConfigId);
var busUnits;
if (self.zStorage.areItemsLoaded('busUnits') && !forceRefresh) {
busUnits = self._getAllLocal(entityName, orderBy, predicate);
return self.$q.when(busUnits);
}
return eq.from('BusUnits')
.expand('BusUnitDimensions')
.where(predicate)
.orderBy(orderBy)
.using(self.em).execute()
.to$q(succeeded, self._failed);
function succeeded(data) {
busUnits = data.results;
self.zStorage.areItemsLoaded('busUnits', true);
self.zStorage.save();
//self.logSuccess('Retrieved ' + busUnits.length + ' business units from server', busUnits.length, true);
return busUnits;
}
}
My departure from John's course examples is that I'm using expand in the function I use to get Business Units from the server, and my hypothesis is that this has something to do with the fact that breeze is going to the server everytime I refresh the page (without clearing cache) instead, and that this also has something to do with the error i'm receiving if I navigate away and then back to the page.
Can anyone offer and suggestions?
Appreciate this was a long time ago and you have probably solved it or moved on but I came up against the same problem recently that took me ages to resolve.
The answer I found is that you have to edit JP's angular.breeze.storagewip.js file.
I contains the names of the entities hard-coded into the file and you will need to change these to match your own entities.
There are two functions where you need to do this, examples below show the changes with the four entities I am using:
function zStorageCore($rootScope, zStorageConfig) {
var storeConfig = zStorageConfig.config;
var storeMeta = {
breezeVersion: breeze.version,
appVersion: storeConfig.version,
isLoaded: {
elementAssets : false,
surveyors : false,
elements : false,
assets : false
}
};
and...
function checkStoreImportVersionAndParseData(importedData) {
if (!importedData) {
return importedData;
}
try {
var data = JSON.parse(importedData);
var importMeta = data[0];
if (importMeta.breezeVersion === storeMeta.breezeVersion &&
importMeta.appVersion === storeMeta.appVersion) {
if (importMeta.isLoaded) {
storeMeta.isLoaded.assets = storeMeta.isLoaded.assets || importMeta.isLoaded.assets;
storeMeta.isLoaded.elements = storeMeta.isLoaded.elements || importMeta.isLoaded.elements;
storeMeta.isLoaded.surveyors = storeMeta.isLoaded.surveyors || importMeta.isLoaded.surveyors;
storeMeta.isLoaded.elementAssets = storeMeta.isLoaded.elementAssets || importMeta.isLoaded.elementAssets;
}
return data[1];
} else {
_broadcast(storeConfig.events.error,
'Did not load from storage because mismatched versions',
{ current: storeMeta, storage: importMeta });
}
} catch (ex) {
_broadcast(storeConfig.events.error, 'Exception during load from storage: ' + ex.message, ex);
}
return null; // failed
}
I solved this by comparing JP's Style Guide course files with his SPA/Angular/Breeze course.