I'm building a web application with various visualization components, made up of Backbone.js Models and Views:
The "PopulationVisualization component" might for example have:
a main model - storing the state of the component
several Backbone views (timesliderView, legendView etc.) - listening to changes on the model
All of these components depend on external dataManagers and dataSource objects but otherwise they are supposed to be decoupled.
On a given page, I'd like to instantiate an instance of the PopulationVisualization component. I'd also like to listen for changes in the main model of that component so that I could serialize its state in the URL.
1) What would this look like if I tried adopting the AMD module pattern?
2) Would I make one module of the PopulationVisualization component or several?
3) Would I expose module-level methods as an API or would I provide direct manipulation of the inner Models and Views?
Thanks.
To answer you questions, here's my advice, answering all three:
Modules should be as small as possible, so I would create a new module for each view, one for the module, and one for the serialization logic. Then I would create one module that ties all this together so that outside code doesn't have to deal with models, views, or serialization.
Here is my first stab at something like this:
// components/populationVisualization/Model.js
define(function (require, exports, module) {
return Backbone.Model.extend({ /* ... */});
});
// components/populationVisualization/views/Timeslider.js
define(function (require, exports, module) {
return Backbone.View.extend({ /* ... */});
});
// components/populationVisualization/views/Legend.js
define(function (require, exports, module) {
return Backbone.View.extend({ /* ... */});
});
// components/populationVisualization/serializer.js
define(function (require, exports, module) {
exports.setupSerialization = function (model) {
// ...
};
});
// components/populationVisualization.js
define(function (require, exports, module) {
var Model = require("./populationVisualization/Model");
var TimesliderView = require("./populationVisualization/views/Timeslider");
var LegendView = require("./populationVisualization/views/Legend");
var serializer = require("./populationVisualization/serializer");
exports.createAndRender = function (modelParams, $el) {
var model = new Model(modelParams);
serializer.setupSerialization(model);
var timesliderView = new TimesliderView({ model: model });
var legendView = new LegendView({ model: model });
$el.append(timesliderView.el);
$el.append(legendView.el);
};
});
Elsewhere in the app you would only require("components/populationVisualization") and call that module's createAndRender method with the appropriate parameters.
Related
Angular JS conceptual overview. View-independent business logic: Services (heading).
The description says - moving view-independent logic from the controller into a service, yet the code says factory. What am I missing here?
angular.module('finance2', [])
.factory('currencyConverter', function() {
var currencies = ['USD', 'EUR', 'CNY'];
Link to the resource
The factory method ('recipe') is a way of creating a 'Service'.
You can also create 'Services' with the service, constant, value, and provider recipes ('methods').
However you do it, you'll end up instantiating an object that is conceptually a 'Service'.
It's been acknowledged widely that this is a confusing aspect of Angular. See this classic Stackoverlow question.
The developer guide does a good job of clarifying these concepts too:
Each web application you build is composed of objects that collaborate to get stuff done. These objects need to be instantiated and wired together for the app to work. In Angular apps most of these objects are instantiated and wired together automatically by the injector service.
The injector creates two types of objects, services and specialized objects.
Services are objects whose API is defined by the developer writing the service.
Specialized objects conform to a specific Angular framework API. These objects are one of controllers, directives, filters or animations.
The injector needs to know how to create these objects. You tell it by registering a "recipe" for creating your object with the injector. There are five recipe types.
The most verbose, but also the most comprehensive one is a Provider recipe. The remaining four recipe types — Value, Factory, Service and Constant — are just syntactic sugar on top of a provider recipe.
Coming from a Java background, I really like the concept of angular factories; we get to mimic POJOs here to an extent. I get to attach meaningful methods and execute logic that's all self-contained within the model itself. Whereas for services, I tend to treat those as I'd treat a service on the server-side, simply for fetching data.
For instance, if we were building a Twitter clone of some sort, for the tweet stream, I'd have a TweetSteamFactory that internally fetches data using TweetService to get the latest tweets. Maybe my factory has a getNextPage() method, which is bound to an ngClick somewhere - when fired, it of course makes its call with TweetService.
At any rate, I do see a pretty clear distinction between services and factories, although my understanding could be misguided.
http://plnkr.co/edit/SqPf212nE5GrSPcZdo5K
Controller
app.controller('MyController', function(FoobarFactory) {
FoobarFactory()
done(function(factory) {
var factory = factory;
$scope.data = factory.getData();
$scope.baz = factory.getBaz();
})
});
Factory
app.factory('FoobarFactory', ['MyService', function(MyService) {
function Foobar() {}; // Empty constructor
angular.extend(Foobar.prototype, {
_init: function() {
var deferred = $.Deferred();
var foobar = this;
this.baz = true;
this.data = undefined;
MyService.getData()
.done(function(data) {
foobar.data = data;
deferred.resolve(foobar);
})
deferred.resolve();
return deferred.promise();
},
getBaz: function() {
return this.baz;
},
getData: function() {
return this.data;
}
});
return function () {
var deferred = $.Deferred();
var foobar = new Foobar();
foobar._init()
.done(function() {
deferred.resolve(foobar);
})
.fail(function(error) {
deferred.reject(error);
});
return deferred.promise();
};
}]);
I am using angular and grafana in my project.
I have a service -> dashboardViewStateSrv
My Service Code :
define([
'angular',
'lodash',
'jquery',
],
function (angular, _, $) {
'use strict';
var module = angular.module('grafana.services');
module.factory('dashboardViewStateSrv', function($location, $timeout) {
function DashboardViewState($scope) {
var self = this;
self.state = {};
self.panelScopes = [];
self.$scope = $scope;
// something
}
return {
create: function($scope) {
return new DashboardViewState($scope);
}
};
});
});
In My side menu controller :
$scope.dashboardViewState = dashboardViewStateSrv.create($scope);
if ($scope.dashboardViewState) {
if($scope.dashboardViewState.state.citreedepth){
depth = +$scope.dashboardViewState.state.citreedepth;
}
}
In My Dashboard controller :
$scope.dashboardViewState = dashboardViewStateSrv.create($scope);
DashboardViewState object is being created twice (Dashboard Ctrl and Side Menu ctrl).
I am creating DashboardViewState object twice, I want to avoid that. If I can avoid creating DashboardViewState object in Side Menu ctrl?
There should be only one view state. As per my understanding all the services are singleton in angular.
Please guide me what I can do?
Services are singletons, they are essentially a constructure function allowing you to use the this keyword inside them. They are instantiated once when first created then that instance is shared throughout your app.
Factories are, well, factories. Somewhere in Angular it will call Object.create() on the object your return from a factory. Meaning each call will return a new instance of it.
So in your use case your creating a new object twice. First by using a factory, then second by returning a new object from that factory.
This may help http://blog.thoughtram.io/angular/2015/07/07/service-vs-factory-once-and-for-all.html
So if you want a single instance of an object through your application you should use .service() not .factory();
If you want to instantiate a new Object only once you could use a service. Have the object as a property and a get method. The service could check if the object is already created and if not make it.
something like this (example code, not tested):
module.service('dashboardViewStateSrv', function($location, $timeout) {
this.object;
this.get = function (){
if(this.object === undefined) {
return this.object = Object.create({}); //Create your object
} else {
return this.object;
}
}
});
however i did notice some booboo's (Sorry always reminds me of Hook when i say that). First you do not need to alias the this keyword, were not working in an jQuery callback, even if we were you can bind your function etc.
Second and this is important. Your passing a $scope object into your service, this is very very bad. Not just for this reason but how can controllers share a single service object if it has a reference to a $scope? Your services should be a collection of single simple methods that have their input and output data. They work on that and continue. You can then chain them and pass them the specific data each method needs. They shouldn't be a monolithic object that has everything in hidden properties, think functional, a pipeline if you will.
What is the best practice when you have a Factory with like 4 related methods, each of those is really long (200+ lines of code) and you want to avoid having a huge file of 800+ lines of code?
One solution is to create 4 factories under the same module , each one exposing a single method and in its own file. Then inject all of them in the controller that requires them.
Is there a better solution? I'd like to create the Factory once, then add methods to it like I was doing module augmentation using the module pattern. Then I just need to inject the Factory once and have all its methods available.
I'd like to create the Factory once, then add methods to it like I was doing module augmentation using the module pattern. Then I just need to inject the Factory once and have all its methods available.
Yes, that will work:
// In your main module file.
angular.module('myModule', []);
// file1.js
angular.module('myModule').factory('BigService1', function(){
// Your actual implementation here.
console.log('I am BigService1');
});
// file2.js
angular.module('myModule').factory('BigService2', function(){
// Your actual implementation here.
console.log('I am BigService2');
});
// ... additional files
// Separate file/service to aggregate your big factory services.
angular.module('myModule').service('AggregateService', [
// Add dependencies
'BigService1','BigService2',
function(BigService1, BigService2){
// Return an object that exposes the factory interfaces.
return {
service1: BigService1,
service2: BigService2
};
}]);
You could also arrange your code the old vanilla js style and then access those libraries in your services like this:
var Mirko = { };
Mirko.FunService = {
getAllSomething : function (p1, p2) {
},
...
};
angular.module('myModule').factory('BigService', function(){
return {
methodOne : Mirko.getAllSomething,
...
};
});
You will end up with one object Mirko that you can access outside the scope of your angular app, but it will in no way differ from other externals api's (not written for angular) you would want to use in your app. The way you handle your own 'external' api can be done the oldschool fashion way, one file per 'class' e.g. 'FunService'.
It might not be the prettiest solution but it will be an easy abstraction.
Just saying...
Maybe segment your methods through other factories, which can be injected to your "main" factory :
// file 1
angular.module('foo').factory('segment1', function () {
return {
method: function () {
// ... super long method
}
};
});
// file 2
angular.module('foo').factory('segment2', function () {
return {
method: function () {
// ... super long method
}
};
});
// your main factory file
angular.module('foo').factory('myFactory', function (segment1, segment2) {
return {
method1: segment1.method,
method2: segment2.method
};
}
I've been writing an application of mine using Marionette, but I feel a bit confused that application extensions are failing.
There seems to be no way to do this sort of thing in MarionetteJS:
var SuperApplication = Backbone.Marionette.Application.extend({
'modules': {
'foo': function (module, app) {
// module definition
},
'bar': function (module, app) {
// module definition
}
}
});
var app = new SuperApplication();
While I have been developing, it has become important to occasionally create a new Application instance and call .module numerous times before the application object becomes useful. It negatively affects testing reliability of each Application object created, because there are no guarantees that they are of the same prototype.
The following method, ugly as it is, cannot properly attach a module definition to an Application as a prototype:
SecondApplication = Backbone.Marionette.Application.extend({
'foo': function () {
var args = slice(arguments);
args.unshift(this);
args.push(function (module, app) {
// module definition
});
return Backbone.Marionette.Module.create.apply(
Backbone.Marionette.Module, args);
}
});
Am I supposed to do this some other way?
EDIT: Marionette modules are now deprecated and will be removed in the next major release
Use ES6/CommonJS/AMD modules instead. Capability sharing can be accomplished in vanilla JavaScript using prototypical inheritance or composition.
Original answer (Marionette pre 2.0)
You're right, you can't create modules like that; you have to use the App.module() pattern.
It's unusual that you're creating multiple application instances. Your typical setup has a single Application instance with Modules and subModules used to partition the site into logical areas, like so:
var MyApp = new Marionette.Application();
MyApp.module('forums', function(mod, app) {});
MyApp.module('gallery', function(mod, app) {});
MyApp.module('gallery.kitchen', function(mod, app) {});
MyApp.module('gallery.diningroom', function(mod, app) {});
MyApp.start();
If you want numerous modules to have similar capability (or to have the same initial definition), the module method can be called multiple times with the same module name to change the functionality. This can be used to create a base module of sorts that can be extended:
var MyApp = new Marionette.Application();
// Create your shared definition:
var ModuleDefaults = { definition: function(mod, app) {
mod.type = 'gallery';
mod.addInitializer(function() { /* do work */ });
}};
// create your modules (quickly!)
_.each(['forums', 'gallery', 'gallery.kitchen', 'gallery.master'],
function(moduleName) {
MyApp.module(moduleName, ModuleDefaults.definition);
});
// override whatever your want:
MyApp.module('forums', function(mod, app) {
mod.type = 'forum';
mod.addFinalizer(function() { /* take out the papers and the trash */ });
});
MyApp.start();
I have a number of backbone models, organized in collections and connected to their corresponding views and/or collections of views. Some of these models that do not belong to the same collection need to trigger an event which is of interest to another model (and maybe more than one).
The recommended way to deal with this is, I think, the "global event dispatcher/aggregator" as described here and other places.
However, I also happen to be using require.js, which seems to go against the idea of attaching the dispatcher/aggregator to the application's namespace object -- or am I wrong here?
So my question is: using require.js how can I have a number of different backbone models trigger an event that will be handled by another model?
A similar solution to what #Andreas proposed but without Backbone.Marionette (heavily inspired nonetheless, see the article linked in the question).
All you have to do is to define a module that creates a singleton of an event listener and require this object in the modules where you want to trigger an event or listen to this event.
Let's say you have app/channel.js defining your channel
define(['backbone', 'underscore'], function (Backbone, _) {
var channel = _.extend({}, Backbone.Events);
return channel;
});
You can then use it as a listener via
require(['app/channel'], function (channel) {
channel.on('app.event', function () {
console.log('app.event');
});
});
and you can trigger an event on this channel via
require(['app/channel'], function (channel) {
channel.trigger('app.event');
});
We using Marionettes app.vent (which is the global event transmitter for our application), allong with require js and it works really well.
app
define(, function(){
return new Backbone.Marionette.Application();
})
Model1
define(['app'], function(app){
return Backbone.Marionette.Model.extend({
initialize: function(){
this.bindTo('app.vent', 'create:model2', this.toSomething, this);
}
})
})
Model2
define(['app'], function(app){
return Backbone.Marionette.Model.extend({
initialize: function(){
app.vent.trigger('create:model2', this);
}
})
})