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';
}
};
}
});
Related
I'm currently working on a project to help me better understand angularjs! I am currently stuck on how to pass a parameter from the controller to service.
In my program, I have created a function called "GetForecastByLocation" when a user types in an input clicks on a button. From there I want to take their input and then pass it to the http call in service.js.
Originally, $http.get was in a long giant string of the API url, but I googled around and it seems that I'm supposed to use parameters when trying to change a portion of the string. As of right now, I know parameter is hardcoded to a specific city, but I want to take new input and pass the value of vm.city to the $http.get call.
If any one can help I would greatly appreciate it. Thank you!
controller.js
var app = angular.module('weatherApp.controllers', [])
app.controller('weatherCtrl', ['$scope','Data',
function($scope, Data) {
$scope.getForecastByLocation = function(myName) {
$scope.city = myName;
Data.getApps($scope.city);},
Data.getApps(city)
.then(function(data)){
//doing a bunch of things like converting units, etc
},
function(res){
if(res.status === 500) {
// server error, alert user somehow
} else {
// probably deal with these errors differently
}
}); // end of function
}]) // end of controller
service.js
.factory('Data', function($http, $q) {
var data = [],
lastRequestFailed = true,
promise;
return {
getApps: function() {
if(!promise || lastRequestFailed) {
promise = $http.get('http://api.openweathermap.org/data/2.5/weather?',{
params: {
q: Tokyo,
}
})
.then(function(res) {
lastRequestFailed = false;
data = res.data;
return data;
}, function(res) {
return $q.reject(res);
});
}
return promise;
}
}
});
Passing arguments to a factory method is no different than passing arguments to a plain old function.
First, set up getApps to accept a parameter:
.factory('Data', function($http, $q){
// ...
return {
getApps: function(city){
promise = $http.get(URL, {
params: {q: city}
}).then( /* ... */ );
// ...
return promise;
}
};
});
Then pass it your argument:
$scope.getForecastByLocation = function(myName) {
$scope.city = myName;
Data.getApps($scope.city);
}
It's just like setting a value to a function's context variable.
Services.js
Simple example of a service.
.factory('RouteService', function() {
var route = {}; // $Object
var setRoute_ = function(obj)
{
return route = obj;
};
var getRoute_ = function()
{
if(typeof route == 'string')
{
return JSON.parse(route);
}
return null;
};
return {
setRoute: setRoute_,
getRoute: getRoute_
};
})
Controllers.js
Simple example of Service usage:
.controller('RoutesCtrl', function ($scope, RouteService) {
// This is only the set part.
var route = {
'some_key': 'some_value'
};
RouteService.setRoute(route);
})
JS FRAMEWORK/ LIBS:
I'm using Angular 1.5.x and lodash 4.x.
QUESTION:
I'm new to JS and struggling with how to inject a new object instance into a abstracted function (ideally factory). Per my service class FormService I have a function creates the FormModel(object) and hydrates its constructor - hydrateModel = function(arr). Is there anyway to abstract that to a generic function or factory class so that I can object a new generic object instance.
I can do this with backend languages but I'm struggling to make that type of syntax work with JS, which has me replicating the hydrateModel function for each data model instance (uGly). Said differently how can I make a OR ideally a . Appreciate any guidance. Ideally this makes sense and apologies if there is a basic answer.
EDIT - IDEAL OUTCOME
Would be to do something like this, which obviously doesn't work.
hydrateModel = function(arr, varObjectInstance) {
var newArr = [];
_.each(arr, function (obj,k) {
// is it possible in JS to inject a new object and reference it dynamically with a different set of dynamic arguments
newArr.push( [varObjectInstance](obj, fillable));
});
return newArr;
},
hydrateModel(arr, new ObjectInstance())
Example, in PHP you could say new $var($arguments)
REFERENCE CODE CONTEXT
// note I've removed the __constructor for brevity sake but it is a fairly basic object extend class
FormModel.$inject = ['__constructor', 'object'];
function FormModel(__constructor, object) {
function Form(data, keys) {
__constructor.call(this, data, keys);
}
Form.prototype = Object.create(__constructor.prototype);
Form.prototype.constructor = Form;
return Form;
}
FormService.$inject = ['FormModel', 'FormDataService'];
function FormService(FormModel,FormDataService) {
var service = this,
forms = {},
fillable = ['app_id','name','class','label','type','maxlength','minlength','placeholder','required','autocomplete','index','helpTitle','helpDescription','messages'],
hydrateModel = function(arr) {
var formEloquent = [];
_.each(arr, function (obj,k) {
formEloquent.push( new FormModel(obj, fillable));
});
return formEloquent;
};
// INIT function: 1. get form field (email, name, password,etc) data for 3 forms along with help messages etc.
service.initForms = function () {
var self = this;
_.each({
register:FormDataService.getRegisterData(), // not including FormDataService for brevity sake but it is http api service to backend
contact:FormDataService.getContactData(),
subscribe:FormDataService.getSubscribeData()
}, function (obj, key) {
forms[key] = {
model:{},
current:1,
// below is my question - how could create a function / factory hydrateModel(obj, newGenericObjectInstance) that would be generic so that I can call new [newGenericObjectInstance](arguments) here or in the hydrateModel
data:hydrateModel(obj),
view:{},
state:{},
help:{}
}
});
};
return {
initForms: function () {
return service.initForms();
}
}
}
DATA EXAMPLE
Example of a form field data row returned from FormDataService (basic)
var formFieldRowExample = {
id: '1010',
name: 'email',
class: 'form--input',
label: 'Enter your email',
type: 'email',
maxlength: 50,
minlength: 4,
placeholder: 'Example: person#example.com',
required: true,
autocomplete: 'on',
validation: [
{
type: 'email',
message: 'Email must be a valid email address'
},
{
type: 'minlength',
message: 'Your email address is too short'
},
{
type: 'maxlength',
message: 'Your email address is too long'
},
{
type: 'required',
message: 'Your email address is required'
}
]
};
In the example above varObjectInstance is an object, not constructor function. It cannot be called or newed.
The workable code may look like this
hydrateModel = function(arr, Cls) {
var newArr = [];
...
newArr.push(new Cls(obj, fillable));
...
}
hydrateModel(arr, ObjectInstance)
I have an angularjs service, that holds a model property for the frontend binding.
I also want to create a function that processes some input data and updates the model accordingly:
angular.module('test').service('testService', testService);
function testService() {
return {
model: [
mylist = null
],
setMyList: setMyList
};
function setMyList(data) {
//process data
mylist = data; //error: "model is not defined"
}
}
from any controller:
testService.setMyList(data);
Problem: the function cannot see the model. Why, and how could I change this?
I solved it as follows:
var model: {
mylist = null
};
return {
model: model,
setMyList: setMyList
}
function setMyList(data)...
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'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