How to model my data in angular js? - javascript

Two questions:
1) I am using angular-js and I am looking for a way to model my data.
I have experience with the idea of a Model in backbone.
Is this a factory in angular? Is it an ok "best practice" to have a LOT of factories (one for each type of model), basically mimicking "class" with "factory"?
The factory represents my model with some "helper" functions (like a model on Backbone) or my factory represents a list of members (like a collection on backbone)?
2) For example, Let's say I want to create objects to map to REST resources and I have an "member" resource that I get by GET-ing: /members/123. This returns a json object with various fields. Something like:
{id: 123, name: 'angularjs', date_created: 1235845}
Now, I want some kind of representation in my AngularJS app of this "member" object. This representation is more than just a mapping of the fields - I want to add "helper" functions, for example a function that converts the date_create field into something human-readable.
How to represent this? using factory + $resource

Here is good example how tot use factory in your case.
Factory
angular.module('myApp').factory('Member', function($http) {
// Member is a class which we can use for retrieving and
// updating data on the server
var Member = function(data) {
angular.extend(this, data);
}
// a static method to retrieve Member by id
Member.get = function(id) {
return $http.get('/Member/' + id).
then(function(response) {
return new Member(response.data);
});
};
// an instance method to create a new Member
Member.prototype.create = function() {
var member= this;
return $http.post('/Member/', member).then(function(response) {
book.id = response.data.id;
return member;
});
}
return Member;
});
Then in controller you can write something like:
Controller
var controller = function(Member) {
var memeber= new Member();
memeber.name = 'Fox';
memeber.create();
// to retrieve a memeber
var memeberPromise = Memeber.get(123);
memeberPromise.then(function(b) {
memeber = b;
});
};
Here I used id only but you understand the flow
Hope it will help you to sort things out

Related

How do you prevent Knockback.js creating view models for null relations?

If my backbone models have relationships (for example, created by backbone-relational), those relationships might be nullable, leading the foreign key fields to sometimes be null.
If I have several knockback view models, and I've specified factories so that when following relations I get the view models with the desired functionality for the model, when it encounters an attribute that is null, it goes ahead and creates a view model passing null as the model, which likely breaks most of the view model's functionality.
Example:
var ChildViewModel = kb.ViewModel.extend({
constructor: function (model, options) {
// this is the problem I'm trying to avoid - creating a view model with
// no model
if (!model) {
// just report the error somehow - the jsfiddle has the
// relevant HTML element
document.getElementById("error").innerHTML = "ChildModelView initialised without a model!";
}
kb.ViewModel.prototype.constructor.apply(this, arguments);
}
});
var ParentViewModel = kb.ViewModel.extend({
constructor: function (model, options) {
// specify factories here, because this way you can easily deal with
// reverse relationships, or complicated relationship trees when you
// have a large number of different types of view model.
kb.ViewModel.prototype.constructor.call(
this,
model,
{
factories: {relation1: ChildViewModel,
relation2: ChildViewModel},
options: options
}
);
}
});
// if we assume that relation2 is a nullable relationship, backbone-relational,
// for example, would give us a model that looks like this:
var model = new Backbone.Model({
id: 1,
relation1: new Backbone.Model({id: 2}), // this works fine
relation2: null // this causes a problem
});
var view_model = new ParentViewModel(model);
And the fiddle:
https://jsfiddle.net/vbw44vac/1/
I've just discovered what I think might be a reasonable solution.
Your factories don't have to be ViewModel "classes", but can be factory functions. So:
var nullable = function (view_model_class) {
var factory = function (object, options) {
if (object === null) return object;
return new view_model_class(object, options);
};
return factory;
};
And then when you're defining your factories:
kb.ViewModel.prototype.constructor.call(
this,
model,
{
factories: {relation1: nullable(ChildViewModel),
relation2: nullable(ChildViewModel)},
options: options
}
);

Sails.js : Add custom instace method result as a model property

