I have an Angular factory, Session.
It exposes a method join that does some authentication stuff then off the back of that exposes an arbitrary resource through the same factory (code snippet below should illustrate).
Is there a best practice here in terms of exposing that property?
Solutions I see:
Exposing getter methods
Using a service rather than a factory and using this
Doing something like this from a factory:
// Public API
var exports = {
join: function(params, callback) {
authenticate(params, function(err, data) {
if (!err) {
exports.arbitraryResource = createArbitraryResource(data.resource);
}
callback(err, data.sessionKey);
});
}
};
return exports;
Really interested to hear how other developers are exposing mutable properties from factories. I'd like to avoid getter methods if only because they're clunky to write and it seems like an anti pattern here.
I would write a method getResource() which would make the authenticate call if resource was null else return the resource that had been setup by the previous call. This way the calling code does not care as to how the resource was fetched.
app.factory('myService', function() {
var resource = null;
return {
getResource: function(callback) {
if (resource) {
callback(null, resource);
} else {
authenticate(params, function(err, data) {
resource = data
callback(err, data);
});
}
}
}
})
Related
So, i have a small API interaction code that looks like this:
function load_posts() {
return $http
.get('/posts')
.then(on_success);
function on_success(response) {
return response.data;
}
}
function get_posts() {
if (blog.posts) {
return $q.when(blog.posts);
}
return load_posts().then(function (posts) {
blog.posts = posts;
return blog.posts;
});
}
I do this to avoid hitting the API for the same results all the time. I have several separate directives and components that might need to call this API endpoint, but they don't need a fresh result everytime. But this results in an ugly race condition: if two or more components call the get_posts method before the load_posts response arrives, then they all issue API requests. There are no side-effects, because this is just a cache attempt, but it defeats the whole purpose.
Any ideas on how to proceed with this one?
The $http service can cache requests. See here or the docs for a deeper explanation of how the caching works.
The default $http cache can be particularly useful when our data
doesn’t change very often. We can set it like so:
$http({
method: 'GET',
url: '/api/users.json',
cache: true
});
// Or, using the .get helper
$http.get('/api/users.json', {
cache: true
});
Now, every request that is made through $http to the URL
/api/users.json will be stored in the default $http cache. The key for
this request in the $http cache is the full-path URL.
This isn't really a race-condition problem, it's just a matter of memoizing the function. You can use something like memoize() from Underscore.js or just implement it yourself:
var load_posts = () => {
const p = $http
.get('/posts')
.then(response => response.data);
load_posts = () => p;
return p;
};
1) Extract data retrieval into separate "blogService" service;
2) Cache promise in your service that does the request;
3) Return the same promise for all clients, you can manipulate results if you dont want to expose whole response object;
var promise = null;
function loadBlogs() {
promise = promise || $http.get("/posts").then(function(response){return reponse.data;});
return promise;
}
4) Then just call service method and wait for promise to resolve wherever you need (controller, directive, etc):
function getPosts() {
blogService.loadBlogs().then(function (posts) {
vm.posts = posts;
});
I have some problem here, I'm using a mix of Angular2 and Nativescript's native JS to use HTTP because I was having problems with Angular2 HTTP before this implementation, but I'm having frustration on how to put the JSON response from the http request to the monthList variable so it can be read all over the place. Please help, Thank you.
Here's my code:
public monthList = [];
constructor(location: Location, private page: Page, private router: Router, private testing: any) {
this.router = router;
this.testing = testing;
let http = require("http");
http.getJSON("link")
.then(function (r) {
// Argument (r) is JSON!
}, function (e) {
// Argument (e) is Error!
console.log(e);
});
}
Inside your .then you set your return object to your monthList
this.monthList = r;
Try doing a console.dump(r) and see what is being returned. You may have to do something like r.xxx or however it is structured.
Then you can probably do something like
this.monthList.month
and access the data of the month and so forth. Or you maybe have to do something like
this.monthList[0].month.
For example in my project I have a JSON object coming back that is nested and I need to set it as something like r.value.data.
Is there a specific issue you're running into? I would think its as simple as:
class SomeAngularClass {
constructor() {
http.getJSON("link")
.then(function(r) {
this.monthList.push(r);
}, function (e) {
console.log(e);
});
}
}
And then you could access it via:
SomeAngularClass().monthList;
The question:
As I understand in sails.js during initialization process Services are initialized before Models.
Is there any possibility to change this behavior? To make Models load before Services.
If it's not, then how can I load particular settings from the database to use them to build instance of my class described in some Service during this Service initialization?
A little bit code for solidity:
api/models/Model.js
console.log("Model Identified");
module.exports = {
attributes: {
name: { type: 'string', required: true, size: 15 },
//Some extra secret fields
}
};
...
api/services/MyCoolService.js
console.log('service inits');
function MyCoolService(options){
//some extraordinary constructor logic may be ommited
}
MyCoolService.prototype.setOptions = function(options){
//Set values for MyCoolService fields.
}
//Some other methods
var myCoolServiceWithSettingsFromDb = new MyCoolService();
//That's the place
model.findOne(sails.config.myApplication.settingsId).exec(function(err,result){
if(!err)
myCoolServiceWithSettingsFromDb.setOptions(result);
});
module.exports = myCoolServiceWithSettingsFromDb;
It's because you instantiate object in service with constructor that needs sails that not exist. Try use this at MyCoolService;
module.exports = {
someOption: null,
method: function () {
var that = this;
sails.models.model.findOne(sails.config.myApplication.settingsId)
.exec(function (err, result) {
if (!err)
that.someOption = result;
});
}
};
that method can be called by sails.services.mycoolservice.method() or simply MyCoolService.method() to give your service some option from DB.
If you want to initiate them at Sails start, call that method at config/bootstrap.js
Thanks to Andi Nugroho Dirgantara,
I ended up with this solution (I still don't like it much, but it works):
api/services/MyCoolService.js
console.log('service inits');
function MyCoolService(options){
//some extraordinary constructor logic may be ommited
}
//All the same as in question
//The instance
var instance;
module.exports = module.exports = {
init: function(options) {
instance = new MyCoolService(options);
},
get: function() {
return instance;
},
constructor: MyCoolService
};
config/bootstrap.js
...
Model.findOrCreate({ id: 1 }, sails.config.someDefaultSettings).exec(function(err, result) {
if (err)
return sails.log.error(err);
result = result || sails.config.someDefaultSettings;
MyCoolService.init(result);
return sails.log.verbose("MyCoolService Created: ", TbcPaymentProcessorService.get());
});
...
tests/unit/service/MyCoolService.test.js
...
describe('MyCoolService', function() {
it('check MyCoolService', function(done) {
assert.notDeepEqual(MyCoolService.get(), sails.config.someDefaultSettings);
done();
});
});
...
It works: the service is instantiated once while bootstraping and it's instance is avaliable everywhere.
But to me this solution still weird... I still don't understand how to globally instantiate instance of my service (for use in a lot of controllers) and make it the best way.
I'm running into an issue with an Ember CLI project where I can't get an injected ember service from a controller action function.
The really strange thing is that this totally works on my models and custom adapters:
the controller:
export default Ember.Controller.extend({
node: Ember.inject.service(),
azureStorage: Ember.computed.alias('node.azureStorage'),
actions: {
myAction: function () {
// this returns null
var x = this.get('azureStorage');
}
}
});
// The service code (azureStorage and fs are NOT null)
if (window.requireNode) {
azureStorage = window.requireNode('azure-storage');
fs = window.requireNode('fs');
}
export default Ember.Service.extend({
azureStorage: azureStorage,
fs: fs,
getActiveAccount: function (store) {
return new Ember.RSVP.Promise(function (resolve, reject) {
var accounts = store.all('account'),
length = accounts.get('length'),
i = 0;
accounts.forEach(function (account) {
if (account.get('active') === true) {
return Ember.run(null, resolve, account);
}
i += 1;
if (i >= length) {
return Ember.run(null, reject, 'could not find any active accounts');
}
});
});
}
});
// the controller test code
var controller = this.subject();
controller.send('myAction');
I would have expected this to return the service and the azureStorage object. On my models & adapters the same pattern works just fine:
export default DS.Adapter.extend({
serializer: serializer.create(),
node: Ember.inject.service(),
azureStorage: Ember.computed.alias('node.azureStorage'),
findQuery: function () {
// this returns the value correctly
var x = this.get('azureStorage');
}
});
Any reason this would work on models & adapters but NOT on a controller?
I'm not familiar with the Ember.inject.service() pattern, but is there a reason you're not using the pattern outlined in http://guides.emberjs.com/v1.10.0/understanding-ember/dependency-injection-and-service-lookup/ ?
Also, why are you injecting node and azureStorage into the controller if you've already abstracted them into an adapter? You should just be using this.store.find('whatever', 123) from the controller to get your data. If your azureStore is different than your normal Ember Data store, then you should create a new store and register it with the application's container. If you inject it in your controller, you can access it with this.azureStore, or alternatively this.container.lookup('store:azure').
Also, not a good practice to start injecting stuff into your models. I would really take a look at the Ember-friendly ways of doing service/dependency injection, because this doesn't look very elegant and you're duplicating a lot of code to get access to something you already have.
How come in sails you cannot access other controller methods from within another one?
like this.
module.exports =
findStore: ->
# do somthing
index: ->
#findStore(); # Error: undefined
Compiled
module.exports = {
findStore: function() {},
index: function() {
return this.findStore(); // Error: undefined
}
};
If you can't do this, then why not? how else should I be doing this...
You can use sails.controllers.yourControllerName.findStore()
the sails global object has references to almost everything.
One of the best ways to organize your code in Sails, at least for me and my team, has been to have all the real business logic in Services (/api/services). Those objects can be accessed globally from any controller.
Also, a good practice is working with promises in services (as Sails use them on the model methods)
Just create a Store service (StoreService.js), with your code:
module.exports = {
findStore: function(storeId) {
// here you call your models, add object security validation, etc...
return Store.findOne(storeId);
}
};
Your Controllers should handle all that is related to requests, calling services, and returning apropriate responses.
For example, in you example, the controller could have this:
module.exports = {
index: function(req, res) {
if(req.param('id')) {
StoreService.findStore(req.param('id'))
.then(res.ok)
.catch(res.serverError);
} else {
res.badRequest('Missing Store id');
}
},
findStore: function(req, res) {
if(req.param('id')) {
StoreService.findStore(req.param('id'))
.then(res.ok)
.catch(res.serverError);
} else {
res.badRequest('Missing Store id');
}
},
};
That way, you have really simple controllers, and all business logic is managed by services.
Having the same problem for last few hours. I used the api/services folder.
It may not be exactly what you need but it is an option.
A good explanation is here. What services would one add to the api/services folder in sails.js
It's slightly annoying when you're just trying to build something quickly, but in the long run it forces good code organization practice (by making it harder to shove all business logic into a controller).
I would like to suggest a solution that works but not the best possible way to do it. We can use bind function to bind the context with the calling source as shown below :
generateUrl is present in the Controller A
function generateUrl(){
return 'www.google.com';
}
get URL is another method in Controller A
getURL(){
A.generateURL.bind(A.generateURL) //func call with optional arg
}
I hope this helps!
A more elegant way to solve this problem is using the keyword this before the function name.
Example:
one: function() {
console.log('First Function');
},
two: function() {
// call the function one in the same controller
this.one();
}
You can do something like this:
//ArticleController
module.exports = {
findStore: async () => {
return await findStoreFunc(req.param('id'));
},
index: async () => {
...
return await findStoreFunc(id);
}
};
const findStoreFunc = async (id) => {...}
And to use the function from another controller:
const ArticleController = require('./ArticleController');
//CustomerController
module.exports = {
index: async () => {
...
let article = await ArticleController.findStore(id);
...
}
};