I have common module on my app that retrieves data from remote server and have get methods to export the retrieved data to other modules.
Lets assume that this module's name is MyData:
define([], function(){
return function(opts) {
var data = null;
// go get data with ajax
var getData = function(){
$.ajax("getData").done(function(response){
data = response;
});
};
getData();
// return public api
if (arguments.length !== 0) {
var method = arguments[0];
switch (method) {
case "get": return data;
}
};
};
});
The problem is when I define other modules that use MyData, everytime it would create a new instance of MyData, causing it to retrieve the data from the server each time.
I could use web storage (or other global variable) to store the returned data, but it seems like a bad solution.
Is there any way to force RequireJS to create only once instance?
P.S:
In my project I user KnockoutJS, and the data variable is an ko.observable, so maybe knockout has a solution for my situation?
RequireJS already loads each module once. You just need to restructure your code a bit:
// myModule.js
define(function() {
// The dataFetch Promise will be initialized when this module
// is loaded for the first time.
var dataFetch = $.ajax("getData");
return {
getData: function () {
// Return the Promise interface and let callers use the
// .done, .fail etc. methods on it
return dataFetch;
}
};
});
Usage:
define([ "myModule" ], function (myModule) {
myModule.getData().done(function (data) {
// use the data
});
});
By using the Promise interface returned by $.ajax, you can ensure asynch access to data that's fetched only once.
BTW, perhaps the pattern that you're trying to implement is a Model.
Related
I have an angular factory which includes methods to manage a list of objects. One of those methods helps to make an ajax call and load more items into the list. When that method is called, I have a need to show the "in progress" state of the ajax call (i.e. loading of items). Now, I can just use a simple variable like service.isLoading = ... and set it to true or false depending on the state. However, the problem is that this variable becomes visible to the outside and any controllers that use this factory.
angular.module('myApp')
.factory('appFactory', function QuestionsServ(...) {
var service = {
list: []
};
// loading state to help show if system is busy loading ajax data
service.isBusy = false;
service.loadItems = function () {
service.isBusy = true;
// make ajax call here
var request = ...
return request.then( function(res) {
// after successful loading show state is not busy
service.isBusy = false;
...
}
...
But I would like to be able to keep this variable visible within the factory only. So I can change isBusy to something similar to the following.
service.isBusy = function () {
return false;
}
But now it is apparently busy from the factory as well. I am no longer able to access isBusy from within loadItems. What is the proper way to go around this issue?
You can make isBusy a var local to the module and manipulate it directly from within your code. Then, to expose it to the outside world, make a function that returns it-- that way other modules or constructs have access to the boolean's value for display purposes but can't manipulate its value inside your service:
angular.module('myApp')
.factory('appFactory', function QuestionsServ(...) {
var service = {
list: []
};
// loading state to help show if system is busy loading ajax data
var isBusy = false;
service.isLoading = function () {
return isBusy;
};
service.loadItems = function () {
isBusy = true;
// make ajax call here
var request = ...
return request.then( function(res) {
// after successful loading show state is not busy
isBusy = false;
...
}
How can I define a Meteor method which is also callable in a template helper?
I have these two files:
file: lib/test.js
Meteor.methods({
viewTest : function (str) {
return str;
}
});
file: client/myView.js
Template.helloWorld.helpers({
txt : function () {
var str = Meteor.call('viewTest', 'Hello World.');
return str;
}
});
When I give "str" a normal string everything works fine. But in this case my template does not get any value. I defined - for the test - in the same file where the method is a normal function and tried to call the function. The error I got was that the function does not exist. So I think that Meteor tries to render the template before it knows anything about the methods I defined for it. But I think that this is a bit unusual - isn't it?
There is now a new way to do this (Meteor 0.9.3.1) which doesn't pollute the Session namespace
Template.helloWorld.helpers({
txt: function () {
return Template.instance().myAsyncValue.get();
}
});
Template.helloWorld.created = function (){
var self = this;
self.myAsyncValue = new ReactiveVar("Waiting for response from server...");
Meteor.call('getAsyncValue', function (err, asyncValue) {
if (err)
console.log(err);
else
self.myAsyncValue.set(asyncValue);
});
}
In the 'created' callback, you create a new instance of a ReactiveVariable (see docs) and attach it to the template instance.
You then call your method and when the callback fires, you attach the returned value to the reactive variable.
You can then set up your helper to return the value of the reactive variable (which is attached to the template instance now), and it will rerun when the method returns.
But note you'll have to add the reactive-var package for it to work
$ meteor add reactive-var
Sashko added a neat little package called meteor-reactive-method to solve this problem.
$ meteor add simple:reactive-method
Template.helloWorld.helpers({
txt: function() {
return ReactiveMethod.call('viewTest', 'Hello World.');
}
});
As I point out in common mistakes, helpers should be side-effect free, so I'd use this technique with caution. However, it's a really handy shortcut for cases where:
The helper should fire only once (it doesn't depend on reactive state).
The invoked method doesn't mutate the database.
You need to interface your return value with a Session variable as the request is asynchronous:
Template.helloWorld.helpers({
txt : function () {
return Session.get("txt") || "Loading";
}
});
Template.helloWorld.created = function() {
Meteor.call('viewTest', 'Hello World.', function(err, result) {
Session.set("txt", result);
});
}
So .rendered should be called once when your template loads (at least it should with the newer version of Meteor.)
The value would be called and displayed. Otherwise it would say "Loading".
Methods on the client side are asynchronous, and their return value is always undefined. To get the actual value returned by the method, you need to provide a callback:
Meteor.call('method', 'argument', function(error, result) {
....
});
Now, there's no easy way to use the result in your helper. However, you can store it in your template as a data object and then return it in the helper:
Template.template.created = function() {
var self = this;
self.data.elephantDep = new Deps.Dependency();
self.data.elephant = '';
Meteor.call('getElephant', 'greenOne', function(error, result) {
self.data.elephant = result;
self.data.elephantDep.changed();
});
};
Template.template.showElephant = function() {
this.elephantDep.depend();
return this.elephant;
};
This is expected behavior. You are not using methods as they are intended.
Your code defines a server method viewTest and a corresponding method stub on the client with the same name.
Meteor.call('viewTest', 'Hello World.'); remotely calls viewTest on the server and in parallel runs the stub on the client.
Regarding the return value of the stub please see the documentation here, in particular:
On the client, the return value of a stub is ignored. Stubs are run
for their side-effects: they are intended to simulate the result of
what the server's method will do, but without waiting for the round
trip delay.
Regarding the return value of the server method please see the documentation here, in particular:
On the client, if you do not pass a callback and you are not inside a
stub, call will return undefined, and you will have no way to get the
return value of the method. That is because the client doesn't have
fibers, so there is not actually any way it can block on the remote
execution of a method.
There is a fine little package for this by #msavin:
https://atmospherejs.com/msavin/fetcher
This is a simple problem but seems tricky due to asynchronous nature of promises.
With in my data service, I want to ensure that data is retrieved back from the server before moving on to next step as other functions/clients depend on that data.
I don't want clients to register callbacks or use .then() and certainly not use $timeout when requesting the data.
How can I make sure that the controller and service that depend on my Data service get the data right away when requested? I've explained my problem using the code below. I would very much like to continue on my current approach if possible.
AngularJS Version: 1.4+
My Current Approach
//Data Service
App.factory('HealthyFoodService', function($resource, $q) {
var fruitsPromise = $resource('api/food/fruits', {}).query().$promise;
var veggiesPromise = $resource('api/food/veggies',{}).query().$promise;
var fruitsData, veggiesData;
//Ensure $q populates the data before moving on to next statement.
$q.all([fruitsPromise, veggiesPromise]).then(function(data) {
fruitsData = data[0];
veggiesData = data[1];
}
function getCitrusFruits() {
var citrusFruits;
var allFrutis = fruitsData;
//code breaks here because fruitsData is still undefined when called from a controller or another service.
//some logic for this function
return citrusFruits;
}
function getLeafyVeggies() {
var leafyVeggies;
var allVeggies = veggiesData;
//code breaks here because veggieData is still undefined when called from a controller or another service.
//some logic for this function
return leafyVeggies;
}
function getLeafyVeggyByName(name) {
//this function is called from other services and controllers.
var leafyVeggies = getLeafyVeggies();
return leafyVeggies[name];
}
return {
getCitrusFruits: getCitrusFrutis,
getLeafyVeggies: getLeafyVeggies,
getLeafyVeggyByName: getLeafyVeggyByName
});
Below are the two clients. One is a controller and another is a service. They both need the data right away as following statements depend on the returned data.
//Controller
App.controller('LeafyVeggieController', function(HealthyFoodService) {
//Ideally I just'like to do something like below instead of calling `.then()` and registering callbacks.
var leafyVeggies = FoodService.getLeafyVeggies();
//leafyVeggies is undefined because data is not available yet;
});
//Another service depending on HealthyFoodService- similar scenario
App.factory('LeafyVeggieReportService', function(HealthyFoodService) {
function generateLeafyVeggieReport() {
var name = 'spinach';
var veggieInfo = HealthyFoodService.getLeafyVeggieByName(spinach);
//veggieInfo is undefined
//logic that need data.
});
My Previous Approach
Below is how I had it partially working before but I wasn't happy about using .then() everytime I needed the data.(Even with in the same service)
App.factory('HealthyFoodService', function($resource, $q) {
//resource variables;
function getLeafyVeggies() {
return $q.all([veggiesPromise]).then(function(data) {
//logic
return leafyVeggies;
});
}
function getLeafyVeggieByName() {
var leafyVeggies = getLeafyVeggies().then(function(data) {
return data;
}
//some logic
//still causes issues when called from another service because above call doesn't get the data right away.
}
return {
getLeafyVeggies: getLeafyVeggies,
getLeafyVeggieByName: getLeafyVeggieByName
}
//controller
App.controller('LeafyVeggieController', function(HealthyFoodService) {
var leafyVeggies = HealthyFoodService.getLeafyVeggies().then(function(data) {
return data;
});
//controller related logic
});
Update
I'm using ui-router as well, so I'm aware that I can use resolve:{} in $stateProvider to inject the data directly into the controller. The puzzle is how to get the data when I make a request from another service or from another function with in the same service without having to use .then().
Solution
Using $q.all([]) in my client services that depend on my Data service has done the trick for me. I have used $q.all([]) whenever I'm in a situation where I need all the data to be present before start processing the logic.
I still have to use .then() on my clients, but by using $q.all([]), I can still slightly simulate a synchronous flow without breaking any asynchronous principles.
I don't think this is possible. There is inherent latency in network operations that you need to wait for. Not doing so results in the application continuing before the data is available.
That being said, most of the native model binding operations will implicitly wait on promises, so there would be no need to .then if no further manipulation of the data is necessary before passing to the view. You can also use the transformResponse method of ngResource to help with this.
Another option might be to shift the complexity to the resolve methods in the route config. In that case you would handle the .then in the resolve and pass the resolved data to your controller. This will keep your controllers cleaner, but still requires you resolve the promises in the route config.
Try having the service hold your data and have the controller reference that data, instead of trying to pass it to your controller scope. Resolve the promise inside the service like so:
App.factory("HealthyFoodService", function($resource,$q) {
var service = {};
service.data = {
fruitsData: [],
veggiesData: []
}
$resource('api/food/fruits', {}).query().$promise.then(function(data) {
$service.data.fruitsData = data[0];
$service.data.veggiesData = data[1];
})
service.getCitrusFruits = function() {
var citrusFruits;
// Perform some logic on service.data.fruitsData
return citrusFruits;
}
return service;
})
In you controller, you can talk to the service like so:
App.controller("FruitController", function($scope, HealthyFoodService) {
// Now you can access the service data directly via $scope.hfs.fruitsData
$scope.hfs = HealthyFoodService.data;
// Or we can create a local copy of the data using the getCitrusFruits function
$scope.citrusFruits = HealthyFoodService.getCitrusFruits();
})
normally we might create a simple singleton object like so with Node.js:
var foo = {};
module.exports = foo;
or
function Foo(){}
module.exports = new Foo();
however
what is the best way to make a clean singleton module that needs an external variable for initialization? I end up making something like this:
var value = null;
function init(val){
if(value === null){
value = val;
}
return value;
}
module.exports = init;
this way someone using the module can pass in an initializing value for a certain variable. Another way to do it would be like so:
function Baz(value){
this.value = value;
}
var instance = null;
module.exports = function init(value){
if(instance === null){
instance = new Baz(value);
}
return instance;
}
there's two problems I encounter with this:
(1) this is minor, but the semantics is wrong. We can rename init to getInstance, but we can't make the same function literal mean "initialize and get" since they are different meanings. So we have to have a function that does two different things. Create an instance and retrieve and instance. I don't like this especially since in some cases we need to make sure the argument to initialize the instance is not null. With multiple developers using a module it's not clear if a module has been initialized yet, and if they pass in undefined into the module that hasn't been initialized, that could become a problem or just confusing at the least.
(2) this is more important - in some cases initializing Baz is asynchronous. For example, making a Redis connection or reading from a file to initialize a singleton, or making a socket.io connection. This is what really trips me up.
e.g. here is a module that I have that I consider really ugly that stores a socket.io connection:
var io = null;
var init = function ($io) {
if (io === null) {
io = $io;
io.on('connection', function (socket) {
socket.on('disconnect', function () {
});
});
}
return io;
};
module.exports = {
getSocketIOConn: init
};
the above module is initialized like so:
var server = http.createServer(app);
var io = socketio.listen(server);
require('../controllers/socketio.js').getSocketIOConn(io);
So I am looking for a design pattern that allows us to create a singleton module where the initialization process is asynchronous. Ideally we won't have the same function both initializing the instance as well as retrieving it. Does such a thing exist?
I don't think there is necessarily a way to create a pattern that solves this problem but perhaps I am making the mistake of structuring my code in a way that is creating a problem that doesn't need to exist- the problem of initializing a module with a value only once, but using one function to both init the instance and retrieve the instance.
It sounds like you're trying to create a module that gets initialized in one place and then uses some shared resource from that initialization for other users of that module. That is a semi-common need in the real world.
First off, it's ideal if a module can load or create the things that it depends on because that makes it more modular and useful on its own and puts less of a burden on someone using it. So, in your case, if your module could just create/load the thing that it needs when the module is first created and just store that resource in it's own module variable, then that would be the ideal case. But, that is not always possible because the shared resource may be someone else's responsibility to set up and initialize and this module just needs to be made aware of that.
So, the common way to do that is to just use a constructor function for the module. In Javascript, you can allow the constructor to take an optional argument that provides the initialization info. The code responsible for setting up the module would call the constructor with the desired setup parameter. Other users of the module that weren't responsible for setting up the module could just either not call the constructor or if they want a return value or there are other constructor parameters that they should pass, they could pass null for that setup parameter.
For example, you could do this:
var io;
module.exports = function(setup_io) {
if (setup_io) {
io = setup_io;
}
return module.exports;
};
module.exports.method1 = function() {
if (!io) {
throw new Error("Can't use method1 until io is properly initalized");
}
// code here for method1
};
// other methods here
Then, users of the module could either do this:
// load myModule and initialize it with a shared variable
var myModule = require('myModule')(io);
or this:
// load myModule without initializing it
// (assume some other module will initialize it properly)
var myModule = require('myModule');
Note: For developer sanity, it would be useful to have individual methods that require appropriate setup (before they can be used properly) to check to see if the module has been setup when any method is called that needs that setup in order to properly inform a developer that they have called a method before setting up the module properly. Otherwise, errors can happen much further downstream and likely won't have useful error messages.
If you now want the initialization process to be async, that can be done too, but it certainly complicates other uses of the module because they won't necessarily know when/if the module has been initialized.
var moduleData;
var readyList = new EventEmitter();
module.exports = function(arg, callback) {
// do some async operation here involving arg
// when that operation completes, you stored the result
// in local module data and call the callback
readyList.on("ready", callback);
someAsyncOperation(arg, function() {
// set moduleData here
// notify everyone else that the module is now ready
readyList.emit("ready");
// remove all listeners since this is a one-shot event
readyList.removeAllListeners("ready");
});
return module.exports;
};
If you have other users of this module that wish to be notified when it has finished initializing, you can allow them to register a callback themselves to be notified when the module is ready.
// pass a callback to this method that will be called
// async when the module is ready
module.exports.ready = function(fn) {
// if module already ready, then schedule the callback immediately
if (moduleData) {
setImmediate(fn);
} else {
readyList.on("ready", fn);
}
};
If, for reasons I don't quite understand, you want to use the same constructor for both initialization and ready detection, that can be done, though I don't think it's near as clear as just using a separate method for ready detection:
var moduleData;
var readyList = new EventEmitter();
module.exports = function(arg, callback) {
// if both arguments passed, assume this is a request for module
// initialization
if (arguments.length === 2) {
// do some async operation here involving arg
// when that operation completes, you stored the result
// in local module data and call the callback
readyList.on("ready", callback);
someAsyncOperation(arg, function() {
// set moduleData here
// notify everyone else that the module is now ready
readyList.emit("ready");
// remove all listeners since this is a one-shot event
readyList.removeAllListeners("ready");
});
} else {
// constructor called just for a ready request
// arg is the callback
if (moduleData) {
// if module already ready, then schedule the callback immediately
setImmediate(arg);
} else {
// otherwise, save the callback
readyList.on("ready", arg);
}
}
return module.exports;
};
Usage for async initializing the module:
// async initialization form
var myModule = require("myModule")(someArg, function() {
// can use myModule here
});
Usage for loading the module and getting notified when someone else has initialized it:
var myModule = require("myModule")(function() {
// can use myModule here
});
I have 3 .js files. The main Home.js and two other .js files, for instance Page1.js, Page2.js
Home.js:
var Home= {
Sample: function (pageId,data) {
pageId.MergePageData(data);
}
}
Page1.js:
var Page1 = {
MergePageData: function (data) {
// do something
}
}
Page2.js:
var Page2 = {
MergePageData: function (data) {
// do something
}
}
I tried calling like this by passing it as a string:
Home.Sample('Page1', 'data');
Home.Sample('Page2', 'data');
But I guess as it is being passed as a string am getting an error
"Object doesn't support property or method 'MergePageData' "
I need to differentiate the call between the two functions in two different js files. How to achieve that?
The pageId in your function is just a variable, and when you call it you specify the type of this variable, and as you pass "Page1", which is a String it is just a string without having anything to do with your real Page1 object. but there are some options that can help you out.
The Other point that you have to consider is, it doesn't matter you have 2 or 3 different js files. the important point is if all your javascript codes are injected to single html page, then you can have all your code in all the JavaScript files in single window context. So if you define something in a global scope which is window, you can have access to it all over your JavaScript code.
It seems your Page1 and Page2 are global objects, so you can do it like:
var Home= {
Sample: function (pageId,data) {
window[pageId].MergePageData(data);
}
}
but I guess data vriable is not a global, you have 2 options, first make it global or just store it in a storage like:
localStorage.setItem("data", JSON.stringify(data));
then change your Sample function like this:
var Home= {
Sample: function (pageId,data) {
window[pageId].MergePageData(JSON.parse(localStorage.getItem("data")));
}
}
Although you have to be very careful about the size of your data object, if it is a big object with a lot of properties and inner objects, you should reconsider and change your solution.
Basically you want to create your object Home inside an IIFE. That way you can pass in any library or object namespace throughout several files.
For your Home object, declare and run an anonymous function, assign your object directly to the the window object. This will be your "namespace" and accessible throughout your files by window.Home. Use this file to initialize your Page objects. As for now the DOM ready event is used from jQuery.
// #param ($): jquery library 1.10.2
(function ($) {
// 1. ECMA-262/5
'use strict';
// 2. PRIVATE CONFIGURATION
var cfg = {
// an object literal to store config
page2: {
page: 'page2',
data: 'data'
}
};
// 3. GLOBAL OBJECT NAMESPACE
window.Home = {
init: function(){
// initialize your other files
this.cache = {
page1: new Home.Sample();
page2: new Home.Sample(cfg.page2);
}
}
};
// 4. ONCE THE DOM IS READY
$(function () {
Home.init();
});
}(window.jQuery));
Then for your other files, a slightly different approach can be used.
// #param ($): jquery library 1.10.2
// #param (home): Home namespace
window.Home = (function ($, home) {
// 1. ECMA-262/5
'use strict';
// 2. CONFIGURATION
var cfg = {
page: 'page1'
data: 'data'
};
// 3. CONSTRUCTOR FUNCTION
home.Sample = function (options) {
this.settings = $.extend({}, cfg, options);
this.init();
};
// 4. PROTOTYPE OBJECT
home.Sample.prototype = {
init: function(){
this.cacheItems();
this.mergePageData(settings.data);
},
cacheItems: function(){
this.page = settings.page;
},
mergePageData: function (data) {
// do something with this.page and data
// consider caching data instead of passing it along
}
};
// 5. GLOBALIZE OBJECT
return home;
}(window.jQuery, window.Home || {}));
This approach is modular and better to maintain. Since the whole configuration is extracted from the logic you will find it easier to create different instances of an object. Simply by passing in options into your Sample object you can change the whole data/structure but keeping behavior as intended. You can fill in options from your server language and use the sizzle selector engine inside jQuery todo powerful DOM traversing and so on ...