I'm a little bit confused about how angularjs application works.
First of all I should say I'm a newbie angularjs user but I'm familiar with other DI frameworks in other languages (like symfony in PHP, spring in Java, a bit Unity).
Each of these DI implementations requires class definitions and DI configuration.
Configuration usually includes:
how should be class injected (automatically by name or type, or manually)
if container should return a singleton instance
what factory class should be used for creating object
serviceIds - each service can be retrieved by serviceId. This serviceId says configuration of creating instance (what services should be injected and what params should be used).
etc.
And this configuration I'm missing in angularjs.
There is an dumb example how I expect such a configuration should work. I have two services, each does similar thing but with different implementation.
angular.module('notify', [], function($provide) {
$provide.factory('LogNotifier', function($window) {
return {
messageMe: function(message) {
$window.console.log(message);
}
}
});
$provide.factory('AlertNotifier', function($window) {
return {
messageMe: function(message) {
$window.alert(message);
}
}
});
});
angular.module('myModule', ['notify'], function($provide) {
// as notifier dependency I must specify its type.
// is there any way how I would configure after its definition
// which notifier implementation should be used?
$provide.factory('DataLoader', function(AlertNotifier) {
var loader = {
loadAllItems: function() {
AlertNotifier.messageMe('Loading items');
// let's asume there is ajax call and promise object return
return [
{name: 'Item1'},
{name: 'Item2'}
];
}
}
return loader;
});
});
See http://jsfiddle.net/5ZDe6/1/
I would like to switch between LogNotifier and AlertNotifier without changing source code of DataLoader service. Is that possible?
Thank you
After a week fiddling with angularjs I realized one very important thing. All angularjs examples actually include the DI configuration I looked for. The configuration is actually a module definition itself.
All I had to do was to separate module definition from class definitions.
Here is solved fiddle of my question - http://jsfiddle.net/5ZDe6/7/
/**
* app module configuration
*/
angular.module('app', ['debug'], function($provide) {
var dataLoaderFactory = function(Notifier) {
return new my.model.DataLoader(Notifier);
}
// if you want to change notifier just change this injection to AlertNotifier
dataLoaderFactory.$inject = ['LogNotifier'];
// Specify factory with service ID DataLoader for class my.model.DataLoader
$provide.factory('DataLoader', dataLoaderFactory);
})
.controller('AppCtrl', my.controller.ItemCtrl); // you can even specify AppCtrl implementation (e.g. CachedCtrl)
my.controller.ItemCtrl.$inject = ['$scope','DataLoader']
my.controller.CachedCtrl.$inject = ['$scope']
/**
* debug module configuration
*/
angular.module('debug', [], function($provide) {
$provide.factory('LogNotifier', function($window) {
return new my.debug.LogNotifier($window);
});
$provide.factory('AlertNotifier', function($window) {
return new my.debug.AlertNotifier($window);
});
});
And there are class definitions separated from DI configuration.
/**
* Controller class definition
*/
my = window.my || {};
my.controller = my.controller || {};
my.controller.ItemCtrl = function($scope, loader) {
$scope.items = loader.loadAllItems();
};
my.controller.CachedCtrl = function($scope) {
$scope.items = [
{ name: 'Cached ctrl value 1'},
{ name: 'Cached ctrl value 2'}
]
}
/**
* Model class definition
*/
my.model = my.model || {};
my.model.DataLoader = function(notifier) {
this.notifier = notifier;
this.loadAllItems = function() {
this.notifier.messageMe('Loading items');
// let's asume there is ajax call and promise object return
return [
{name: 'Item1'},
{name: 'Item2'}
];
};
};
/**
* Some debug classes definition
*/
my.debug = my.debug || {}
my.debug.LogNotifier = function($window) {
this.$window = $window;
this.messageMe = function(message) {
this.$window.console.log(message);
}
};
my.debug.AlertNotifier = function($window) {
this.$window = $window;
this.messageMe = function(message) {
this.$window.alert(message);
}
}
I suppose that is the cleanest way how to achieve my requirements. Unfortunately if you really want to do this way you have to write a bit more code.
Frank
Related
I created a module in nodejs where I wish to expose it's constants too. But this particular module contains a dependency which is provided at construction time i.e. dependency injection.
this is module1
const STORE_TYPE = {
STORE1: 1,
STORE2: 2
};
function service(dependency1) {
this.dep = dependency1;
}
service.prototype.doSomething = function(param1, store) {
if (STORE_TYPE.STORE1 == store) {
return this.dep.get(param1);
} else {
return "something";
}
};
module.exports = service;
I'm using module1 here:
var dep = require('./dep');
var dep1 = new otherService(dep);
var service = require('./service')(dep1);
function getData() {
return service.doSomething(id, /*this is module1 constant*/1);
}
How do I refere to module1's constants if module1 has a constructor.
I don't wish to add separate method only to create service since callee needs to perform multiple steps.
Try this:
service.js
exports.STORE_TYPE = {
STORE1: 1,
STORE2: 2
};
exports.service = function service(dependency1) {
this.dep = dependency1;
}
service.prototype.doSomething = function(param1, store) {
if (STORE_TYPE.STORE1 == store) {
return this.dep.get(param1);
} else {
return "something";
}
};
Using that module
app.js
const service = require('./service').service;
const STORE_STYLE = require('./service').STORE_TYPE;
I need to fetch some data before allowing the page to render so that the page isn't empty for a second before showing the info. But I'm unable to get my resolve to work.
The issue is that the uid line throws an error because states.getAuth() is undefined. states.getAuth() should (and does) return authentication info about the user when using it from my controllers but when using it in this resolve it doesn't for some reason.
Am I going about this completely wrong? I have never had to do a resolve like this before so I wouldn't know so some guidance would be great.
Let me know if I have to include any of my services or if this route snippet is enough to figure out a solution.
.when('/programs/:program', {
templateUrl: 'views/pages/single-program.html',
resolve: {
'isAuth': ['fbRefs', function(fbRefs) {
return fbRefs.getAuthObj().$requireAuth();
}],
'programData': ['$route', 'fbRefs', 'states', function($route, fbRefs, states) {
// Get our program key from $routeParams
var key = $route.current.params.program;
// Get unique user id
var uid = states.getAuth().uid; // Throws error
// Define our path
var path = uid + '/programs/' + key;
// Fetch the program from Firebase
var program = fbRefs.getSyncedObj(path).$loaded();
return program;
}]
}
})
Added states service code by request:
auth.service('states', [function() {
var auth;
return {
getAuth: function() {
return auth;
},
setAuth: function(state) {
auth = state;
}
};
}]);
You are using the 'Service Recipe' to create the states service, but returning like a 'Factory Recipe'.
According to the doc:
https://docs.angularjs.org/guide/providers#service-recipe
You should either use this:
auth.factory('states', [function() {
var auth;
return {
getAuth: function() {
return auth;
},
setAuth: function(state) {
auth = state;
}
};
}]);
Or this:
auth.service('states', [function() {
var auth;
this.getAuth = function() {
return auth;
};
this.setAuth = function(state) {
auth = state;
};
}]);
I am working on a CMS that I originally was using Knockout but I decided to try Angular because it like more of its functionality. In the CMS, one of the sections will be 'Users'. It has a table that allows the headers to be clicked to sort the data. The controller is below:
userControllers.controller('UserListControl', ['$scope', 'User',
function($scope, User) {
$scope.users = User.query();
$scope.columns = [
{ 'field': 'last', 'label': 'Last Name' },
{ 'field': 'first', 'label': 'First Name' },
{ 'field': 'username', 'label': 'Username' },
{ 'field': 'email', 'label': 'Email' },
];
$scope.orderProp = 'last';
$scope.orderDirection = false;
$scope.tableSort = function(field) {
if ($scope.orderProp === field) {
$scope.orderDirection = !$scope.orderDirection;
}
$scope.orderProp = field;
};
$scope.tableSortClass = function(field) {
if ($scope.orderProp === field) {
if ($scope.orderDirection) {
return 'sortDesc';
}
return 'sortAsc';
}
};
}]);
It is part of my adminApp that I created. Since there will be other sections that will also use the table sort properties (orderProp, orderDirection) and methods (tableSort, tableSortClass), is there a place I can put these methods so my eventual recordsController will also have access to them?
OK, so I am trying to create it using a service and factory function. This is all new to me so I am not completely sure what I am doing but here is what I have:
adminServices.factory('TableSort', [
function() {
var orderProp = 'id';
var orderDirection = false;
function sort(field) {
alert('test');
if (orderProp === field) {
orderDirection = !orderDirection;
}
orderProp = field;
}
function sortClass(field) {
if (orderProp === field) {
if (orderDirection) {
return 'sortDesc';
}
return 'sortAsc';
}
}
}]);
I was hoping to access them in my html using something like ng-click="TableSort.sort(field)" but it doesn't work as it is right now.
As stated above in the other posts, you can create a service that you can inject into various controllers to "share" the code.
Below is a full example:
myApp.service('myService', function myService() {
return {
someVar: "Value",
augmentName: function(name){
return "Sir " + name;
}
}
});
This first piece is the service. I've defined a "myService" and given it one function "augmentName" Now you can inject the myService and access the augment name function.
myApp.controller('testCtrl', function ($scope, myService) {
$scope.testFunction = function(name){
console.log(myFunction.someVar); //This will access the service created variable
return myService.augmentName(name);
}
}
The controller injects the service and then calls it within one of its functions.
Now your HTML code should have access to the controller if you have defined an ng-controller with "testCtrl" or if you have put testCtrl as the controller in your router.
You can now call ng-model="testFunction(someName)" and it will resolve as expected.
Hope that helps. Let me know if you want me to go into greater depth
If you are still trying to figure out how everything in angular works, the angular phone cat tutorial helped me allot when I started out. I'd recommend donating an hour or so into playing with it.
Also, I highly recommend experimenting as early as possible with yeoman/angular generator, this will force you to use angular "the angular way" and can really help you with getting your project set up correctly.
You can use a Service or a Factory to hold these common methods. Additionally, you could use the $rootScope.
You can create a service and put all those properties and method in it. Here is an example for the same:
userControllers.service('UserListControl', function() {
var orderProp = 'last';
var orderDirection = false;
return {
tableSort : function(field) {
if (orderProp === field) {
orderDirection = !orderDirection;
}
orderProp = field;
};
tableSortClass: function(field) {
if (orderProp === field) {
if (orderDirection) {
return 'sortDesc';
}
return 'sortAsc';
}
};
}
});
I have a function inside a directive that makes a query (and gets results, according to the console). The problem is that I can't seem to be able to store those results into a factory, and pass them to a controller.
This is the directive:
scope.getVersions = function(release) {
if (angular.isUndefined(release)) return;
musicInfoService.getReleaseVersions(release.id)
.success(function(data) {
dataService = data.versions;
console.log(dataService);
});
};
The console shows that dataService contains an array with the results.
Then, I try to store it into a factory:
app.factory('dataService', [function(){
return { items: [] };
}]);
And I call it in a controller:
function VersionController($scope, dataService) {
$scope.versions = dataService.items;
console.log($scope.versions);
}
But both items and $scope.versions come back an empty array. Did I miss something?
You should really use a backing field to store that data, and use setter and geter functions for writing and reading respectively:
app.factory('dataService', [function(){
var _items={};
return {
setItems:function(value){
_items=value;
},
getItems:function(){
return _items;
}
};
}]);
And for setting the data:
musicInfoService.getReleaseVersions(release.id)
.success(function(data) {
dataService.setItems(data.versions);
console.log(dataService);
});
and reading:
function VersionController($scope, dataService) {
$scope.versions = dataService.getItems();
console.log($scope.versions);
}
See demo plunk.
There's a misunderstanding of angular factories going on here. You're trying to set dataService = , which will never work.
As Mohammad mentioned, you need to have a variable set outside of your return value in the factory and the return value is basically an object with functions that allow you to manipulate your constant. So what you need is a getter "getItems()" for getting the items, and a setter "addItem(item)" for adding an item to your items array.
So you're never directly injecting your "items" into a controller, you're injecting a bunch of functions that can get or manipulate your "items".
scope.getVersions = function(release) {
if (angular.isUndefined(release)) return;
musicInfoService.getReleaseVersions(release.id)
.success(function(data) {
dataService.addItem(data.versions);
console.log(dataService.getItems());
});
};
app.factory('dataService', [function(){
var items = [];
return {
addItem: function(item) {
items.push(item);
},
getItems: function() {
return items;
}
};
}]);
function VersionController($scope, dataService) {
$scope.$watch(dataService.getItems, function(items) {
$scope.versions = items;
console.log($scope.versions);
});
}
I have a $resource whose API will always return some data that needs to be cleaned up before going into the presentation layer. Specifically, it's .NET returning Date objects in the lovely '/Date(...)/' format.
I don't want to have to write a callback every time I call .query() or .get(). Is there some way to extend the resource with a callback that gets called upon REST methods that update the instance's properties, or by adding some sort of $watch that gets fired when the date property changes? Basically something that will happen for every instance of this $resource.
angular.module('myAppServices', ['ngResource'])
.factory('Participant', ['$resource', function ($resource) {
var res = $resource('api/url/participants/:id', { id: '#id' });
// This obviously doesn't work, but something kinda like this?
res.prototype.$watch(this.FieldName, function(newVal, oldVal) {
if (needsCleaning(newVal.fieldName) {
this.FieldName = cleanupField(newVal);
}
};
});
Ah-ha, I found a way around it and will leave it here. In version 1.1.2 they added support for passing all the $http.config options to a $resource. Naturally, the CDN I'm using doesn't have a recent enough version of angular-resource.js, but switching CDNs solved that.
I just used the transformResponse option to modify the data as it comes back.
angular.module('myAppServices', ['ngResource'])
.factory('Participant', ['$resource', '$http', function ($resource, $http) {
var res = $resource('api/url/participants/:id', { id: '#id' }, {
save: {
method: 'POST',
transformResponse: $http.defaults.transformResponse.concat([
function (data, headersGetter) {
data.FieldName = yourDateParsingFunction(data.FieldName);
return data;
}
])
}
});
I'm just adding my transformer on to $httpProvider's transformResponse, which will do all the deserialization, etc.
An easy way to do this is to overwrite the existing $resource methods you want to do post-processing on with your own. See the code and comments below for an example.
angular.module('myAppServices', ['ngResource'])
.factory('Participant', ['$resource', function ($resource) {
var res = $resource('api/url/participants/:id', { id: '#id' }, {
// create aliases for query and get to be used later
_query: { method: 'GET', isArray: true },
_get: { method: 'GET' }
});
// redefine the query method
res.query = function() {
// call the original query method via the _query alias, chaining $then to facilitate
// processing the data
res._query.apply(null, arguments).$then(function(res) {
var data = res.data;
// do any processing you need to do with data here
return data;
});
};
// redefine the method
res.get = function() {
// call the original get method via the _get alias, chaining $then to facilitate
// processing the data
res._get.apply(null, arguments).$then(function(res) {
var data = res.data;
// do any processing you need to do with data here
return data;
});
};
return res;
});
You'd use it the same way you're currently using Participant in your code, via Participant.query() or Participant.get(). The data you return in the chained $then handler will be used to resolve the promise returned by $resource.
The way I did it was by adding a service to the module:
angular.module('keeniolab', ['ngResource']).
factory('KeenIO',function ($resource) {
// factory implementation
}).service('KeenModel', function (KeenIO) {
var KeenSession = function () {
this.data = {};
};
KeenSession.prototype.fetch = function (query) {
var self = this;
KeenIO.get(query, function (result) {
self.data = result;
});
};
return new KeenSession();
});
Now you can simply monitor the collection:
$scope.$watchCollection(function () {
return KeenModel.data;
},
function (value) {
// code here
});
Keen.IO Resource Factory with Service Model