Call method from within itself? - javascript

I have this snippet:
myApp.factory('productsStore', function ($http, $q, Product) {
var products = "";
products = productsStore.get();
return {
get: function () {
return Product.query({});
}
};
});
How can I call the get() method, from within the same 'factory'? products = productsStore.get() does not work obviously.

You can use the Revealing Module Pattern:
myApp.factory('productsStore', function ($http, $q, Product) {
var products = "";
var get = function () {
return Product.query({});
};
products = get();
return {
get: get
};
});
Reasons I enjoy this pattern:
No cluttering with this., MyObject., etc. prefixing.
You see clearly in the bottom return what is publicly exposed.
A great article on the subject: Mastering the Module Pattern

You could assign the object you return to a variable, then call the get function defined in your variable, then return the variable.
Either that, or just duplicate your query code.

Related

How to bind properly to a service property in AngularJS?

I have a factory where I have a couple of predefined partners (it could be anything else, I thought it's an example that's easy to understand). On run time, we select the current partner (based on some logic I omitted here).
angular.module('myApp').factory('PartnersService', function ($location, $log) {
var partners = {
firstPartner: {
name: 'Default Partner',
id: 1 // just an extra property as example
},
secondPartner: {
name: 'Other Partner',
id: 2
}
};
// set default value
var partner = partners.firstPartner;
var initPartner = function () {
// based on some logic (omitted), select partner
partner = partners.secondPartner;
$log.log("initPartner should have changed partner to " + partner.name);
};
return {
initPartner: initPartner,
partners: partners,
partner: partner,
};
});
Then, I would like to access the partner as PartnersService.partner and see as it changes, e.g. from a controller:
angular.module('myApp').controller('myController',
function ($scope, $log, PartnersService) {
// PartnersService.partner is the default partner (firstPartner)
PartnersService.initPartner();
// After initPartner, PartnersService.partner is still
// the default, but I expected it to change
});
I found some workarounds (in my opinion... are they workarounds?), but it feels unnatural for me, so I'd like to ask if there's a better way.
See my full, working example on JS Bin. I apologize if you find the example a bit lengthy, but I wanted to make sure Stack Overflow users understand my concerns and can point out if something is wrong with the way I think.
Workaround 1 (getter?):
angular.module('myApp').controller('myController', function ($scope, $log, PartnersService) {
PartnersService.initPartner();
var partner = PartnersService.getPartner();
$log.log('I could use a getter: ' + partner.name);
});
angular.module('myApp').factory('PartnersService', function ($location, $log) {
var getPartner = function () {
return partner;
};
return {
getPartner: getPartner,
// ...
};
});
Workaround 2 (nest in an object literal):
angular.module('myApp').controller('myController', function ($scope, $log, PartnersService) {
PartnersService.initPartner();
$log.log('or nest the partner in an extra object literal: '
+ PartnersService.extraObjectLiteral.partner.name);
});
angular.module('myApp').factory('PartnersService', function ($location, $log) {
var partners = { /*...*/ }
var extraObjectLiteral = {
partner: partners.firstPartner
};
var initPartner = function () {
// based on some logic (omitted), select partner
extraObjectLiteral.partner = partners.secondPartner;
};
return {
extraObjectLiteral: extraObjectLiteral,
//...
};
});
Change to:
this.partner = partners.secondPartner;
in your initPartner method. That will solve it.
What you're really doing when you do
var partners = { ... }
var partner = partners.firstPartner;
is, you're creating local objects in the class, but they are not exposed members of the class. And with the
return {
initPartner: initPartner,
partners: partners,
partner: partner
};
you create members of the class, and copy the values of the local variables to the class' members. In your initPartner method, you change the local object, but the class' object remain unchanged.
I would go with the approach of using getters and setters as this follows the Revealing Module Pattern which is really preferred for maintaining cleaner, more readable and understandable code.
Factory code is as follows.
(function () {
angular.module('myApp').
factory('PartnersService', PartnersService);
function PartnersService($location, $log) {
var partners = {
firstPartner: {
name: 'Default Partner',
id: 1 // just an extra property as example
},
secondPartner: {
name: 'Other Partner',
id: 2
}
};
var partner;
//Set default value
setDefaultPartner();
var service = {
getPartner: getPartner,
setPartner: setPartner
};
return service;
/*** Function Declarations ***/
/* Accessible Functions */
function getPartner() {
return partner;
}
function setPartner() {
var selectedPartner = initPartner();
return selectedPartner;
}
/* Private Functions */
function initPartner() {
/*
* Some (omitted) logic to select partner
*/
partner = partners.secondPartner;
$log.log("initPartner should have changed partner to " + partner.name);
return partner;
}
function setDefaultPartner() {
partner = partners.firstPartner;
}
}
})();
Notice that the only public/accessible members left are the getPartner and setPartner (which calls initPartner) functions.
The controller would be the following.
(function () {
angular.module('myApp').
controller('myController', myController);
function myController($scope, $log, PartnersService) {
$scope.partner = PartnersService.getPartner();
$log.log("should be default partner: " + $scope.partner.name);
$scope.partner = PartnersService.setPartner();
$log.log("After setPartner which calls initPartner, the current partner is now the " + $scope.partner.name);
}
})();
Modifying your JS Bin, the following is the resulting console log.
"should be default partner: Default Partner"
"initPartner should have changed partner to Other Partner"
"After setPartner which calls initPartner, the current partner is now the Other Partner"
I don't think you are missing anything obvious. Both of your workarounds seem ok to me, except that in the latter case I would probably use the following structure:
var partners = { ... };
var initPartner = function () {
retObj.partner = partners.secondPartner;
};
var retObj = {
initPartner: initPartner,
partners: partners,
partner: partners.firstPartner
};
return retObj;

