I have a singleton that creates classes for me. Each class is in a separate file.
// singleton.js
angular.module('mymodule')
.service('singleton', SingletonClass)
// someclass1.js, someclass2.js, ...
// Multiple files with same layout. Each web page has different # of these files.
// Therefore, it's hard to inject them statically
// (ie, via aggregator service/factory).
angular.module('mymodule')
.??('someClass', function(singleton) {
classOptions = {}; // details here
// creates a class with options and adds it to `singleton`s cache
singleton.addClass(classOptions);
})
// yet another file
angular.module('mymodule')
.controller('myController', function(singleton) {
singleton.getClasses(); // gets all classes added by #addClass method
});
Basically, I want each individual "class" to be self-contained in its own file similar to how modules are self-contained. That means I don't want want singleton or any aggregator service/factory to know of the existence of these classes in its creation.
Edit:
I'd like to clarify: I'm using singleton instead of individually injecting the created classes because I don't yet know which classes will be created yet. The web page will dynamically load certain js files. Each file will inject the singleton object and use the addClass method to create a new class and store it inside the singleton object itself. The controllers using singleton.getClasses() won't know how many or which classes it's getting, but they have a uniform interface which allows it to use them in a predictable fashion.
You could try setting up providers for each class (per my comment above), though that seems . . . time consuming. Maybe you just don't need it? Make each class a separate file in a closure (like you use to need to do with jquery), and grab the singleton object from the injector:
// file 1 - Add other files for other classes.
;(function(window, $) {
$(function() {
var classOptions = {};
$('body').injector().get('singleton').addClass(classOptions);
});
})(window, jQuery);
This is not exactly the angular way, and angular purists will call for your head, but I think it will achieve what you're after if you don't want to mess with providers.
Related
I'm relatively new to AngularJS and the problem I'm facing is one of those "I want to inject a Service into an app.config" type of scenarios, which I realise cannot be done. (I'm comfortable with the different between Service and Provider, and why a Service cannot be injected into a .config.)
What I am trying to accomplish is to use angular-schema-form together with angular-translate such that field titles in generated forms are translated.
There is an issue where someone asks how to do this, and the advice given is to take advantage of angular-schema-form's postProcess, which is a property of the Provider. This callback gives you the form object before it is rendered, giving you the opportunity to manipulate it with user code. Therefore translation could be done within here.
The postProcess method is called on the Provider, so it is done within an app.config:
app.config(function(schemaFormProvider, $translateProvider) {
schemaFormProvider.postProcess(function(form){
// within here I can inspect the form object, find all
// properties whose key is "title", and then perform
// language translation on their values.
So, that is apparently the place where I have an opportunity to manipulate control titles and so on.
Over to the angular-translate library, for me to 'manually' translate strings, I can use the $translate service. This provides both synchronous and asynchronous methods to translate a given key string. The synchronous one is $translate.instant(key).
To glue these two together, what I have tried so far (which does work) is to create a 'bridge' method like this:
var app = angular.module('myApplicationName', ['schemaForm', 'pascalprecht.translate']);
....
app.config(function(schemaFormProvider, $translateProvider) {
schemaFormProvider.postProcess(function(form){
// ... code here which iterates over properties
// and finds all control titles ...
key = app.myTranslate(key);
// ....
}
....
});
app.myTranslate = function (key) {
var service = angular.injector(['ng', 'myApplicationName']).get("$translate");
return service.instant(key);
}
This does work, but it seems ugly and unsafe (as presumably there's no guarantee $translate is ready when the callback is first invoked) and the calls to angular.injector(['ng', 'myApplicationName']).get... are presumably expensive.
Is there a better way, or is this the only way I'm going to get it done, considering the constraints of the libraries I'm working with?
I have also considered an alternative approach altogether, which would be to instead perform the translations on the schema or form objects before they are processed by angular-schema-form. This could be done from within Controllers, eliminating the problem of accessing the $translate service. I may end up going down that route, but it would still be nice to understand the best solution for the above scenario.
I am trying to have external modules change my $translateProvider.translation on the main module. see this as a "tranlation plugin" for my app.
it seems like changing translations from the $translate service is not possible.
mymodule.service('MyService', function ($translateProvider) {
var lib = function () {
//EDITED FOR BREVITY
this._registerTranslations = function (ctrl) {
if (!ctrl.i18n) return;
for (var name in ctrl.i18n) {
/////////////////////////////
// THIS IS THE PLACE, OBVIOUSLY PROVIDER IS NOT AVAILABLE!!!!
$translateProvider.translations(name, ctrl.i18n[name]);
//////////////////////////////
}
};
//EDITED FOR BREVITY
};
return new lib();
});
anyone with a bright idea?
So, to answer your question: there's no way to extend existing translations during runtime with $translate service without using asynchronous loading. I wonder why you want to do that anyway, because adding translations in such a way means that they are already there (otherwise you would obviously use asynchronous loading).
Have a look at the Asynchronous loading page. You can create a factory that will load a translation from wherever you want.
I created an Angular constant to hold new translations. If I want to add a new translation, I add it to the constant. Then in my custom loader, I first check the constant to see if the translation exists (either a new one, or an updated one). If so, I load it from the constant. If not, I load it from a .json file (or wherever you load your initial translations from). Use $translate.refresh() to force translations to be reloaded and reevaluated.
Demo here
The demo is pretty simple. You would need to do a little more work if you wanted to just change a subset of the translations, but you get the general idea.
From the AngularJS docs (https://docs.angularjs.org/guide/providers):
You should use the Provider recipe only when you want to expose an API for application-wide configuration that must be made before the application starts. This is usually interesting only for reusable services whose behavior might need to vary slightly between applications.
Providers are to be used with the application's .config function. $translateProvider for configuration, $translate for other services and controllers.
I'm trying to minimize server calls by avoiding any requests I can.
Let's say, for the sake of a example, I have a collection of Matchboxes which belong to Users and have Tags assigned, and then also have a collection of Tags and a collection of Users as part of other pages. Getting matchboxes retrieves the user and tag info, so that I can instantiate all required models with one request, accessing the Tags and Users pages retrieves similar collections (only they deal only with their respective models).
My problem: if matchboxes is one page, and tags and users are two other pages, what's a good way to make sure only one model is ever instantiated for any given entity, ie. if I go into users or tags and edit an entry associated with a matchbox the matchbox entry should have the same entry assigned allowing it to listen and react to the updates with out requiring sending requests when going back to the matchbox page in the example.
I've looked over Backbone.relational but it doesn't seem to do what I need, and would rather not wall myself into a framework. So solutions involving patterns are preferable.
Ended up using http://pathable.github.io/supermodel/ which uses the pattern of overwriting the model attribute on collections with a custom function which calls a special Model.create that itself returns an existing (updated with the new values if necessary) instance of said model. The Model.create call has to be used everywhere else in code for unique models.
So essentially every model has a all() method which is a collection of all instances by id. Whenever a model is added it checks it against the collection and returns an existing object if it exists; the data used to instantiate the duplicate is used to update the existing object ensuring data is not stale (which is a nice bonus to the uniqueness I wanted).
The cleanest method seems to be to just wrap the model function into a function that returns it for clearer use; then for every collection that needs to have unique models wrap said model in the function. I came up with this at the moment:
app.single = function (modelPrototype) {
return function (attrs, options) {
return modelPrototype.create(attrs, options);
};
};
(app there is just a scope global, tied to a particular namespace)
So in collections instead of,
model: app.Model
I would then use
model: app.single(app.Model),
Whenever I update a entry in one part of the application the change will trickle down to every other collection/model since if it's the same instance from the user's perspective it's the same instance in code too.
That's about all I could tell from reading the pattern though the code and documentation. Which is sufficient for my own uses.
I suspect this solution would still have some issues if you're caching renders but I haven't found a use for that (prefer to re-render whenever I can to avoid dealing with various artifacts) so it's all good for me.
Unfortunately the codebase seems to be partially abandoned, so while it works with Backbone 1.0.0 (as far as unique models go), I may need to re-create/fork the pattern in future projects.
I think you should think twice about nesting your models and collections in this way, especially if it's primarily for the purpose of easing the bootstrapping of your app. Instead, try to use id's for inter-referencing between models as much as possible. This design problem you have is most likely only the first of many to come if you structure your model/collection tree in a certain way now, only to find it too inflexible later.
That being said, If all you need is for models referencing other models/collections to be able to refer to the same model/collection instance, then simply instantiating them during bootstrap and passing them in to their respective parent models would be sufficient. You could either load some bootstrap data in one request, or preferably inline that data in the HTML:
<script>
var bs_data = {
users : [
...
],
tags : [
...
],
matchboxes : [
...
]
};
</script>
And then instantiate the corresponding models or collections using the bootstap data.
var matchboxes = new Matchboxes();
matchboxes.set(bs_data.matchboxes);
var users = new Users({matchboxes:matchboxes});
users.set(bs_data.users);
The bootstrap data would come from the same backend so your models and collections would already be in sync without having to fetch anything.
As for design patterns; passing dependencies as constructor arguments is actually the dependency injection pattern, albeit more automated solutions to do so exist.
To make sure only one model is ever instantiated, and it is shared among the other elements that use it, being able to listen and update when any of the elements make a change to it, you can use a Singleton pattern. You can read more about it here
If you use Requirejs you can get same effect if you always return the model instantiated. For example:
// the shared model
define([
'jquery',
'underscore',
'backbone'
], function ($, _, Backbone) {
'use strict';
var Model = Backbone.Model.extend({
// ...
});
// return instantiated, so we'll get the same object back whenever we use this model (singleton)
return new Model();
});
// a view using the model
define([
'jquery',
'underscore',
'backbone',
'model'
], function ($, _, Backbone, modelInstance) {
'use strict';
var View = Backbone.View.extend({
initialize: function () {
// listen to what other elements do
this.listenTo(modelInstance, 'eventFromOtherElement', this.doSomething);
// when this element does something, other elements should be listening to that event
modelInstance.trigger('thisViewEvent');
},
doSomething: function () {
// ...
}
});
return View;
});
I'm currently writing a Backbone Marionette app which ultimately amounts to about 6 different "screens" or pages which will often times share content and I am unsure of how to best structure and access Regions.
I am using the app/module setup described here: StackOverflow question 11070408: How to define/use several routings using backbone and require.js. This will be an application which will have new functionality and content added to it over time and need to be scalable (and obviously as re-usable as possible)
The Single Page App I'm building has 4 primary sections on every screen: Header, Primary Content, Secondary Content, Footer.
The footer will be consistent across all pages, the header will be the same on 3 of the pages, and slightly modified (using about 80% of the same elements/content) on the remaining 3 pages. The "morecontent" region will be re-usable across various pages.
In my app.js file I'm defining my regions like so:
define(['views/LandingScreen', 'views/Header', 'router'], function(LandingScreen, Header, Router) {
"use strict";
var App = new Backbone.Marionette.Application();
App.addRegions({
header: '#mainHeader',
maincontent: '#mainContent',
morecontent: '#moreContent',
footer: '#mainFooter'
});
App.addInitializer(function (options) {
});
App.on("initialize:after", function () {
if (!Backbone.History.started) Backbone.history.start();
});
return App;
});
Now, referring back to the app setup in the aforementioned post, what would be the best way to handle the Regions. Would I independently re-declare each region in each sub-app? That seems to be the best way to keep modules as independent as possible. If I go that route, what would be the best way to open/close or hide/show those regions between the sub-apps?
Or, do I keep the Regions declared in app.js? If so, how would I then best alter and orchestrate events those regions from sub-apps? Having the Regions defined in the app.js file seems to be counter-intuitive to keeping what modules and the core app know about each other to a minimum. Plus, every example I see has the appRegions method in the main app file. What then is the best practice for accessing and changing those regions from the sub-app?
Thanks in advance!
I actually have a root app that takes care of starting up sub-applications, and it passes in the region in which they should display. I also use a custom component based off of Backbone.SubRoute that enables relative routing for sub-applications.
check out this gist: https://gist.github.com/4185418
You could easily adapt it to send a "config" object for addRegions that defines multiple regions, instead of the region value I'm sending to the sub-applications' start method
Keep in mind that whenever you call someRegion.show(view) in Marionette, it's going to first close whatever view is currently being shown in it. If you have two different regions, each defined in its own app, but both of which bind to the same DOM element, the only thing that matters is which region had show called most recently. That's messy, though, because you're not getting the advantages of closing the previous view - unbinding Event Binders, for example.
That's why, if I have a sub-app that "inherits" a region from some kind of root app, I usually just pass in the actual region instance from that root app to the sub-app, and save a reference to that region as a property of the sub-app. That way I can still call subApp.regionName.show(view) and it works perfectly - the only thing that might screw up is your event chain if you're trying to bubble events up from your region to your application (as the region will belong to the root app, rather than the sub-app). I get around this issue by almost always using a separate instance of Marionette.EventAggregator to manage events, rather than relying on the built-in capabilities of regions/views/controllers/etc.
That said, you can get the best of both worlds - you can pass the region instance into your sub-app, save a reference to it just so you can call "close", then use its regionInstance.el property to define your own region instance pointing to the same element.
for(var reg in regions) if regions.hasOwnProperty(reg) {
var regionManager = Marionette.Region.buildRegion(regions[reg].el,
Marionette.Region);
thisApp[reg] = regionManager;
}
It all depends on what your priorities are.
I personally prefer to use the modules in my Marionette application. I feel it removes the complexity that require.js adds to your application. In an app that I am currently working on, I've created one app.js file that defines my backbone application but I am using a controller module that loads my routes, fills my collections and populates my regions.
app.js ->
var app = new Backbone.Marionette.Application();
app.addRegions({
region1: "#region1",
region2: "#region2",
region3: "#region3",
region4: "#region4"
});
app.mainapp.js ->
app.module('MainApp', function(MainApp, App, Backbone, Marionette, $, _) {
// AppObjects is an object that holds a collection for each region,
// this makes it accessible to other parts of the application
// by calling app.MainApp.AppObjects.CollectionName....
MainApp.AppObjects = new App.AppObjects.Core();
MainApp.Controller = new Backbone.Marionette.Controller.extend({
start: function() {
// place some code here you want to run when the controller starts
} //, you can place other methods inside your controller
});
// This code is ran by Marionette when the modules are loaded
MainApp.addInitializer(function() {
var controller = new MainApp.Controller();
controller.start();
});
});
You would then place your routes inside another module that will be accessed in the controller.
Then in the web page, you would start everything by calling.
$(function () {
app.start();
});
Marionette will automatically run and load all of your modules.
I hope this gets you started in some direction. Sorry I couldn't copy and past the entire application code to give you better examples. Once this project has been completed, I am going to recreate a demo app that I can push to the web.
I am using Backbone/RequireJS to provide my application with modularization and structure. One thing I am coming up against, and would greatly appreciate some advice in this area.
When a user visits the page, the first thing that happens is some JSON that populates a couple of models. I would like these models to be available where-ever I am in the app, as they contain the data and support for the program. Is it permissible to use window.modelName, or do you recommend another/better way of accomplishing this?
Using your suggested solution defies the whole purpose of using AMD in the first place.
Define a module (let's call it globals) as such:
define(function (require) {
var globals = function () {
return {};
};
return globals();
});
Now, when you init you can add values to it:
globals = require('globals');
globals.mymodel = new MyModel();
mymodel.fetch();
Later, and from any other module, you can access your globals module:
globals = require('globals');
console.log(globals.mymodel.get('myattr');