I Made a factory that keeps the information in my scopes in a series of 6 pages. Now when the user completes the 6th page and pushes the object I want the factory to reset to empty arrays again.
I already tried a lot with the timeout and apply elements, also tried a lot of combinations to set the array to empty (null, "", {}). but it still loads the old information when I load the page again(page 1/6).
The submit function (That also needs to reset the scopes) is
$scope.send = function(){
if(ArrayInfo.Checkmark == true){
firebase.database().ref('lorem/' + ArrayInfo.Ordernumber).set({
info: ArrayInfo,
Dates: Dates,
gasmeter: gasmeter,
gasmeter1: gasmeter1
}).then(function(){
firebase.database().ref('lorem2/' + ArrayInfo.currentYear).set({
last_number: ArrayInfo.Ordervalue
});
}).then(function(){
//ArrayInfo = {};
setTimeout(function(){
ArrayInfo = "";
$scope.info = "";
$scope.$apply();
$scope.$digest();
}, 50);
});
//close newrental
setTimeout(function(){
if (window.confirm('Information saved! You are ready to leave this screen? no changes possible after this point'))
{
//disable back button in home
$ionicHistory.nextViewOptions({
disableBack: true
});
//go home
$state.go("app.auth");
}
//error close newrental
else
{
alert("Take your time");
}
}, 50);
}
//error send array
else {
alert("Please accept the terms and conditions.");
}
}
My factory looks like this
mainapp.factory("infoFactory", function(){
ArrayInfo = {};
placeholders = {
"licenseone" : "img/placeholder.png",
"licensetwo" : "img/placeholder.png",
"licensethree" : "img/placeholder.png",
"licensefour" : "img/placeholder.png",
"imageone" : "img/front.png",
"imagetwo" : "img/sideleft.png",
"imagethree" : "img/back.png",
"imagefour" : "img/sideright.png",
"imagefive" : "img/roof.png",
"imagesix" : "img/placeholder.png",
"imageseven" : "img/placeholder.png",
"imageeight" : "img/placeholder.png"
};
gasmeter = {
"url" : "img/gas/gas1.png",
"gasvalue" : "1"
}
gasmeter1 = {
"url" : "img/gas/gas1.png",
"gasvalue" : "1"
}
ArrayInfo.returned = false;
RawDate = {};
Dates = {};
console.log(ArrayInfo);
return ArrayInfo;
return gasmeter;
return gasmeter1;
return placeholders;
return RawDate;
return Dates;
})
and I load the information in my controller like this
$scope.info = infoFactory;
$scope.Dates = Dates;
$scope.RawDate = RawDate;
$scope.gasmeter = gasmeter;
$scope.gasmeter1 = gasmeter1;
The angular version I am using is "3.6.6"
First of all, when you put return in your code, there's no use to include additional code after that, because it will never run. You need to return an Object instead.
mainapp.factory("infoFactory", function(){
ArrayInfo = {};
placeholders = {
"licenseone" : "img/placeholder.png",
// Rest of the images
};
gasmeter = {
"url" : "img/gas/gas1.png",
"gasvalue" : "1"
}
gasmeter1 = {
"url" : "img/gas/gas1.png",
"gasvalue" : "1"
}
ArrayInfo.returned = false;
RawDate = {};
Dates = {};
console.log(ArrayInfo);
return {
ArrayInfo: ArrayInfo,
gasmeter: gasmeter,
gasmeter1: gasmeter1,
placeholders: placeholders,
RawDate: RawDate,
Dates: Dates
};
})
Now you can inject infoFactory to the controller and use it like this: infoFactory.RawDate.
Now, if you want to reset the factory, you can add a function that reset all the data:
mainapp.factory("infoFactory", function(){
// Save a reference to the current pointer of the factory, so you won't loose it inside other scopes
var self = this;
self.params = {
ArrayInfo: {},
placeholders: {},
gasmeter: {},
gasmeter1: {},
ArrayInfo: false,
RawDate: {},
Dates: {}
};
self.reset = function() {
self.params.ArrayInfo = {};
self.params.placeholders.licenseone = "img/placeholder.png";
self.params.gasmeter.url = "img/gas/gas1.png";
self.params.gasmeter.gasvalue = "1";
self.params.gasmeter1.url = "img/gas/gas1.png";
self.params.gasmeter1.gasvalue = "1";
self.params.ArrayInfo.returned = false;
self.params.RawDate = {};
self.params.Dates = {};
}
self.reset(); // Call this function by default in order to initially set the factory properties
return {
reset: self.reset, // You can export the reset function and use it outside the factory too
ArrayInfo: self.params.ArrayInfo,
gasmeter: self.params.gasmeter,
gasmeter1: self.params.gasmeter1,
placeholders: self.params.placeholders,
RawDate: self.params.RawDate,
Dates: self.params.Dates
};
})
Now when you have a reset function, you can use it like this outside the factory: infoFactory.reset() whenever you want to reset the data to the initial state. I created inside the factory a base object (this.params = { .. }) and saved inside it all the details properties, inside the reset function I have updated those properties without breaking the original references (Working example).
The above is just an example, but you can (or perhaps should) encapsulate the params of the factory, and only allow the user to control and change the values via helper functions. Example of how to do it:
mainapp.factory("infoFactory", function(){
var self = this;
self.params = {
returned: false,
};
return {
setReturned: function(val) { self.params.returned = val === true; },
returned: function() { return self.params.returned; }
}
});
The above example will hide the actual params.returned from the user outside the factory, and only allow it to set the returned flag via helper function, i.e infoFactory.setReturned( true ); or infoFactory.setReturned( false );, and inside that setReturned function you can implement complex logic to validate the value sent to the function. Note that infoFactory.setReturned( 'invalid value!!!' ); will set the returned flag to false since i'm validating the value using the strict === operator - val === true.
Then, to get the value from the factory you call the infoFactory.returned() function - By using a function you're blocking outside access to the properties of the factory.
As a side note - Don't use setTimeout(function(){ ... }); Use $timeout and $interval and then you won't need $scope.$apply(); + $scope.$digest(); in order to manually run a digest cycle because it is being handeled nativaly by Angularjs for you
Related
I have a PHP page that loads two JS files at the end. In the first file I have this...
// global variables
var refineSearchStorage = {};
// function calls
window.addEventListener("load", function() {
refineSearchStorage.get();
});
refineSearchStorage = {
data : null, // empty storage
get : function() {
refineSearchStorage.data = localStorage.getItem("refineSearchStorage");
if(refineSearchStorage.data === null) {
refineSearchStorage.data = { refineKeywords: '' };
refineSearchStorage.save();
}
else {
refineSearchStorage.data = JSON.parse(refineSearchStorage.data);
}
},
add : function(x) {
refineSearchStorage.data.refineKeywords = x;
refineSearchStorage.save();
},
save : function() {
localStorage.setItem("refineSearchStorage", JSON.stringify(refineSearchStorage.data));
}
};
Inline javascript calls Function 1 from the middle of the page. It is created by PHP after a search result...
<script>
window.addEventListener('load', function () {
searchActions('{$keywords_human}');
});
</script>
Function 1 appears in the 2nd JS page and the result is Uncaught TypeError: Cannot set property 'refineKeywords' of null... instead of adding to the localStorage.
function searchActions(x) {
refineSearchStorage.add(x);
}
Function 2 below is called with the click of a button and adds to a localStorage variable without issue. It is also located on the 2nd JS page...
function keywordAdd(y) {
var existingParams = refineSearchStorage.data.refineKeywords;
var param = y.toLowerCase();
var newParams;
newParams = (existingParams + ' ' + param).trim();
refineSearchStorage.add(newParams);
}
Function 1 used to work, but I did something to break it when I split the functions on to different pages. What did I do?
It's because
window.addEventListener('load', function () {
searchActions('{$keywords_human}');
});
gets called before
window.addEventListener("load", function() {
refineSearchStorage.get();
});
That error means that your data attribute is null
(screenshot) See what I mean by that
convert add to
add : function(x) {
refineSearchStorage.data = refineSearchStorage.data || {}; // this might help
refineSearchStorage.data.refineKeywords = x;
refineSearchStorage.save();
},
In my Ionic app I've added the plugin 'ngStorage' and it comes with a little demo code:
var add = function (thing) {
$localStorage.things.push(thing);
}
This works exactly as told. I add("foo") it, and do getAll() and the value is there. I remove the add(), but keep the getAll(), I still have the value "foo" (as expected).
This isn't very usefull for me, I want to access it with keys, so I've made the following:
var addByKey = function (key, value) {
$localStorage.things[key] = value;
// Or, I've also tried:
$localStorage.things.key = value;
}
When I do the addByKey("foo","bar") and then the getAll() I get the values exactly as I want. When I remove the addByKey() and reload, I expect it to still remember the set information, but it doesn't exist. However, the first attempt via the add() function still exists, "foo" is still there (meaning the array doesnt reset).
How do I make a key->value type of structure?
In case it's usefull:
.factory ('StorageService', function ($localStorage) {
$localStorage = $localStorage.$default({
things: []
});
var _getAll = function () {
return $localStorage.things;
};
var _add = function (thing) {
$localStorage.things.push(thing);
}
var _addByKey = function (thing, value) {
$localStorage.things[key] = value;
// Or, I've also tried:
$localStorage.things.key = value;
}
return {
getAll: _getAll,
add: _add,
addByKey: _addByKey
};
})
Assuming that you want a key value storage system you can simply use an object instead of an array so that every key can be set as a property of this object.
.factory('StorageService', function($localStorage) {
$localStorage = $localStorage.$default({
things: {}
});
var _getAll = function() {
return $localStorage.things;
};
var _addByKey = function(thing, value) {
$localStorage.things[thing] = value;
}
return {
getAll: _getAll,
addByKey: _addByKey
};
})
However, assuming that you want to keep a reference of all values on the main collection and access them through keys, you can consider using an object to store the things intead of an array. So that you can use a property to store all items (you can store in a different place as well) and use this object to store your keys by referencing the to a desired value on your collection.
You may need to implement the deletion logic to maintain the consistence between the collection and the dictionary.
Your factory would look like this:
.factory('StorageService', function($localStorage) {
$localStorage = $localStorage.$default({
things: {
items: []
}
});
var _getAll = function() {
return $localStorage.things.items;
};
var _add = function(thing) {
$localStorage.things.items.push(thing);
}
var _addByKey = function(thing, value) {
var i = $localStorage.things.items.push(value) - 1;
$localStorage.things[thing] = $localStorage.things.items[i];
}
return {
getAll: _getAll,
add: _add,
addByKey: _addByKey
};
})
I'm attempting to call a service from within another service, then use the returned object to perform some operations. I keep running into a TypeError: getDefinitions is not a function error, however.
Below is my service is called, the service doing the calling, and my relevant controller code:
definitions.service.js:
'use strict';
angular.module('gameApp')
.factory('definitionsService', ['$resource',
function($resource) {
var base = '/api/definitions';
return $resource(base, {}, {
get: {method: 'GET', url: base}
});
}]);
utilities.service.js:
'use strict';
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
return {
description: description,
detail: detail,
severity: severity,
};
function description(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].description;
}
function detail(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].detail;
}
function severity(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].severity;
}
var getDefinitions = function() {
definitionsService.get().$promise.then(function(data) {
return data;
});
};
}]);
controller.js:
'use strict';
angular.module('gameApp')
.controller('AccountsController', AccountsController);
AccountsController.$inject = ['$routeParams', 'customersService', 'utilitiesService'];
function AccountsController($routeParams, playersService, utilitiesService) {
var vm = this;
var playerId = $routeParams.playerId;
var getAccounts = function() {
playersService.getAccounts({
playerId: playerId
}).$promise.then(function(accounts) {
for (var i = 0; i < accounts.length; i++) {
if (angular.isDefined(accounts[i].secCode)) {
accounts[i].code = accounts[i].secCode;
accounts[i].severity = utilitiesService.severity(accounts[i]);
accounts[i].detail = utilitiesService.detail(accounts[i]);
accounts[i].description = utilitiesService.description(accounts[i]);
}
}
vm.accounts = accounts;
});
};
var init = function() {
getAccounts();
};
init();
}
Currently your service returns before your variable gets defined. That means the definition is never reached. So it is declared, as the function executes, but is undefined. Just move your variable definition to the top.
This will only prevent the definition error. Another problem is that your getDefinitions function doesn't return anything but you're calling a property on it. One solution I can think of is using a callback, that gets executed when your data is loaded:
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var data;
reload();
var utils = {
description: description,
detail: detail,
severity: severity,
reload: reload,
loaded: null
};
return utils;
function reload() {
definitionsService.get().$promise.then(function(data) {
data = data;
if (utils.loaded && typeof utils.loaded === "function") {
utils.loaded();
}
});
}
function description(account) {
var key = angular.isDefined(data.ABC[account.code]) ? account.code : '-';
return data.IDV[key].description;
}
}]);
Then in your controller you could use the service like this:
utilitiesService.loaded(function(){
accounts[i].description = utilitiesService.description(accounts[i]);
})
old question but still relevant. To expand on Florian Gl's answer above if you have a service with multiple functions and one or more of those functions requires a "pre-service" function to be called for example to load some resource data in like configuration information move that service call to the top, outside of the nested function (in this case below I am dealing with the promise scenario in JavaScript):
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var myFirstConfigValue = '';
// call any all services here, set the variables first
configurationService.GetConfigValue('FirstConfg')
.then(function (response) {
// set the local scope variable here
myFirstConfigValue = response;
},
function() { });
function myTestFunction() {
// make an ajax call or something
// use the locally set variable here
ajaxService.functionOneTwo(myFirstConfigValue)
.then(response) {
// handle the response
},
function(err) {
// do something with the error
});
}
}]);
Key point to note here is that if you need to load in some data you do that first outside of any other functions inside your service (e.g. you want to load some JSON data).
I have problem with below code. I have prices factory which returns object containing prices received from server by websocket. Prices are sent after button Create is clicked. Problem is that main.prices variable is not updated at all. I can check everything by Check button, which confirms this. Prices.data is updated, but this.prices is not, but it refers the same object, so I thought it should be updated as well. Do you have any ideas why below does not work as expected?
angular.module('myApp', ['ngWebSocket'])
.factory('ws', ['$websocket', function($websocket){
var url = 'ws://localhost/websocket';
var ws = $websocket(url);
return ws;
}])
.factory('prices', ['ws', function(ws){
var prices = {
data: [],
clear: function(){
this.data = [];
},
create: function(){
ws.send('send')
}
}
ws.onMessage(function(message){
message = JSON.parse(message.data);
var type = message.type;
if (type == 'new prices'){
prices.data = message.data;
}
});
return prices;
}])
.controller('main', ['prices', function(prices){
this.prices = prices.data;
this.check = function(){
console.log('works ', prices.data);
console.log('not works ', this.prices);
};
this.create = function(){
prices.create();
};
this.stop = function(){
prices.clear();
};
}]);
<div ng-controller="main as main">
{{ main.prices }}
<button ng-click="main.create()">Create</button>
<button ng-click="main.stop()">Stop</button>
<button ng-click="main.check()">Check</button>
</div>
There are a lot of issues with the code you posted (working on a fiddle so i can help rework it) ...
First change :
if (type == 'new prices'){
prices.data = message.data;
}
To:
if (type == 'new prices'){
prices.data.length = 0;
prices.data.push.apply(prices.data,message.data) ;//copy all items to the array.
}
From a readability / maintainability point of view you should just use this.prices vs this.prices.data. It's confusing to map them to other variables, when you can just use prices. Also note that I updated it to use "that" constantly to avoid any type of context this issues.
.controller('main', ['prices', function(prices){
var that = this;
that.prices = prices;
that.check = check;
that.create = create;
that.stop = stop;
function check(){
console.log('works ', that.prices.data);
console.log('not works ', that.prices);
}
function create(){
that.prices.create();
}
function stop(){
that.prices.clear();
}
}]);
To add to the previous response, you also have an issue on the clear():
var prices = {
...
clear: function(){
this.data = [];
},
...
}
when you do the clear with this.data = [] you are actually creating a new empty array an storing that in the this.data prop, and since this is a NEW array, the reference on main controller -> this.prices = prices.data; is still pointing to the old one. If you need to delete elements on the array just use this.data.length = 0 as Nix pointed out for the other method. that will keep all references in sync since you are re using the original array
I have a page which lists numerous types of 'tiles' (<div>'s) in a main content area and a header which has a list of links which acts as filters.
E.g. In the header area, click the 'pdf' filter link - only tiles which have ng-show="showFiles['pdf'] will be shown. If 'video' is clicked, tiles with ng-show="showFiles['video'] will show and so on..
The header template is controlled by hdrController and the tiles by pageController.
Initially, when the view loads the $scope variable showFiles in pageController receives an object from a service Tweaks which sets all items to true (thus showing all tiles at start up):
testApp.controller('pageController', ['$scope', 'Tweaks', function($scope, Tweaks){
$scope.showFiles = Tweaks.tagFilters('all');
}]);
testApp.factory('Tweaks', function(){
var tweaksFactory = {};
var obj = {};
tweaksFactory.tagFilters = function(filter) {
if(filter == 'all') {
obj = {
'video' : true,
'pdf' : true,
'doc' : true
};
} else {
obj = {
'video' : false,
'pdf' : false,
'doc' : false
};
}
return obj;
};
return tweaksFactory;
});
Question: When clicking the filter links, a directive is applied which detects clicks - this then needs to update $scope.showFiles to show only tiles which are of the specific filter type.
See Plunkr - The $scope of pageController which contains the showFiles object doesn't update, so the changes aren't reflected.
Can someone offer any suggestions? I'm new to Angular - is this approach the best way to achieve the result?
You allways create a new 'obj' - so the reference in the controller won't be updated. Anyway you should always access the data/status through service functions. plnkr
testApp.factory('Tweaks', function(){
var tweaksFactory = {};
var obj = {};
tweaksFactory.tagFilters = function(filter) {
if(filter == 'all') {
obj = {
'video' : true,
'pdf' : true,
'doc' : true
};
} else {
obj = {
'video' : false,
'pdf' : false,
'doc' : false
};
obj[filter] = true;
}
console.log('alter the object - so it reflects in the scope');
console.log(obj);
return obj;
};
tweaksFactory.show = function(type) {
console.log(obj, type);
return obj[type];
};
return tweaksFactory;
});
communication with two or more controllers is done with services and events, you can do it with adding new service for broadcasting messages
testApp.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.prepForBroadcast = function(msg) {
$rootScope.$broadcast('handleBroadcast', msg);
};
return sharedService;
});
You can see your code updated using this service to allow directive to communicate with controller:
http://plnkr.co/edit/M3RECJmZa64cxKtpWeHO?p=info