I have something like:
App.IndexController = Ember.ObjectController.extend({
results : function(){
return new Ember.RSVP.Promise(function(resolve, reject){
Ember.$.getJSON('/search').then(function(res){
console.log('response data is: ', res);
resolve(res)
})
})
}.property(),
...
})
// data
$.mockjax({
url: '/search',
responseText : {
type: 'different people',
res: [
{name: 'charlie', age: '55'},
{name: 'bobby', age: '19'},
{name: 'raymond', age: '39'}
]
}
})
my jsbin
How do I actually use the returned data? Currently, results returns a promise ( this.get('results') ) so I can't use it in my hbs template. Do I need to convert it to an object and then return that object?
Answering the Question
Internally Ember uses a PromiseProxyMixin to do the magic rendering of promises that we know and love. Here is your updated JSBin working with a PromiseProxy:
http://emberjs.jsbin.com/danazu/edit?html,js,output
Your results property becomes this:
results: Ember.computed.promise(function(resolve, reject) {
Ember.$.getJSON('/search').then(function(res) {
console.log('response data is: ', res);
return resolve(res);
});
})
However I don't recommend this. Take a look at this discourse thread to get some information on why you might not want to do this. In short, it will be clunky to handle all the different states of promises.
Looking For a Better Way
The Router is the perfect place to deal with promises with AJAX requests. There must be some reason that you're not just loading this data in your Router in the model or afterModel hooks. Could you create another nested resource to represent these search results and then just link-to that resource?
In your Router:
Router.map(function() {
this.resource('search', { path: '/search/:term' });
});
App.PeopleRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON('/search?term=' + params.term);
}
});
If that won't work at the very least you could:
Send an action when you want the search results
Handle the AJAX request in the controller
Set the results on the controller when they are resolved.
I use ic ajax for easy use with promises. I don't currently use Ember data so I wrote myself a nice rest client on top of ic ajax to easily get/post/put to urls:
rsvpAjax: function(url,method, data){
var headers = {};
// build request
var opts = {};
var defaultOpts = {
type: method,
headers: headers,
data: JSON.stringify(data),
dataType: 'json',
xhrFields: {
withCredentials: true
}
};
return ajax.request(url, $.extend({}, defaultOpts, opts));
}
And then I have get method:
getAsync: function(url){
return this.rsvpAjax(url, "GET");
}
Then using it elsewhere in a facade/helper class:
import objFactory from 'appname/models/obj';
...
...
foo: function(){
//get your rest client somehow
var apiFacade = this.getRestClient();
return restClient.getAsync('url_string').then(function(response){
//targets some part of the response - here its an array of
var data = response.data;
return data.map(function(obj){
return objFactory.create(obj);
});
});
}
foo returns an array of Ember objects. the objFactory I import is just a Ember.Object class. The obj passed into the create generally is a one to one match with the properties in said Ember.Object. With an initializer, I inject this object into every route:
import someFacade from "app/facades/facade";
export default {
name: 'someFacade',
initialize: function(container, app) {
//a singleton by default
container.register('facades:someFacade',someFacade);
app.inject('route', 'someFacade', 'facades:someFacade');
}
};
Any of my routes can use this facade without importing it thanks to DI.
//Ember blocks
model: function(){
return this.someFacade.foo();
}
or:
setupController(controller, model){
//set controller prop on return of resolved val
this.someFacade.foo().then(function(foosReslovedVal){
controller.set('someProp', foosResolvedVal);
});
}
Related
I am trying to pass an integer and string parameters from an AngularJS client to an ASP.NET Web API server, without using a POCO class to wrap the parameters in. I've tried the following:
The parameters I want to pass are "id", which is an integer, and "name", a string.
My AngularJS service looks like this:
function myResource($resource, myServerUrl) {
return {
testFunction: $resource(myServerUrl + "/api/testing/:id/:name/MethodB", null,
{
'get': { method: 'GET' }
})
};
}
The code in the AngularJS controller is the following:
var vm = this;
vm.testFunction = function (Id, Name) {
myResource.testFunction.get({ id: Id, name: Name },
function(response) {
// Do something
},
// Handles errors
function(error) {
// Process error
});
}
And finally, the Web API controller:
[RoutePrefix("api/testing")]
public class MyController : ApiController
{
// This is NOT the method I am using for hit example.
// It's a different one with a similar signature
// so I am showing it here just in case it has
// impact on what I am trying to do.
[Authorize]
[Route("{name}/{id:int}/MethodA")]
public IHttpActionResult GetData(string name, int id)
{
// Do something
}
// This is the method I am using for this example
[Authorize]
[Route("{id:int}/{name}/MethodB")]
public IHttpActionResult MyTestMethod(int id, string name)
{
// Do something
}
}
When I try to run the above code, I get the following error:
{"message":"The requested resource does not support http method 'GET'."}
If I change the code to use POST instead of GET, i.e.:
testFunction: $resource(myServerUrl + "/api/testing/:id/:name/MethodB", null,
{
'get': { method: 'POST' }
})
and
// This is the method I am using for this example
[Authorize]
[HttpPost]
[Route("{id}/{name}/MethodB")]
public IHttpActionResult MyTestMethod(int id, string name)
{
// Do something
}
I get this:
{"message":"The requested resource does not support http method 'POST'."}
And if I modify my AngularJS service to not use colon in front of the parameters, i.e. this:
testFunction: $resource(myServerUrl + "/api/testing/id/name/MethodB", null,
{
'get': { method: 'POST' }
})
I get this error instead:
{"message":"The request is invalid.","messageDetail":"The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Http.IHttpActionResult MyTestMethod(Int32, System.String)' in 'MyController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."}
What is the problem here? How can I pass an integer and a string from AngularJS to a Web API controller, without using a POCO class? I am aware I can wrap both parameters in a value object and pass it that way to the Web API, but that's unnecessary complexity for me at this point. There should be a simple way to do this.
I've done this with two or three integer parameters without a problem, but for some reason an integer and a string is not working. Any advice on this would be appreciated. It's one of those things that should be simple to do, but apparently it's not.
Ok here's an example where I passed an object from controller to service, and the service uses $resource
var navApp = angular.module('navApp', [
'ngResource',
'ui.bootstrap',
'ngAnimate'
]);
navApp.controller('menuController', [
'$scope',
'navSectionList',
'navGetCategories',
function ($scope, navSectionList, navGetCategories) {
$scope.navSectionList = navSectionList.query();
$scope.getSectionID = function (event) {
$scope.navGetCategories = navGetCategories
.getResource([event])
.query();
};
}
]);
navApp.factory('navSectionList', [
'$resource',
function ($resource) {
return $resource('/api/navigation/section/list', {}, {
query: { method: 'GET', params: {}, isArray: true }
});
}
]);
navApp.factory('navGetCategories', [
'$resource',
function ($resource) {
var service = {
getResource: function (sectionID) {
return $resource('/api/navigation/category/' + sectionID, {}, {
query: { method: 'GET', params: {}, isArray: true }
});
}
};
return service;
}
]);
getSectionID kept passing undefined to navGetCategories.getResource() until I put event into a box [event].
I still don't really know why it wouldn't work without the square brackets, maybe someone else can explain why.
well, I do it this way
Server
[HttpGet]
[Route("Api/Account/GetPasswordReset")]
public dynamic GetPasswordReset(string UserName, string OldPassword, string NewPassword)
{}
Client
$http({
method: "GET",
url: CTHelper.ApiUrl() + '/Api/Account/GetPasswordReset?UserName=' + CTHelper.UserName()
+ '&OldPassword=' + $scope.CurrentPassword + '&NewPassword=' + $scope.NewPassword,
}).then(function mySuccess(response) {
}, function myError(response) {
});
I am making a request to a 3rd api through my backend. The api returns an array of events to my frontend and I am having trouble storing it in the ember DS. The route for my API request is eventful and the route/model I am trying to create an event for is eventful-event. I call:
this.get('store').createRecord('eventful-event', concert)
in my eventful adapter and get that 'store' is undefined. Here is my code for my eventful route:
import Ember from 'ember';
export default Ember.Route.extend({
model () {
return this.get('store');
},
data : {},
ajax: Ember.inject.service(),
actions: {
searchForEvents (data) {
let eventful = this.get('store').createRecord('eventful', data);
return eventful.save();
// .then(() => this.transitionTo('search-results'));
// return this.get('store').findAll('eventful');
// let something = this.get('store').findAll('eventful');
// console.log('something:', something);
// return eventful.save();
}
}
});
and my eventful adapter:
import ApplicationAdapter from 'ga-wdi-boston.event-bookmarker/application/adapter';
import Ember from 'ember';
export default ApplicationAdapter.extend({
ajax: Ember.inject.service(),
createRecord (store, type, record) {
let serialized = this.serialize(record, { includeId: true});
let data = { serialized };
let dataToSend = {'keywords': record.record.keywords, 'location': record.record.location };
return this.get('ajax').request('/eventful', {
method: 'POST',
data: dataToSend,
}).then((events) => {
events['eventful-event'].forEach(function(concert){
this.get('store').createRecord('eventful-event', concert);
});
});
}
});
This forEach is where the error is thrown. I'm new to ember so I apologize if I am overlooking something simple, or if my approach is not correct. Any advice or help would be much appreciated.
Just put a debugger in. 'This' in the adapter is undefined.
I believe context is your problem. Try using store (argument of adapter method) instead of this.get('store') inside local function:
createRecord (store, type, record) {
let serialized = this.serialize(record, { includeId: true});
let data = { serialized };
let dataToSend = {'keywords': record.record.keywords, 'location': record.record.location };
return this.get('ajax').request('/eventful', {
method: 'POST',
data: dataToSend,
}).then((events) => {
events['eventful-event'].forEach(function(concert){
store.createRecord('eventful-event', concert);
});
});
}
I have pass two callback function success and error on promise returned from ajax call using then method. Now i am unable to get Ember component object inside success/error method.
import Ember from 'ember';
export default Ember.Component.extend({
data:null,
issueType:'',
description:null,
prepareSubmitRaiseIssueModal:function(){
var data = this.get('data');
this.set('ticket.category',data.category);
this.set('ticket.name',this.get('session.currentUser.first_name'));
this.set('ticket.phone',this.get('session.currentUser.phone'));
this.set('ticket.groupId',data.groupId);
this.set('ticket.ownerId',this.get('session.currentUser.id'));
this.set('ticket.oyoId',this.get('session.currentOwnerHotelOyoId'));
this.set('ticket.ticketRaisedBy','owner');
this.set('ticket.bookingId',data.bookingId);
this.set('ticket.subType',data.subType);
this.set('ticket.subSubIssue',data.subSubIssue);
this.set('ticket.email',this.get('ticket.oyoId')+'#oyoproperties.com');
this.set('ticket.subject',this.get('ticket.oyoId')+' : '+this.get('ticket.category'));
this.set('ticket.description',this.get('description'));
},
success:function(){
console.log(this.get('description'));
},
error:function(){
console.log(this.get('description'));
},
actions :{
submitIssue:function(){
this.prepareSubmitRaiseIssueModal();
this.get('ticket').submitRaiseIssue().then(this.success,this.error);
//this.send('closeRaiseIssueModal');
},
closeRaiseIssueModal:function(){
this.sendAction('closeRaiseIssueModal');
}
}
});
i am able to get Ember component object if instead of passing named function i pass anonymous function.
submitIssue:function(){
var self = this;
this.prepareSubmitRaiseIssueModal();
this.get('ticket').submitRaiseIssue().then(function(response){
console.log(self.get('description'));
},
function(err){
console.log(self.get('description'));
});
//this.send('closeRaiseIssueModal');
},
is there any way i can get the Ember component object's reference for former case??
Wow speaking of a spaghetti.
prepareSubmitRaiseIssueModal:function(){
var data = this.get('data');
this.set('ticket.category',data.category);
this.set('ticket.name',this.get('session.currentUser.first_name'));
this.set('ticket.phone',this.get('session.currentUser.phone'));
this.set('ticket.groupId',data.groupId);
this.set('ticket.ownerId',this.get('session.currentUser.id'));
this.set('ticket.oyoId',this.get('session.currentOwnerHotelOyoId'));
this.set('ticket.ticketRaisedBy','owner');
this.set('ticket.bookingId',data.bookingId);
this.set('ticket.subType',data.subType);
this.set('ticket.subSubIssue',data.subSubIssue);
this.set('ticket.email',this.get('ticket.oyoId')+'#oyoproperties.com');
this.set('ticket.subject',this.get('ticket.oyoId')+' : '+this.get('ticket.category'));
this.set('ticket.description',this.get('description'));
},
How about
prepareSubmitRaiseIssueModal:function(){
var data = this.get('data');
var ticket = this.get('ticket')
ticket.setProperties({
'category': data.category,
'name': ...
})
},
And to pass reference's you can either use
promise.then(function() {
this.mysuccess();
}.bind(this), function() {
this.myerror();
}.bind(this))
const self = this;
promise.then(function() {
self.blah();
});
promise.then(result => {
this.blah();
})
In your case I would write a utility JS file for displaying notifications.
And handle success for each promise personally and let errors be handled in a general error method.
utils/notifications.js
function reportError(error) {
displayNotification('error', getErrorMessage(error));
}
import reportError from 'utils/notifications';
export default Ember.Controller.extend({
....
promise.then(result => {
// Do custom stuff with result;
}, reportError);
....
});
And promise within a promise
return promise1.then(x => {
return promise2.then(x2 => {
return promise3 ... etc
})
}).catch(reportError); // Single hook needed but u need to return promises
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 have moved some common code to factory. but the controller is executing before factory get loaded. In this case i am getting the blank response(zero results)
can anyone suggest the best solution.
here is my angular factory,
app.factory('TabsFactory', function($resource){
var activetabs = {};
activetabs.getDepositAccountDetails = function() {
return $resource('xxxx/:number', {}, {
getDepositAccountDetailsService: {
method: 'GET',
isArray: false
}
});
}
activetabs.getAccountInfo = function(){
return accountinit.accountInfo;
}
activetabs.setAccountInfo = function(accountnumber, result) {
var accountinit = {
accountInfo: []
}
if (result.code == "v") {
activetabs.getDepositAccountDetails().getDepositAccountDetailsService({
number: accountnumber
}).$promise.then(function(response) {
accountinit.accountInfo = response;
//here i am getting the JSON response
}, function(error) {
});
}
return accountinit;
}
return activetabs;
});
controller,
TabsFactory.setAccountInfo(accountnumber, $scope.accountInfo);
$scope.accountInfo = TabsFactory.getAccountInfo();
alert(JSON.stringify($scope.accountInfo));
You should use chain promise to update scope variable, because your accountInfo variable is updated inside $resource promise.
Code
TabsFactory.setAccountInfo(accountnumber, $scope.accountInfo).then(function(data){
$scope.accountInfo = TabsFactory.getAccountInfo();
alert(JSON.stringify($scope.accountInfo));
});
Update
Service method should return promise inorder to continue promise chain
activetabs.setAccountInfo = function(accountnumber, result) {
var accountinit = {
accountInfo: []
}
if (result.code == "v") {
//added return below
return activetabs.getDepositAccountDetails().getDepositAccountDetailsService({
number: accountnumber
}).$promise.then(function(response) {
accountinit.accountInfo = response;
return accountinit.accountInfo;
//here i am getting the JSON response
}, function(error) {
});
}
return accountinit;
}
Yes, this will happen because of JavaScript executing asynchronous operations but your controller in such a way that it expects things to be synchronous operations.
When you call TabsFactory.getAccountInfo() its possible that your $resource('xxxx/:number') is still not completed and response ready for you to process!!
So, what to do? You have make use of promise. I usually have a repository (A factory with method that return promise) to handle server communications. Here is an example:
app.factory('accountRepository', ["$http","$q",function($http,$q){
return {
getDepositAccountDetails : function(id) {
var deferred = $q.defer();
$http.ger('xxx').success(deferred.resolve).error(deferred.reject);
return deferred.promise;
}
};
}] );
My repository will have more operations like add account, update account info etc..
my controller/service then calls these methods as follows:
accountRepository.getDepositAccountDetails(123).then(function(response) {
// Process the response..
}, function(error) {
// Some error occured! handle it
});
doing so, my code gets executed only after I get response from server and data is ready for consumption or display. Hope this helps..
Update: You might want to have a look at this to get the idea ;)