When should I create factory or VM in angular - javascript

This is how my angular app looks like.
(function () {
"use strict";
angular
.module("app")
.controller("custCtrl", custCtrl);
custCtrl.$inject = ['dataService','custFactory'];
/* #ngInject */
function custCtrl(dataService, custFactory) {
var vm = this;
//line##
vm.customer= custFactory.Create('customer');
GetCustomers();
function GetCustomers() {
dataService.read().then(function (data) {
vm.customer = data.fields;
}
});
}
return vm;
}
})();
Factory Method
(function () {
'use strict';
angular
.module('app.factory')
.factory('custFactory', custFactory);
custFactory.$inject = ['$q'];
/* #ngInject */
function custFactory($q) {
var _create = function (type) {
var obj = {};
switch (type.toString().toLowerCase()) {
case "customer":
obj = new Customer();
break;
default:
obj = null;
}
return obj;
}
return {
Create: _create
};
}
})();
View Model
function Customer()
{
var dto = this;
dto.Customer = {
"Name" : "",
"Gender" : "", // & so on
}
return dto;
}
If you check my above custCtrl on //line##, I am calling factory method to instantiate customer object as below.
vm.customer= custFactory.Create('customer');
But if I don't create a customer VM & factory and simple assign an empty string as below.
vm.customer= {};
Still its working with no issue.
So my question why should I create a VM & factory?? What is its
benefit??

AngularJS patterns and best practices involve following the MV(whatever) pattern and designing/implementing modular components with high cohesion and low coupling. That way you can easily modify one piece of code without having to make changes in another piece of code.
Typically a factory or service is used as the layer of client-side code that interacts with a RESTful API or some sort of server-side code. Typically that is the only function of the factory. That way you can inject the factory into any controller that needs to use the factory's functions. When you need to modify how you call your API, you only need to make a change in the factory instead of every single controller that would use that function.
Similarly with the view model object you have created, you only need to make changes in one place. Imagine having five different controllers that all need to use a Customer object. Now you decide you want to remove the Name field and replace it with FirstName and LastName. Would you want to go through all your controllers and make that change, or would you want to just change the view model?
To give you the short answer. There's nothing functionally wrong with not creating a view model or a factory/service. From an architectural or design perspective, it makes tons of sense to have a strong separation of concerns in your application. Therefore, it makes sense to use a factory for data access and a view model for your data objects.
Try imagining your application growing to have hundreds of controllers/views and dozens of factories or services. It would be a huge pain to maintain that application if you didn't have these patterns and best practices implemented from the beginning of development.

the controller alias is suitable when you have nested controllers,
while the factory or services are suitable to share data between different controllers.
For example it's useful to have your mehtod CreateCustomer if you use it from different controllers. Since you don't store data in it, it would be better to use a service instead. Anyway it allows you to save code.
EXAMPLE
var app = angular.module("myApp", []);
app.factory("myFactory", function(){
var private = {
users: {}
};
var data = {
getUser: function(id){
return private.users[id];
},
createUser: function(id){
private.users[id] = someData;
return private.currentUser;
}
};
return data;
});
app.controller("myC1", ["myFactory", function(myFactory){
$scope.user = myFactory.createUser(1);
}]);
app.controller("myC2", ["myFactory", function(myFactory){
$scope.user = myFactory.getUser(1);
}]);
As you see the two controllers can access the same data

Related

Save form data in view