I'm relatively new to JavaScript programming, so this problem may have a trivial solution. Working with Sails.js, I've created this model.
module.exports = {
tableName: 'FOO_TABLE',
attributes: {
FOO: 'string',
BAR: 'number',
BAR2: function() {
return this.BAR + 1;
}
},
};
Then, in a controller I get all the instances:
FOO_MODEL.find().exec(function(err, FOOS) {
return res.view({data: JSON.stringify(FOOS)});
});
The problem is that inside FOOS, it's not the BAR2 method. I've come with this solution (using Underscore.js):
FOOS = _.map(FOOS, function(FOO){ FOO.BAR2 = FOO.BAR2(); return FOO; });
But I don't see it efficient / smart, as I think I will probably find this problem again. How would you do it? Thank you
If all you want is to set a calculated value for each new instance, you could set BAR2 to be of type number in the model (instead of a function), and add a beforeCreate class method like:
beforeCreate: function(values, cb) {
values.BAR2 = values.BAR + 1;
return cb();
}
If you want to keep BAR2 as an instance method, but have it serialized along with the object, you could override the default toJSON instance method:
toJSON: function() {
var obj = this.toObject();
obj.BAR2 = obj.BAR2();
return obj;
}
Any time an instance is stringified, its toJSON method will be called.
Check Your Query Result
If you're calling your instance-method during a request, namely, within a query-callback -- you may want to check what result you are getting passed in.
In my case, I am using the Sails/Waterline ORM Model-method, find instead of findOne (like a bonehead), which actually argues a collection (array). So I was attempting something along the lines of:
[ {...} ].hasItem(id)
... while what I needed was something like:
myQueryResults[0].hasItem(id)
Or rather, query correctly using findOne. Idiocy is apparently a poor practice, but it happens I guess.
Hope this help!

Declaring variables on a backbone model without setting defaults

I'm just starting out with backbone.js and I'm looking for a way of declaring fields on a model without having to provide defaults. It's really just for reference, so that when I start creating instances, I can see what fields I need to initialize.
With something like java I'd write
public class CartLine{
StockItem stockItem;
int quantity;
public int getPrice(){
return stockItem.getPrice() * quantity;
}
public int getStockID(){
//
}
}
However with backbone models, I'm referencing the fields in my method's but I'm not actually declaring them - It looks like I could easily create a CartLine object that doesn't contain a stockItem attribute or a quantity attribute. It feels strange not to mention the fields when I declare the object. Particularly as the object is supposed to represent an entity on the server.
var CartLine = Backbone.Model.extend({
getStockID: function(){
return this.stockItem.id;
},
getTotalPrice: function() {
return this.quantity * this.StockItem.get('price');
}
});
I guess I can add some sort of reference by using validate -
CartLine.validate = function(attrs){
if (!(attrs.stockItem instanceof StockItem)){
return "No Valid StockItem set";
}
if (typeof attrs.quantity !== 'number'){
return "No quantity set";
}
}
But my question is - am I missing something? Is there an established pattern for this?
The defaults are really for "fields" or data that is transferred back and forth from the server as part of the json.
If you just want to create some member variables as part of the Model, which are proprietary and not going to be sent back and forth to the server, then you can declare them a) on the object itself or b) in the initialize method (called during construction), and they can be passed in as part of opts:
var Widget = Backbone.Model.extend({
widgetCount: 0,
defaults: {
id: null,
name: null
}
initialize: function(attr, opts) {
// attr contains the "fields" set on the model
// opts contains anything passed in after attr
// so we can do things like this
if( opts && opts.widgetCount ) {
this.widgetCount = opts.widgetCount;
}
}
});
var widget = new Widget({name: 'the blue one'}, {widgetCount: 20});
Keep in mind that if you declare objects or arrays on the class, they are essentially constants and changing them will modify all instances:
var Widget = Backbone.Model.extend({
someOpts: { one: 1, two: 2},
initialize: function(attr, opts) {
// this is probably not going to do what you want because it will
// modify `someOpts` for all Widget instances.
this.someOpts.one = opts.one;
}
});

Adding more functions to Backbone Models