Injecting a $scope.property by ref into a factory instance

I need a Factory object that can be applied with a $scope.property to get a result. I also need to know if either the modifier in the factory instance, or the $scope.property changes to update the result. How I am seeing this pattern might be wrong of course.
app.factory('MyFactory',
function () {
var MyFactory = function (name, modifier) {
this.name = name;
this.result = 0;
this.modifier = modifier;
}
//I might want to call this when modifier or MyProperty changes
MyFactory.prototype.modifyingMethod = function () {
this.result = this.modifier * //externalProperty;
}
MyFactory.prototype.watcher = function () {
//no idea what I will do here or if I need this at all
// I need to recalculate the result like with $watch
this.modifyingMethod();
}
return MyFactory;
}
)
app.controller('MyCtrl'
function($scope, MyFactory) {
$scope.MyProperty = 42;
$scope.MyFactoryOptions = [
new MyFactory('Option1', 1),
new MyFactory('Option2', 2),
new MyFactory('Option3', 3),
new MyFactory('Option4', 4),
new MyFactory('Option5', 5)
];
}
So I have the problem that I need to $watch MyProperty and the modifier (it can be changed bu users) so I can change the result. If the Property is a value type passing it into the Factory constructor will not work. Perhaps I could pass a function in the returns MyProperty.
Can I set up internal $watch on the factory. If I would do this outside of the factory, in the controller, I would need to do it for each instance. Should I perhaps set up some sort of register method on my factory object?
Are there any suggestions, patterns or something I might want to use?
Basically you could understand your Factory as an interface to a collection of objects (either an array or associative array respectively pure javascript object).
I find your approach with Objects very appealing and I am sure you can achieve similar things. Still I put together a fiddle, that shows how I would solve the problem:
In a MVC pattern your Factory would be the model, the controller should be as simple as possible and your HTML with directives represents the view.
You controller watches for changes from the user ($scope.MyProperty with $watch). While the model is self aware of any depending external property changes. Note that the changes of the ExternalObject service/factory will only be recognizable, if those aren't primitive values. That is why in the ExternalObject factory I return the whole object.
In a perfect world you wouldn't need to listen for changes with an interval, but will receive a javascript event. If this is possible, do it! Note that object updates out of Angular's scope will need you to do a $scope.$apply(), if you want to see the changes in the view. $intervaland $timeout both call a $scope.apply(), so using them is best practice.
Actually there still has to be a lot of cleanup to be done in this code, but it might give you a basic idea how to use an alternative structure:
var app = angular.module('yourApp', []);
window.yourGlobalObject = {};
setInterval(function () {
yourGlobalObject.externalProperty = Math.floor(Math.random() * 5000);
}, 1000);
app.factory('ExternalObject', ['$window', function ($window) {
return $window.yourGlobalObject;
}]);
app.factory('MyFactory', ['$interval', 'ExternalObject', function ($interval, ExternalObject) {
var options = [{
name: 'Option1',
modifier: 1
}, {
name: 'Option2',
modifier: 2
}, {
name: 'Option3',
modifier: 3
}],
cachedExternalProperty = 0,
cachedMyProperty = 0;
MyFactory = {
getOptions: function () {
return options;
},
addOption: function (name, modifier) {
options.push({
name: name,
modifier: modifier
});
},
setMyProperty: function (value) {
cachedMyProperty = value;
},
setResults: function (myProperty) {
angular.forEach(options, function (option, key) {
option.result = option.modifier * ExternalObject.externalProperty * myProperty;
});
console.log(options);
}
};
// let the service check for updates in the external property, if changed
$interval(function () {
if (cachedExternalProperty !== ExternalObject.externalProperty) {
cachedExternalProperty = ExternalObject.externalProperty;
MyFactory.setResults(cachedMyProperty);
}
}, 1000);
return MyFactory;
}]);
app.controller('MyCtrl', ['$scope', 'MyFactory', function ($scope, MyFactory) {
$scope.MyProperty = 42;
$scope.MyFactoryOptions = MyFactory.getOptions();
$scope.setResults = function () {
MyFactory.setResults($scope.MyProperty);
};
$scope.$watch('MyProperty', function (value) {
MyFactory.setMyProperty(value)
MyFactory.setResults(value);
});
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body>
<section ng-app="yourApp" ng-controller="MyCtrl">
<button ng-click="setResults(MyProperty)">Update Results</button>
<div ng-repeat="factory in MyFactoryOptions">{{factory.name}} {{factory.result}}</div>
<input type="number" ng-model="MyProperty">
</section>
</body>

AngularJS function and $scope

I have function defined in my controller like this:
var getTransactions = function(transactionType, partnerId) {
return loadTexts.getTransactionTexts(transactionType, partnerId).then(function (res) {
$scope.loadedTexts = res.data.Transactions;
});
};
Another $scope function is calling it. Now, when I put console.log for res.data.Transactions, everything is logged as it has to be, but when I want to assign that data to $scope variable, it simply doesn't work. (After load function, $scope.loadedTexts is empty however)
$scope.load = function() {
getTransactions($scope.TransactionTypeId, $scope.selectedPartner.Id);
};
Any help?
CODE:
app.controller("textsController", function ($scope, $http, autoComplete, loadTexts) {
$scope.selectedPartner = { "Name": "", "Id": null };
$scope.loadedTexts = [];
.. other functions are here ...
var getTransactions = function(transactionType, partnerId) {
return loadTexts.getTransactionTexts(transactionType, partnerId);
};
$scope.load = function () {
getTransactions($scope.transactionType, $scope.selectedPartner.Id).then(function(res) {
$scope.loadedTexts = res.data.Transactions;
});
};
});
In the code you are showing everything looks fine to me. Note however, that you are storing a reference to Transactions within loadedTexts. Maybe the "Transactions" object res.Data.Transactions is somehow cleared elsewhere.
You could try to copy the object instead of assigning a reference (use e.g. angular.copy) to test if this is the cause.

Unset object property

I have a provider:
AdviceList.provider('$adviceList',function(){
this.$get = function ($rootScope,$document,$compile,$http,$purr){
function AdviceList(){
$http.post('../sys/core/fetchTreatments.php').success(function(data,status){
this.treatments = data;
console.log(this.treatments); // the correct object
});
this.adviceCategories = [
// available in the controller
];
}
return{
AdviceList: function(){
return new AdviceList();
}
}
}
});
Further, i have this controller:
AdviceList.controller('AdviceListCtrl',function($scope,$adviceList){
var adv = $adviceList.AdviceList();
$scope.treatments = adv.treatments; // undefined
});
Why is it, that the controller's $scope.treatments stays undefined, this.treatments inside the provider however, is filled correctly? Also, adviceCategories is available in my controller.
The call you get teatment is async in nature so the results may not have been populated when you try to assign them.
So here
var adv = $adviceList.AdviceList();
$scope.treatments = adv.treatments; //The treatments would only get filled after the server call is over.
You need to rewrite the code in a way that you assign it to your scope property on the success callback.
I will recommend you to simplify your code
1) Use simple factory method of angular instead of provider
2) return a promise to avoid using callbacks
AdviceList.service('adviceList', function ($http) {
return {
adviceList: function () {
return $http.post('../sys/core/fetchTreatments.php');
}
}
});
AdviceList.controller('AdviceListCtrl', function ($scope, $adviceList) {
adviceList.AdviceList().then(function (data) {
$scope.treatments = data //set value to data when data is recieved from server
});
});

Using deeply nested object from JSON in AngularJS - strange behavior

I'm trying to understand how AngularJS sees an object from a deeply nested JSON. Here's an example plunker. The data comes from service and is assigned to $scope.data. The javascript code seems to want me to declare every level of the object first before usage, but referencing a deep level within object from the view HTML always works, and using the deep level in a function kinda works. It's rather inconsistent.
I'm not sure if my understanding of $scope is lacking, or if this has something to do with promise objects. Advise please?
HTML
<body ng-controller="MainCtrl">
Referencing nested obj in view works:
{{data.level1.level2}}
<br>
Using nested obj within declared scope var doesn't work:
{{nestedObj}}
<br>
Using nested obj in a function works but throws TypeError:
{{getLen()}}
</body>
Javascript
var app = angular.module('app', []);
app.factory('JsonSvc', function ($http) {
return {read: function(jsonURL, scope) {
$http.get(jsonURL).success(function (data, status) {
scope.data = data;
});
}};
});
app.controller('MainCtrl', function($scope, JsonSvc) {
JsonSvc.read('data.json', $scope);
// Using nested obj within declared scope var doesn't work
// Uncomment below to break whole app
// $scope.nestedObj = $scope.data.level1.level2;
// Using nested obj in a function works but throws TypeError
// Declaring $scope.data.level1.level2 = [] first helps here
$scope.getLen = function () {return $scope.data.level1.level2.length};
});
JSON
{
"level1": {
"level2": [
"a",
"b",
"c"
]
}
}
Your $http request is asynchronous.
app.controller('MainCtrl', function($scope, JsonSvc) {
JsonSvc.read('data.json', $scope);
//$scope.data.level1.level2 doesn't exist yet at this point in time
//and throws an exception
$scope.nestedObj = $scope.data.level1.level2;
//$scope.data.level1.level2 doesn't exist yet at this point in time
//and throws an exception
//once Angular does dirty checking this one will work since the
//$http request finished.
$scope.getLen = function () {
return $scope.data.level1.level2.length
};
});
Since you have three scope objects that rely on that data it would be best to assign those in the call back.
app.factory('JsonSvc', function ($http) {
return {read: function(jsonURL, scope) {
$http.get(jsonURL).success(function (data, status) {
scope.data = data;
scope.nestedObj = scope.data.level1.level2;
scope.getLen = function () {
return scope.data.level1.level2.length;
};
});
}};
});
If you do not want to set it all up on the call back, you could also use $broadcast() and $on()
app.factory('JsonSvc', function ($http, $rootScope) {
return {
read: function (jsonURL, scope) {
$http.get(jsonURL).success(function (data, status) {
scope.data = data;
$rootScope.$broadcast("jsonDone");
});
}
};
});
app.controller('MainCtrl', function ($scope, JsonSvc) {
JsonSvc.read('data.json', $scope);
$scope.name = "world";
$scope.$on("jsonDone", function () {
$scope.nestedObj = $scope.data.level1.level2;
$scope.getLen = function () {
return $scope.data.level1.level2.length;
};
});
});
Ray, another option is to return the $http.get call since its a promise and use the .then() function to declare $scope.nestedObj or anything else you want to do with data once it returns.
Here's my example: http://plnkr.co/edit/GbTfJ9
You can read more about promises in Angular here: http://docs.angularjs.org/api/ng.$q

Categories

Resources