how can i save data from 1 view to another in angularjs?
i did $rootScope
From what I see, you use 2 different controllers for each view (or one for the view and none for the root view).
The problem is that Angular can't share data between controllers like that.
You either have to use a service/factory, or use the rootscope, but not as you did, rather with broadcast and emit
If I were you I would use a service.
EDIT Here you go, a service for you :
(function() {
'use strict';
angular
.module('YourModuleName')
.factory('CountriesService', CountriesService);
CountriesService.$inject = ['Your', 'dependencies', 'here', 'in', 'string'];
/* #ngInject */
function CountriesService(your, dependencies, here, not, in, string) {
var service = {
setCountries: setCountries,
getCountries: getCountries
};
var vm = this;
vm.countries = []; // Or maybe an object ?
// ... List of other variables you need to store.
return service;
////////////////
function setCountries(listOfCountries) {
vm.countries = listOfCountries;
}
function getCountries() {
return vm.countries;
}
}
})();
This will store your variables. In your controller you add CountriesService as a dependency, to save you use CountriesService.setCountries and to load you use CountriesService.getCountries. Be aware that refreshing the page will delete all the data !
EDIT NUMBER 2
If you're scared of John papa guidelines, here is a simple service you can use in the same file you put your controller :
app.factory('CountryControl', function(your, dependencies) {
var service = {
setCountries: setCountries,
getCountries: getCountries
};
this.countries = []; // Or maybe an object ?
// ... List of other variables you need to store.
return service;
////////////////
function setCountries(listOfCountries) {
this.countries = listOfCountries;
}
function getCountries() {
return this.countries;
}
});
I have an app that does this more or less. A service fixes this nicely AND creates a mechanism such that you can do this anywhere in your app.
First, I would recommend not trying to manage this with scope. Just put an object on your controller (myFormObj), and add the properties you want to it (name, rank, serialnumber, etc).
Then bind the input fields of the form, to the properties in that object (as opposed to scope vars). So your ng-model things would look like myCtl.formObj.name, and so on.
When the user triggers the event that changes the view, save a COPY (angular.copy) of that formObj off to the side, usually in a Service (think FormStateService or something). FormStateService could do nothing more than hold a simple array.
this.forms = { 'TheNameOfYourForm' : theFormObjToSave };
So, when the user triggers that event that leaves the form, you just do this:
formStateSvc.forms [ 'NameOfMyForm' ] = angular.copy ( theFormObj );
When the user comes back to the original view and the controller initializes, you just ask the formStateSvc:
if ( 'NameOfMyForm' in formStateSvc.forms ) {
this.formObj = formStateSvc.forms [ 'NameOfMyForm' ];
}
Voila, your old form state is restored.
More robustly, you could create "addForm, removeForm" methods etc, you could ensure against things like undefined, and you could make the rebind to the former state implicit (when your form's controller inits, just ask it to restore the state if there's any to restore). So your controller would just have:
this.formObj = formStateSvc.rebindOldDataIfItExists ( 'MyFormName' );
You get the idea.
A simple approach is to create a value provider object and publish it on scope:
//Create value provider object
app.value("FormObj", {});
app.controller("myController", function($scope, FormObj) {
//Publish on scope
$scope.FormObj = FormObj;
});
Then have the ng-model directives use that object:
Name <input ng-model="FormObj.name"><br>
Rank <input ng-model="FormObj.rank"><br>
SerialNum <input ng-model="FormObj.ssnum"><br>
The value object is a singleton which persists for the life of the application. Changes to the contents of the object will be retained and available to other controllers and will survive changes to the view.
The DEMO on PLNKR

AngularJS controllers, design pattern for a DRY code

