I'm trying to use inheritance in angular services, as explained here:
http://blog.mgechev.com/2013/12/18/inheritance-services-controllers-in-angularjs/, I want to use the "Inject the parent" method.
However, it doesn't seem to work, and I can't see why.
var myApp = angular.module('myApp',[]);
angular.module('myApp').controller('MyCtrl', MyCtrl);
angular.module('myApp').factory('BaseModel', BaseModel);
angular.module('myApp').factory('ThreadModel', ThreadModel);
angular.module('myApp').factory('PostModel', PostModel);
function MyCtrl($scope, ThreadModel, PostModel) {
$scope.tableNameForThreads = ThreadModel.getTableName();
$scope.tableNameForPosts = PostModel.getTableName();
}
function BaseModel() {
var tableName = "";
var service = {
init: init,
getTableName: getTableName
};
return service;
function getTableName() {
return tableName;
}
function init(theTableName) {
tableName = theTableName;
}
}
function ThreadModel(BaseModel) {
var service = Object.create(BaseModel);
service.init("threads");
return service;
}
function PostModel(BaseModel) {
var service = Object.create(BaseModel);
service.init("posts");
return service;
}
The result is that ThreadModel.getTableName() returns "posts" in stead of "threads".
I tried both Object.create(...) and angular.copy(BaseModel, this), but both don't seem to make a deep copy.
JSFIDDLE: http://jsfiddle.net/dirkpostma/Lvc0u55v/3989/
What am I doing wrong here?
The problem is that with this set up using Object.create you produce services with the tableName variable stored in the same common closure (BaseModel function). To put it simply, init method modifies the same local tableName variable.
You could fix it like this:
function BaseModel() {
var service = {
init: init,
getTableName: getTableName
};
return service;
function getTableName() {
return this._tableName;
}
function init(theTableName) {
this._tableName = theTableName;
}
}
Note, that getTableName and init methods now work with instance property this._tableName which is not shared between TableModel and PostModel instances.
Demo: http://jsfiddle.net/Lvc0u55v/3991/
#dfsq has already well explained and given a simple solution. I put here what I am thinking about this issue.
In your code Object.create(BaseModel) creates a new object whose prototype is a returned value of BaseModel function. In those children models init method modifies tableName within the local scope of BaseModel function. If you replace tableName with this.tableName, that will work as you expected: both init and getTableName methods will actually modify/call tableName property of service variable within ThreadModel or PostModel functions. But it looks complicated.
In your case I would like suggest the following service inheritance solution, which would be clearer. There is an other post that can be interesting.
var myApp = angular.module('myApp', []);
angular.module('myApp').controller('MyCtrl', MyCtrl);
angular.module('myApp').service('BaseModel', BaseModel);
angular.module('myApp').service('ThreadModel', ['BaseModel', ThreadModel]);
angular.module('myApp').service('PostModel', ['BaseModel', PostModel]);
function MyCtrl($scope, ThreadModel, PostModel) {
$scope.tableNameForThreads = ThreadModel.getTableName();
$scope.tableNameForPosts = PostModel.getTableName();
}
function BaseModel() {
this.tableName = "";
this.getTableName = function() {
return this.tableName;
}
this.init = function(theTableName) {
this.tableName = theTableName;
}
}
function ThreadModel(BaseModel) {
angular.extend(ThreadModel.prototype, BaseModel);
this.tableName = "threads";
}
function PostModel(BaseModel) {
angular.extend(PostModel.prototype, BaseModel);
this.tableName = "posts";
}
JSFiddle: http://jsfiddle.net/Lvc0u55v/3993/
Related
I'd like to create an utility class in Angular.js that can be used by several controllers.
So far I created this:
'use strict';
angular
.module('App')
.factory('AppUtils', AppUtils);
function AppUtils() {
var vm = this;
vm.getPersonOf = getPersonOf;
function getPersonOf(personId, allPersons) {
var person = {};
person.personId = {personId: personId};
allPersons.forEach(function(p) {
if (p.personId === personId) {
person = p;
}
});
return person;
}
}
And I tried to use it in a controller like this:
'use strict';
angular
.module('App')
.controller('AppController', AppController);
function AppController(personId, allPersons, AppUtils) {
var vm = this;
vm.personId = personId;
vm.person = AppUtils.getPersonOf(vm.personId, allPersons);
...
}
But I get this:
PhantomJS 1.9.8 (Windows 7 0.0.0) App should dismiss modal FAILED
Error: [$injector:undef] Provider 'AppUtils' must return a value from $get factory method.
http://errors.angularjs.org/1.5.0/$injector/undef?p0=InvoiceUnitsUtils
(The real names have been renamed to make it easier.)
Why am I getting that error? Am I not declaring properly the Utility module?
The factory is in charge of creating a service and handing its instance to you. To do this, you need to return your utility class:
function AppUtils() {
return {
getPersonOf: getPersonOf
// pass other utility functions...
}
function getPersonOf(personId, allPersons) {
var person = {};
person.personId = {personId: personId};
allPersons.forEach(function(p) {
if (p.personId === personId) {
person = p;
}
});
return person;
}
}
I removed the vm part because we are handing a service which usually has no view model (the controller is in charge of that, service is more of a business logic expert).
Here's some more information about the $get function in Angular's providers:
https://docs.angularjs.org/guide/providers
Inside the controller I am trying to breakup my code into named functions for readability. However, in the parameterized named functions the scope and the injected dependency are all null. How do access these inside the named functions. Thanks for you help.
(
function() {
'use strict';
var moduleName = 'ufsrAppModule';
var controllerName = 'ufsrController';
var dependencyInjection = ['api', 'appHost', 'userAccount', 'userProfileFactory', 'fsrFactory', 'userFsrFactory', internalFunc];
angular.module(moduleName)
.controller(controllerName, dependencyInjection);
function internalFunc(api, appHost, userAccount, userProfileFactory, fsrFactory, userFsrFactory) {
var vm = this; //controller AS in ng-controller, do not use $scope
init(api, appHost, userAccount, userProfileFactory, fsrFactory, userFsrFactory, vm);
}
function init(api, appHost, userAccount, userProfileFactory, fsrFactory, userFsrFactory, vm) {
vm.facilityChanged = facilityChanged;
...
...
function facilityChanged(vm, fsrFactory) {
/*update UI then retrieve services*/
vm.postStatus = undefined;
vm.services = undefined;
vm.roles = undefined;
vm.services = fsrFactory.service().query({
/*parameters*/
FacilityID: vm.facility
})
.$promise.then(
function(data) {
vm.services = data;
});
}
}
})();
Strict DI can be done separately in this style
angular.module(moduleName)
.controller(controllerName, controllerFunction);
controllerFunction.$inject = ['$scope', '$http'];
function controllerFunction($scope, $http) {
...
}
This style is also recommended by John Papa's Angular style guide.
The facilityChanged is not working because its parameters are overwriting those that are passed into init
It can be fixed by changing
function facilityChanged(vm, fsrFactory) {
to
function facilityChanged() {
Edit: Attached jsbin
I strongly recommend putting the init function inside your controller function to save the parameter passing, just like the activate function in John Papa's guide.
Refined jsbin
I have two controllers: Controller1 and Controller2
In Controller1's $scope, I have set up all my values I need. Using the data in $scope, I'm trying to run certain functions and pass the return values to Controller2.
I was thinking about making a factory to pass variable from Controller1 to Controller2. However, I realized all input values I need lives in Controller 1. I wonder whether factory can persist the data when it runs in Controller1 and return that data when it runs again in Controller2.
Thanks
Factory is a singleton so it can be used to share data among different controllers or directives. Take a look at the fiddler here. I have created a factory 'sharedContext' which can be used to share any key-value pair across controllers using different $scope.
Factory
myApp.factory("sharedContext", function() {
var context = [];
var addData = function(key, value) {
var data = {
key: key,
value: value
};
context.push(data);
}
var getData = function(key) {
var data = _.find(context, {
key: key
});
return data;
}
return {
addData: addData,
getData: getData
}
});
From the controller that needs to share the object can call the 'addData' method of the factory to store the data under a key. The other controllers/directives which are interested in accessing the shared data can do so by calling the 'getData' method and passing the correct key.
Controller (Sharing Data)
function MyCtrl_1($scope, sharedContext) {
$scope.input_1 = 5;
$scope.input_2 = 15;
$scope.add = function() {
$scope.result = $scope.input_1 + $scope.input_2;
sharedContext.addData("Result", $scope.result)
}
}
Controller (accessing shared data)
function MyCtrl_2($scope, sharedContext) {
$scope.getData = function() {
$scope.result = sharedContext.getData("Result").value;
}
}
The only assumption here is that both the controllers need to use the exact key to share the data. To streamline the process you can use a constant provider to share the keys. Also note that I have used underscore.js to look for the key in the shared context dictionary.
This is the simplest solution that you can come up with. As you can see the factory is a simple object and because of that construct it's passed by reference not by value that means in both controller dataFactory is the same
http://plnkr.co/edit/eB4g4SZyfcJrCQzqIieD?p=preview
var app = angular.module('plunker', []);
app.controller('ControllerOne', function (dataFactory) {
this.formFields = dataFactory
});
app.controller('ControllerTwo', function (dataFactory) {
this.formData = dataFactory
});
app.factory('dataFactory', function () {
return {};
})
edit
app.factory('dataFactory', function () {
var factory = {
method1: function (arg) {
console.log('method1: ', arg)
factory.method2('called from method1')
},
method2: function (arg) {
console.log('method2: ', arg)
}
}
return factory;
})
This is the function that I am working with to call my factory
var myService = function($http) {
return {
bf: null,
initialize: function() {
this.promise = $http.get(this.server + "/requestkey").success(function(data) {
myService.bf = new Blowfish(data.key);
});
}
}
And I am creating this object using
TicTacTorrent.service('AService', ['$http', myService]);
However, when calling AService.initialize() it creates the promise object like it should, but it doesn't update the BF object. I'm confused as to how to update the bf object to be the new value. How would I reference myService.bf since this.bf would create a local instance for .success function?
Try this:
var myService = function($http) {
this.bf = null;
return {
bf: this.bf,
initialize: function() {
this.promise = $http.get(this.server + "/requestkey").success(function(data) {
myService.bf = new Blowfish(data.key);
});
}
}
Where do you want to initialize?
Have you seen the $provider example code?
Search for "provider(name, provider)" and check if it suits your need.
Otherwise I'm unsure what the code you'vew written will run like.
I usually write factories like this:
angular.module('app').factory('myService', ['$http', function($http) {
var publicObj = {};
publicObj.bf = ""; // Just to make sure its initialized correctly.
publicObj.initialize = function() {snip/snap... myService.bf = new Blowfish(data.key);};
return publicObj;
}]);
The difference might be that you previous code returned an inline anonymous object which might have a hard time referring to itself. But by that logic it should work by just making myService return a predeclared var and returning that.
I am trying to include a library of functions, held in a factory, into a controller.
Similar to questions like this:
Creating common controller functions
My main controller looks like this:
recipeApp.controller('recipeController', function ($scope, groceryInterface, ...){
$scope.groceryList = [];
// ...etc...
/* trying to retrieve the functions here */
$scope.groceryFunc = groceryInterface; // would call ng-click="groceryFunc.addToList()" in main view
/* Also tried this:
$scope.addToList = groceryInterface.addToList();
$scope.clearList = groceryInterface.clearList();
$scope.add = groceryInterface.add();
$scope.addUp = groceryInterface.addUp(); */
}
Then, in another .js file, I have created the factory groceryInterface. I've injected this factory into the controller above.
Factory
recipeApp.factory('groceryInterface', function(){
var factory = {};
factory.addToList = function(recipe){
$scope.groceryList.push(recipe);
... etc....
}
factory.clearList = function() {
var last = $scope.prevIngredients.pop();
.... etc...
}
factory.add = function() {
$scope.ingredientsList[0].amount = $scope.ingredientsList[0].amount + 5;
}
factory.addUp = function(){
etc...
}
return factory;
});
But in my console I keep getting ReferenceError: $scope is not defined
at Object.factory.addToList, etc. Obviously I'm guessing this has to do with the fact that I'm using $scope in my functions within the factory. How do I resolve this? I notice that in many other examples I've looked at, nobody ever uses $scope within their external factory functions. I've tried injecting $scope as a parameter in my factory, but that plain out did not work. (e.g. recipeApp.factory('groceryInterface', function(){ )
Any help is truly appreciated!
Your factory can't access your $scope, since it's not in the same scope.
Try this instead:
recipeApp.controller('recipeController', function ($scope, groceryInterface) {
$scope.addToList = groceryInterface.addToList;
$scope.clearList = groceryInterface.clearList;
$scope.add = groceryInterface.add;
$scope.addUp = groceryInterface.addUp;
}
recipeApp.factory('groceryInterface', function () {
var factory = {};
factory.addToList = function (recipe) {
this.groceryList.push(recipe);
}
factory.clearList = function() {
var last = this.prevIngredients.pop();
}
});
Alternatively, you can try using a more object oriented approach:
recipeApp.controller('recipeController', function ($scope, groceryInterface) {
$scope.groceryFunc = new groceryInterface($scope);
}
recipeApp.factory('groceryInterface', function () {
function Factory ($scope) {
this.$scope = $scope;
}
Factory.prototype.addToList = function (recipe) {
this.$scope.groceryList.push(recipe);
}
Factory.prototype.clearList = function() {
var last = this.$scope.prevIngredients.pop();
}
return Factory;
});
You cannot use $scope in a factory as it is not defined. Instead, in your factory functions change the properties of the object the factory is returning, e.g.
factory.addToList = function (recipe) {
this.groceryList.push(recipe);
}
these will then get passed on to your $scope variable
$scope.addToList = groceryInterface.addToList;
// ... = groceryInterface.addToList(); would assign to `$scope.addToList` what is returned, instead of the function itself.
This isn't the exact answer for this question, but I had a similar issues that I solved by simply passing $scope as an argument to a function in my factory. So it won't be the normal $scope, but $scope at the time the function in the factory is called.
app.controller('AppController', function($scope, AppService) {
$scope.getList = function(){
$scope.url = '/someurl'
// call to service to make rest api call to get data
AppService.getList($scope).then(function(res) {
// do some stuff
});
}
});
app.factory('AppService', function($http, $q){
var AppService = {
getList: function($scope){
return $http.get($scope.url).then(function(res){
return res;
});
},
}
return AppService;
});