I am attempting to add some functions to backbone so that I can communicate with mongodb. Now I know this won't work client side; however, I do like backbone's functionality for server side model logic as well. I noticed that I would be doing a bunch of repeat work if I kept adding the same functionality for each model so decided to create a "app_model" file to extend backbone when I'm server side. I also don't want to override the standard Backbone functions because they will be useful client side.
So let's take this user class for instance:
var Backbone = require('./app_model');
var User = Backbone.Model.extend({
name : "users",
defaults: function() {
return {
username: "default",
role: 2,
created: new Date(),
updated: new Date(),
logged: new Date()
};
},
idAttribute: "username",
/**
* A predefined listing of user roles
*/
userRoles: [
"admin", //0
"author", //1
"user" //2
],
initialize: function() {
if(!!app) {
this.svrInit();
}
}
});
module.exports = User;
And I want to append functions onto backbone by using my "app_model.js" file, which looks something like this currently:
var Backbone = require('backbone'),
Deferred = require('Deferred'),
when = Deferred.when;
Backbone.Model.prototype.svrInit = function() {
//TODO: perhaps the code below should be made static some how so we don't have a bunch of instances of collection
var model = this;
if(!!app.db){
app.db.collection(this.name,function(err,collection){
model.collection = collection;
});
}
};
Backbone.Model.prototype.svrSave = function() {
var model = this.toJSON();
var dfd = new Deferred();
this.collection.insert(model, {safe:true}, function(err, result){
dfd.resolve();
});
return dfd;
};
Backbone.Model.prototype.svrFind = function(options) {
var model = this.toJSON();
var dfd = new Deferred();
this.collection.find(options, {safe:true}, function(err, result){
dfd.resolve();
});
return dfd;
};
module.exports = Backbone;
I ran my tests when I abstracted this out and it seemed to work alright. Is there a better way to do any of this? Any pit falls? I am using the global "app" variable, is that bad? If so what are some ways around it? I do find it ugly that I had to put this.svrInit() inside the init function at the model level is there anyway to automatically make that happen after creation?
So I've been thinking about this question for a couple days and I the cleanest thing I've come up with is something like this:
var MyModel = function( attributes, options ) {
Backbone.Model.apply( this, arguments );
this.specialInitializer();
};
MyModel.extend = Backbone.Model.extend;
_.extend( MyModel.prototype, Backbone.Model.prototype, {
specialInitializer: function() {
// called after the users 'initialize'
console.log("MyModel initialized.", this);
},
otherNewMethod: function() {
// this is just like any other instance method,
// just as if Backbone.Model implemented it
}
} );
So what this does is basically make an entirely new 'kind' of Backbone.Model. One which also calls specialInitializer. If you look at the backbone source just after the constructor definition for Backbone.Model you'll see this is a similar strategy.
Construct the instance.
Call an initializer the implementor is supposed to define.
Extend the prototype with functionality (in their case Backbone.Events, in ours, Backbone.Model).
Your new initializer can of course call whatever else it needs, etc.
As for your other questions about the static collection stuff and global app variable, I'm afraid I don't follow exactly what is going on there since I don't see a definition for app and don't know what you're using the collection for.
Here's a fiddle that demonstrates this with some extra logging and such.
I'm working on a fairly large code-base with 4-5 levels of inheritance in the views. This is the pattern I'm using:
var BaseView = Backbone.Model.extend({
somefunc: function() {
//contents
},
otherfunc: function(a,b,c) {
//contents
},
//...
});
var User = BaseView.extend({
// things in user view can now access somefunc and otherfunc
});
Here's a quick example in a jsfiddle (note the doSearch function being inherited)

Set backbone model attribute value to another model

Can I set another backbone model as its attribute ?
I have a model that represents a basic entity, I 'd like to reuse it as part of something else.
But, it looks like Backbone's model attribute value can only be String.
Sort answer: yes you can:
myHouse.set({ door: new Door() })
But, in my opinion, is not a good idea to do so, because I don't think Backbone is expecting to found Objects in the Model.attributes. I didn't try but I don't think methods like Model.toJSON are gonna have a correct behavior if someone of the attributes is an Object.
But said so, I don't see any problem to declare real attributes in the Model that make reference to objects like:
myHouse.door = new Door();
If I understood correctly you need to do something like that:
var ModelOne = Backbone.Model.extend({
method : function() {
console.log('ModelOne > method');
},
sayHello : function() {
console.log('ModelOne > hello');
}
});
var ModelTwo = ModelOne.extend({
method : function() {
ModelOne.prototype.method.apply(this);
console.log('ModelTwo > method');
}
});
var methodTwo = new ModelTwo();
methodTwo.method();
methodTwo.sayHello();
Output:
ModelOne > method
ModelTwo > method
ModelOne > hello
One more variation of setting Backbone.Model to the attribute of another model is to set it as default value:
var UserModel = Backbone.Model.extend({
defaults: {
id: null,
name: null,
address: new AddressModel()
}
});
var someUser = new UserModel();
someUser.address.set('country', 'ZA');
When you are doing someUser.save() data enclosed in someUser.attributes.address will be a usual data object.
! One thing not yet tested by me is population of AddressModel upon someUser.fetch()

Categories

Resources