I have created a full example for the purpose of describing this issue. My actual application is even bigger than the presented demo and there are more services and directives operated by every controller. This leads to even more code repetition. I tried to put some code comments for clarifications,
PLUNKER: http://plnkr.co/edit/781Phn?p=preview
Repetitive part:
routerApp.controller('page1Ctrl', function(pageFactory) {
var vm = this;
// page dependent
vm.name = 'theOne';
vm.service = 'oneService';
vm.seriesLabels = ['One1', 'Two1', 'Three1'];
// these variables are declared in all pages
// directive variables,
vm.date = {
date: new Date(),
dateOptions: {
formatYear: 'yy',
startingDay: 1
},
format: 'dd-MMMM-yyyy',
opened: false
};
vm.open = function($event) {
vm.date.opened = true;
};
// dataservice
vm.data = []; // the structure can be different but still similar enough
vm.update = function() {
vm.data = pageFactory.get(vm.service);
}
//default call
vm.update();
})
Basically I moved all the logic I could to factories and directives. But now in every controller that uses certain directive I need, for example, a field that keeps the value that directive is modifying. And it's settings. Later I need similar field to keep the data that comes from dataservice, and the call itself (method) is the same as well.
This leads to a lot of repetition.
Graphically I see the current example to look like this:
While I believe the proper design should look more like this:
I tried to find some solution here, but none seem to be confirmed. What I have found:
AngularJS DRY controller structure, suggesting I pass the $scope or vm and decorate it with extra methods and fields. But many sources say it is dirty solution.
What's the recommended way to extend AngularJS controllers? using angular.extend, but this have problems when using controller as syntax.
And then I have found also the answer (in the link above):
You don't extend controllers. If they perform the same basic functions then those functions need to be moved to a service. That service can be injected into your controllers.
And even when I did there is still a lot of repetition. Or is it the way it just has to be? Like John Papa sais (http://www.johnpapa.net/angular-app-structuring-guidelines/):
Try to stay DRY (Don't Repeat Yourself) or T-DRY
Did you face a similar issue? What are the options?
From a over all design perspective I don't see much of a difference between decorating a controller and extending a controller. In the end these are both a form of mixins and not inheritance. So it really comes down to what you are most comfortable working with. One of the big design decisions comes down to not just how to pass in functionality to just all of the controllers, but how to also pass in functionality to say 2 out of the 3 controllers also.
Factory Decorator
One way to do this, as you mention, is to pass your $scope or vm into a factory, that decorates your controller with extra methods and fields. I don't see this as a dirty solution, but I can understand why some people would want to separate factories from their $scope in order to separate concerns of their code. If you need to add in additional functionality to the 2 out of 3 scenario, you can pass in additional factories. I made a plunker example of this.
dataservice.js
routerApp.factory('pageFactory', function() {
return {
setup: setup
}
function setup(vm, name, service, seriesLabels) {
// page dependent
vm.name = name;
vm.service = service;
vm.seriesLabels = seriesLabels;
// these variables are declared in all pages
// directive variables,
vm.date = {
date: moment().startOf('month').valueOf(),
dateOptions: {
formatYear: 'yy',
startingDay: 1
},
format: 'dd-MMMM-yyyy',
opened: false
};
vm.open = function($event) {
vm.date.opened = true;
};
// dataservice
vm.data = []; // the structure can be different but still similar enough
vm.update = function() {
vm.data = get(vm.service);
}
//default call
vm.update();
}
});
page1.js
routerApp.controller('page1Ctrl', function(pageFactory) {
var vm = this;
pageFactory.setup(vm, 'theOne', 'oneService', ['One1', 'Two1', 'Three1']);
})
Extending controller
Another solution you mention is extending a controller. This is doable by creating a super controller that you mix in to the controller in use. If you need to add additional functionality to a specific controller, you can just mix in other super controllers with specific functionality. Here is a plunker example.
ParentPage
routerApp.controller('parentPageCtrl', function(vm, pageFactory) {
setup()
function setup() {
// these variables are declared in all pages
// directive variables,
vm.date = {
date: moment().startOf('month').valueOf(),
dateOptions: {
formatYear: 'yy',
startingDay: 1
},
format: 'dd-MMMM-yyyy',
opened: false
};
vm.open = function($event) {
vm.date.opened = true;
};
// dataservice
vm.data = []; // the structure can be different but still similar enough
vm.update = function() {
vm.data = pageFactory.get(vm.service);
}
//default call
vm.update();
}
})
page1.js
routerApp.controller('page1Ctrl', function($controller) {
var vm = this;
// page dependent
vm.name = 'theOne';
vm.service = 'oneService';
vm.seriesLabels = ['One1', 'Two1', 'Three1'];
angular.extend(this, $controller('parentPageCtrl', {vm: vm}));
})
Nested States UI-Router
Since you are using ui-router, you can also achieve similar results by nesting states. One caveat to this is that the $scope is not passed from parent to child controller. So instead you have to add the duplicate code in the $rootScope. I use this when there are functions I want to pass through out the whole program, such as a function to test if we are on a mobile phone, that is not dependent on any controllers. Here is a plunker example.
You can reduce a lot of your boilerplate by using a directive. I've created a simple one to replace all of your controllers. You just pass in the page-specific data through properties, and they will get bound to your scope.
routerApp.directive('pageDir', function() {
return {
restrict: 'E',
scope: {},
controller: function(pageFactory) {
vm = this;
vm.date = {
date: moment().startOf('month').valueOf(),
dateOptions: {
formatYear: 'yy',
startingDay: 1
},
format: 'dd-MMMM-yyyy',
opened: false
};
vm.open = function($event) {
vm.date.opened = true;
};
// dataservice
vm.data = []; // the structure can be different but still similar enough
vm.update = function() {
vm.data = pageFactory.get(vm.service);
};
vm.update();
},
controllerAs: 'vm',
bindToController: {
name: '#',
service: '#',
seriesLabels: '='
},
templateUrl: 'page.html',
replace: true
}
});
As you can see it's not much different than your controllers. The difference is that to use them, you'll use the directive in your route's template property to initialize it. Like so:
.state('state1', {
url: '/state1',
template: '<page-dir ' +
'name="theOne" ' +
'service="oneService" ' +
'series-labels="[\'One1\', \'Two1\', \'Three1\']"' +
'></page-dir>'
})
And that's pretty much it. I forked your Plunk to demonstrate.
http://plnkr.co/edit/NEqXeD?p=preview
EDIT: Forgot to add that you can also style the directive as you wish. Forgot to add that to the Plunk when I was removing redundant code.
I can't respond in comment but here what i will do :
I will have A ConfigFactory holding a map of page dependent variables :
{
theOne:{
name: 'theOne',
service: 'oneService',
seriesLabels: ['One1', 'Two1', 'Three1']
},
...
}
Then i will have a LogicFactory with a newInstance() method to get a proper object each time i need it.
The logicFactory will get all the data / method shared betwwen controllers.
To this LogicFactory, i will give the view-specific data. and the view will have to bind to this Factory.
And to retrieve the view-specific data i will pass the key of my configuration map in the router.
so let say the router give you #current=theOne, i will do in the controller :
var specificData = ServiceConfig.get($location.search().current);
this.logic = LogicFactory.newInstance(specificData);
Hope it help
I retouch your example, here is the result : http://plnkr.co/edit/ORzbSka8YXZUV6JNtexk?p=preview
Edit: Just to say this way, you can load the specific configuration from a remote server serving you the specific-view data
I faced completely the same issues as you described. I'm a very big supporter of keeping things DRY. When I started using Angular there was no prescribed or recommended way to do this, so I just refactored my code as I went along. As with many things I dont think their is a right or wrong way to do these things, so use whichever method you feel comfortable with. So below is what I ended up using and it has served me well.
In my applications I generally have three types of pages:
List Page - Table list of specific resource. You can
search/filter/sort your data.
Form Page - Create or Edit resource.
Display Page - Detailed view-only display page of resource/data.
I've found there are typically a lot of repetitive code in (1) and (2), and I'm not referring to features that should be extracted to a service. So to address that I'm using the following inheritance hierarchy:
List Pages
BaseListController
loadNotification()
search()
advancedSearch()
etc....
ResourceListController
any resource specific stuff
Form Pages
BaseFormController
setServerErrors()
clearServerErrors()
stuff like warn user is navigating away from this page before saving the form, and any other general features.
AbstractFormController
save()
processUpdateSuccess()
processCreateSuccess()
processServerErrors()
set any other shared options
ResourceFormController
any resource specific stuff
To enable this you need some conventions in place. I typically only have a single view template per resource for Form Pages. Using the router resolve functionality I pass in a variable to indicate if the form is being used for either Create or Edit purposes, and I publish this onto my vm. This can then be used inside your AbstractFormController to either call save or update on your data service.
To implement the controller inheritance I use Angulars $injector.invoke function passing in this as the instance. Since $injector.invoke is part of Angulars DI infrastructure, it works great as it will handle any dependencies that the base controller classes need, and I can supply any specific instance variables as I like.
Here is a small snippet of how it all is implemented:
Common.BaseFormController = function (dependencies....) {
var self = this;
this.setServerErrors = function () {
};
/* .... */
};
Common.BaseFormController['$inject'] = [dependencies....];
Common.AbstractFormController = function ($injector, other dependencies....) {
$scope.vm = {};
var vm = $scope.vm;
$injector.invoke(Common.BaseFormController, this, { $scope: $scope, $log: $log, $window: $window, alertService: alertService, any other variables.... });
/* ...... */
}
Common.AbstractFormController['$inject'] = ['$injector', other dependencies....];
CustomerFormController = function ($injector, other dependencies....) {
$injector.invoke(Common.AbstractFormController, this, {
$scope: $scope,
$log: $log,
$window: $window,
/* other services and local variable to be injected .... */
});
var vm = $scope.vm;
/* resource specific controller stuff */
}
CustomerFormController['$inject'] = ['$injector', other dependencies....];
To take things a step further, I found massive reductions in repetitive code through my data access service implementation. For the data layer convention is king. I've found that if you keep a common convention on your server API you can go a very long way with a base factory/repository/class or whatever you want to call it. The way I achieve this in AngularJs is to use a AngularJs factory that returns a base repository class, i.e. the factory returns a javascript class function with prototype definitions and not an object instance, I call it abstractRepository. Then for each resource I create a concrete repository for that specific resource that prototypically inherits from abstractRepository, so I inherit all the shared/base features from abstractRepository and define any resource specific features to the concrete repository.
I think an example will be clearer. Lets assume your server API uses the following URL convention (I'm not a REST purest, so we'll leave the convention up to whatever you want to implement):
GET -> /{resource}?listQueryString // Return resource list
GET -> /{resource}/{id} // Return single resource
GET -> /{resource}/{id}/{resource}view // Return display representation of resource
PUT -> /{resource}/{id} // Update existing resource
POST -> /{resource}/ // Create new resource
etc.
I personally use Restangular so the following example is based on it, but you should be able to easily adapt this to $http or $resource or whatever library you are using.
AbstractRepository
app.factory('abstractRepository', [function () {
function abstractRepository(restangular, route) {
this.restangular = restangular;
this.route = route;
}
abstractRepository.prototype = {
getList: function (params) {
return this.restangular.all(this.route).getList(params);
},
get: function (id) {
return this.restangular.one(this.route, id).get();
},
getView: function (id) {
return this.restangular.one(this.route, id).one(this.route + 'view').get();
},
update: function (updatedResource) {
return updatedResource.put();
},
create: function (newResource) {
return this.restangular.all(this.route).post(newResource);
}
// etc.
};
abstractRepository.extend = function (repository) {
repository.prototype = Object.create(abstractRepository.prototype);
repository.prototype.constructor = repository;
};
return abstractRepository;
}]);
Concrete repository, let's use customer as an example:
app.factory('customerRepository', ['Restangular', 'abstractRepository', function (restangular, abstractRepository) {
function customerRepository() {
abstractRepository.call(this, restangular, 'customers');
}
abstractRepository.extend(customerRepository);
return new customerRepository();
}]);
So now we have common methods for data services, which can easily be consumed in the Form and List controller base classes.
To summarize the previous answers:
Decorating controllers: as you said, this is a dirty solution; Imagine having different factories decorating the same controller, it will be very difficult (especially for other developers) to prevent collision of properties, and equally difficult to trace which factory added which properties. It's actually like having multiple inheritance in OOP, something that most modern languages prevent by design for the same reasons.
Using a directive: this can be a great solution if all your controllers are going to have the same html views, but other than that you will have to include fairly complex logic in your views which can be difficult to debug.
The approach I propose is using composition (instead of inheritance with decorators). Separate all the repetitive logic in factories, and leave only the creation of the factories in the controller.
routerApp.controller('page1Ctrl', function (Page, DateConfig, DataService) {
var vm = this;
// page dependent
vm.page = new Page('theOne', 'oneService', ['One1', 'Two1', 'Three1']);
// these variables are declared in all pages
// directive variables,
vm.date = new DateConfig()
// dataservice
vm.dataService = new DataService(vm.page.service);
//default call
vm.dataService.update();
})
.factory('Page', function () {
//constructor function
var Page = function (name, service, seriesLabels) {
this.name = name;
this.service = service;
this.seriesLabels = seriesLabels;
};
return Page;
})
.factory('DateConfig', function () {
//constructor function
var DateConfig = function () {
this.date = new Date();
this.dateOptions = {
formatYear: 'yy',
startingDay: 1
};
this.format = 'dd-MMMM-yyyy';
this.opened = false;
this.open = function ($event) {
this.opened = true;
};
};
return DateConfig;
})
This code is not tested, but I just want to give an idea. The key here is to separate the code in the factories, and add them as properties in the controller. This way the implementation is not repeated (DRY), and everything is obvious in the controller code.
You can make your controller even smaller by wrapping all the factories in a larger factory (facade), but this may make them more tightly coupled.

JavaScript variable scope - proper use

I read this style guide for angular from johnpapa. There is a snippet:
/*
* recommend
* Using function declarations
* and bindable members up top.
*/
function Avengers(dataservice, logger) {
var vm = this;
vm.avengers = [];
vm.getAvengers = getAvengers;
vm.title = 'Avengers';
activate();
function activate() {
return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}
function getAvengers() {
return dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}
So my question is in functions activate() and getAvengers(), they both reference variable (dataservice) and function (getAvengers()) outside of their scope. Is this proper use? Should I bind these 2 in the variable vm instead, e.g:
vm.getAvengers = getAvengers;
vm.dataservice = dataservice;
...
function activate() {
return vm.getAvengers().then(....);
}
function getAvengers() {
return vm.dataservice.getAvengers().then(.....);
}
Specifically for your case
Would say if you are meaning to use this within angular app would recommend not exposing the service, exposing it through this object does not add value and might down the road, when a less experienced developer modifies your code, might result in wonky access to shared dependencies.
If you want access to the dataservice objects functionality across multiple entities then register it as an angular service, and inject it to the different entities that need it.
In General
Both of the ways you are describing are perfectly correct use, but as is usually the case the answer which to use is "it depends."
Why you would use one for another would be if you wanted to expose the variable externally (i.e. if you wanted to let others access that object through the returned object, expecting others to dynamically change the service on your object)
So in this example you should ask yourself a few question
Do I want to expose this object through another object or do I want to let angular DI pass this along to the other controllers that need this functionality
Do I want to allow external entities to modify this object
Does exposing this service through my object make the use of the perceived use of this object more confusing?
But again for this particular case you should not expose it through your object ( through your variable vm, which is bound to the return object this, in this case )
The vm is a acronym for a view model (a object representation of your view) it is meant to be used within your view to bind elements, ui events to it. The dataservice and the logger seems to nothing to do with the view at all, they are just services used within a controller. If you assign them to the vm then you probably create a tightly coupling between your view and services thus it seems like a not a very good idea to me. You can think about the VM as a interface (glue) between your view and controller.
Here is a picture of the interactions between view model, controller, view and services.

Using $http outside of controllers?

I'm trying to split up and organise an AngularJS application so that it isn't just a 5000 line main.js file. Splitting off directives, etc. and using make to build working code is all fine. However, my controller has a couple of moderately complex internal classes. These used to be defined roughly as follows (only one shown for clarity):
var app = angular.module("infrasense", []);
app.controller("AppMain", function($scope, $http, $timeout) {
function NavTree(dbMain, dbTimeout, allTagTypes, allAttTypes) {
...
}
NavTree.prototype = {
...
}
...
$scope.navTree[0] = new NavTree(dbMain, dbTimeout);
...
});
The NavTree class (which holds a hierarchical tree of sites and assets in a data logging application) is rendered using a directive and uses $http internally to talk to a backend server (the tree is too complex to be held in memory at once, plus it changes).
In order to keep using a simple (cat-based) tool to generate my final code I want to move NavTree out of the controller. I currently do this by passing $http into it from inside the controller:
function NavTree($http, dbMain, dbTimeout, allTagTypes, allAttTypes) {
...
this.$http = $http;
...
}
app.controller("AppMain", function($scope, $http, $timeout) {
...
$scope.navTree[0] = new NavTree($http, dbMain, dbTimeout);
...
});
This works but feels inelegant and non-AngularJS-ish. Can anyone suggest the "proper" way to do this sort of thing?
Success! I am now a good proportion of the way towards moving these inelegant internal classes out of main.js and into services, where they belong.
The key realisation, which I'd missed when reading the documentation but which was restated in "I Wish I Knew Then What I Know Now — Life With AngularJS" is that services are just singletons which work with dependency injection.
My service is defined as follows (where "Popup" is another service that manages popup windows for error messages):
app.factory("ThingTree", function (Popup, $q, $http) {
// Database information. This is set up by the "init" function.
// (There's only one DB, and this way I only have to pass its
// connection info once.)
var dbMain = "";
var dbTimeout = 0;
...
// Each level of the tree is an array of these tree objects.
function TreeNode() {}
TreeNode.prototype = {
open: function() { ... }
...
};
return {
// Initialise the database connection.
init: function(myDbMain, myDbTimeout) {
dbMain = myDbMain;
dbTimeout = myDbTimeout;
...
},
// Create a tree and return the root node.
create: function() { ... },
...
}
});
Thanks for the prods in the right direction!

AngularJS. Best practice concerning proper two way data binding from a service

I have an 'Account' service which acts as a centralized data storage used in multiple controllers and views. This service will hold, besides getter and setter methods all data associated with two types of users, being a logged in user and an anomonous user.
The idea behind this service is that it should be used as a centralized data structure which will keep all associated views and controllers in synch. Eg: If a user logs in whe will know his email and all 'newsletter subscription' fields can be prefilled.
The current setup i use is as below. Note that each Async call on the server will return a promise.
// The Service
angular.module('A').provider('AccountService', [function() {
this.$get = function ($resource) {
var Account = {
getLoggedInAccountAsync : function () {
var Account = $resource(WebApi.Config.apiUrl + 'Account/Get');
return Account.get();
},
getUserData : function () {
return Account.userData;
},
setUserData : function (accountData) {
var merge = angular.extend((Account.currentAccountData || {}), accountData);
Account.currentAccountData = merge;
}
};
return Account;
}
});
// Our main app controller. Get's fired on every page load.
angular.module('A').controller('MainController', ['AccountService', function (AccountService) {
var loggedInAccount = AccountService.getLoggedInAccountAsync();
loggedInAccount.$then(loggedInAccountPromiseResolved);
function loggedInAccountPromiseResolved (response) {
if (response.data.isLoggedIn) {
AccountService.setAccountData(response.data);
}
};
return $scope.MainController= this;
}])
// Our specific controller. For example a newsletter subscription.
angular.module('A').controller('SpecificController', ['AccountService', function (AccountService) {
this.getUserData = AccountService.getUserData;
return $scope.Controller = this;
}])
// Newsletter subscription view
<input id="email" type="email" name="email" ng-model="SpecificController.getUserData().UserName"/>
Using the above code ensures that whenever we use the service to bind our data via Controller.getUserData().property to our view it will stay in synch throughout our entire app.
The code above will also throw if for example the .UserName value is not defined, or if there is no account data at all in case of an anomonous user. One way around this is to 'dump' our 'template' object within our service with null values this will ensure the key/values exist. Another way would be to use watchers and let our controller(s) 'write' to our service in case an user fills a field.
The first option gives me more flexability as i can centralize all the data binding in the service but it feels dirty because you never know what will happen with the server data, it could come in as a different format for example.
Does anyone have any other solution? I would prefer to not let my controllers do the writing of the data to the service.
Thank you for your time!
There are many questions here, I will answer as best as I can.
No, what you are doing is not a "best practice".
First, The model should be injected directly into the view, and maintaining the value is the responsibility of the Controller, with the help of "$scope". Your "ng-model="Controller.getUserData().UserName" is bad. There is a good example of what to do in the end of the blog article I noticed below.
Second, when you fetch data from a service, most of the time, the answer will be asynchronous, so you'd better take a look at the Promise API. The official doc of AngularJS is not always fantastic and sometimes the answer can be found on google groups or in blog article.
For your problem, here is a good article : http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/
Firstly, if you use service in AngularJS, we expect a call to server or whatever that may not return data instantly. So we have two approaches of using ugly callback or beautiful $q.
I prefer using promise since it's cleaner to write. I will rewrite your snippets in a better way :
// Service
angular.module('A').provider('AccountService', [function() {
this.$get = function ($resource, $q) {
var Account = {
getUserData : function () {
var d = $q.defer();
d.resolve(Account.userData);
return d.promise;
}
};
return Account;
}
});
// Controller
angular.module('A').controller('Controller', ['AccountService', function (AccountService) {
var init = function() {
getUserData();
};
var getUserData = function() {
AccountService.getUserData().then(function(data) { $scope.username = data; });
};
init();
//return $scope.Controller = this; // you don't need to do this
}])
HTML:
<input id="email" type="email" name="email" ng-model="username"/>

Categories

Resources