I've started working on EmberJS and I absolutely love it. It does have a learning curve but I believe it has fundamentally very meaningful principles.
My questions is how to make GET and POST calls in Ember JS. I understand that there are models / store, but models (in my opinion) would be to only a particular entity's attributes.
Any thoughts on the following questions would be great.
1. USER A send friend request to USER B. Should there be a "Request"
model? And do I just make a POST request?
2. Some arbitrary data needs to be returned for the page. Not
particularly of a model. Should I still make a model for that?
For use a simple GET request?
3. User needs to update this profile photo. How can the file
to be uploaded, be set as a model attribute?
How should I go about making regular GET and POST calls (if I am to do them at all). Just use jQuery's $.ajax() or is there another way. I also found a service ember-ajax which has extended the $.ajax into a promises style.
Any thoughts would be much appreciated.
Long live EmberJS :)
First option: You can use ember-data. It has customizations such as serializers or adapters.
Second option: You can use addons like ember-ajax.
Our usage is just using jQuery's ajax(). We wrote a service that just wraps jquery.ajax() and use it everywhere in our code. We believe that it gives us a flexibility of writing different kind of queries. We don't have any model of ember-data.
Sample -pseudo- code:
export default Ember.Service.extend({
doPostCall(target, data, options=null){
//consider cloning options with Ember.$.extend
var requestOptions= options || {};
requestOptions.url=target;
requestOptions.type='POST';
requestOptions.data=JSON.stringify(data);
doRemoteCall(requestOptions);
},
doGetCall(target, data=null, options=null){
//consider cloning options with Ember.$.extend
var requestOptions=options || {};
requestOptions.url=target;
requestOptions.type='GET';
requestOptions.data=data;
doRemoteCall(requestOptions);
},
doRemoteCall(requestOptions){
//assign default values in here:
// such as contentType, dataType, withCredentials...
Ember.$.ajax(requestOptions)
.then(function(data) {
Ember.run(null, resolve, data);
}, function(jqXHR , textStatus, errorThrown) {
jqXHR.then = null;
Ember.run(null, reject, jqXHR, textStatus, errorThrown);
});
}
});
PS: By the way, if you need to run your app in server-side (with fastboot), you cannot use jQuery. So use ember-network.
If you are performing CRUD operations over models, ember-data is nice. In most apps, CRUD operations account for ~90% of requests.
There is occasions where an app needs to make requests that not ideal for ember-data, and that is where ember-ajax enters the game. It allows you to do requests like you'd do with just jQuery, with the nice addition that requests are done though a service that has some extension points to allow to customize things like headers that are used app-wide, which is more complex with just raw jquery.
If your app is going to run in fastboot, you should use ember-network, because it works both in the browser and in node, while jquery/ember-ajax does don't.
Current best practise would be ember-fetch. since ember-network is deprecated in favor of ember-fetch.
You can just install ember-fetch addon by running the below command,
ember install ember-fetch
and then just import fetch and you are good to use fetch method.
import fetch from 'fetch';
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return fetch('/my-cool-end-point.json').then(function(response) {
return response.json();
});
}
});
Related
I'm building an Ember app with Parse as my backend. Parse out of the box does something odd with relationships – they only work one way. :/ I've been down the road of trying to manually make relationships work both ways by saving both models in Ember, but that seems messy and can mean that data gets out of sync quite easily. Yuck. So now I'm looking for alternatives.
First, let me explain in a bit more detail.
Let's say I have a post and comments. Parse will store the relationship from comments -> post, but not the other way around. Or, I can manage a list of comments in a 'Pointer array', but not the one-to-one relationship from comments -> post.
I see two options:
Modify the way the API works using their Cloud Code platform. This might be the easier route, but I'd rather address this in Ember so I can open source my adapter/serializer for others who run into this issue.
Figure out a way to make multiple requests in my adapter. I imagine it would work something like this:
a. this.store.findRecord('post', id) is called in the app code.
b. Ember adapter sees that post has a hasMany relationship based on what's defined in the model.
c. The adapter generates a url that looks for comments where the post's id matches the one I'm trying to find (pretty easy to do with Parse). Yes, this means multiple requests, but only 1 more (per relationship). It would be similar to what Ember does when there is a links attribute in the Payload, but we don't have that since Parse doesn't recognize the hasMany relationship`.
Looking for 2 things. First, any thoughts on if I'm thinking about this correctly, or suggestions on other options. Second, and probably most important, some ideas on what hook I can latch onto in my adapter to fire off a second request. My initial thought was something like this:
findRecord: function(store, type, id, snapshot) {
snapshot.eachRelationship(relationship => {
// Do something to build a url for each hasMany relationship
});
return Ember.RSVP.promise(...);
}
Any help with this is much appreciated.
So, basically you're trying to override the default findRecord behaviour of ember-data.
Your current approach sounds about right. You can create an adapter and provide custom definition to methods such as findRecord, findAll, query and so on.
A typical code example can be :
findRecord(store, type, id, snapshot) {
let data = {};
const traditional = true;
return new Ember.RSVP.hash({
news: Ember.$.ajax({ url: wpUrl + 'post', dataType: "jsonp", type: 'GET' }),
events: Ember.$.ajax({ url: wpUrl + 'comments', dataType: "jsonp", type: 'GET' })
});
});
The code snippet is completely vague, but i hope you got my point..
I am doing a POC for isomorphic JavaScript application to render HTML from the server side. The POC is working with simple HTML, but I want to make an API call and get the JSON response and send to the render function. I tried various ways but it is not working.
What am I missing? I am very new to React.js.
loadCategoriesFromServer: function() {
var self = this;
// get walking directions from central park to the empire state building
var http = require("http");
url = "api url here";
var request = http.get(url, function (response) {
// data is streamed in chunks from the server
// so we have to handle the "data" event
var buffer = "",
data,
route;
response.on("data", function (chunk) {
buffer += chunk;
});
response.on("end", function (err) {
data = JSON.parse(buffer);
//console.log(data.d);
//console.log(data.d.Items);
self.setState({
categories: data.d.Items
});
});
});
}, // load from server end
getInitialState: function() {
return { categories: [] };
},
componentWillMount: function() {
console.log("calling load categories")
this.loadCategoriesFromServer();
},
render: function () {
//console.log("data");
//console.log(this.state.categories);
var postNodes = this.state.categories.map(function (cat) {
console.log(cat);
});
return (
<div id="table-area">
//i want to paint the data here..
</div>
)
}
});
Fetching inside of component using componentWillMount is not a right place, in case when you need to render server side. You need to somehow move it out form component, and pass actual data as props after it is fetched - for example as #JakeSendar suggested in his answer.
I have some experience doing isomorphic app with React, and the main problem I faced is how to wait until all data would be loaded before first render
As #FakeRainBrigand already mentioned in comments, there is not only one way to do this, and it depends from your requirements.
There is few ways to do build an isomorphic app, the some interesting from my perspective is: https://github.com/webpack/react-starter and http://fluxible.io/
But, the most elegant way to do this, as I figured out for myself - is to organise asynchronous rendering for react components, in particular using RxJS.
In general my application is structured as following:
views - React components without any logic (just a view)
models - Observables with current state (initial data is loaded using superagent, then combined with other models and/or actions results).
In simple case it is something like:
Rx.Observable.defer(fetchData).concat(updatesSubject).shareReplay()
actions(or intents) - Observers used to collects user input, do something, and dispatch action results to subscribers models and/or other actions. In simple case something like:
updatesSubject = new Rx.Subject();
action = new Rx.Subject();
action.switchMap(asyncRequest).subscribe(updatesSubject)
components - Observables(stream of virtual DOM elements) combined from models, other components and actions (I have a note about this, explaining how and why to create Observable React elements with RxJS), also now I am planning to add partial components (tuple from: react component, observables, observers, and properties. partially filled with using DI)
router - component responsible to handling location changes,
in general main feature is to map location changes to stream of virtual DOM elements and meta information. But in details, it is bit more complicated in my case(url generation, active url highlighting, handling scrolls when navigating, also it has possibility of nested routes and multiple views)
All this is assembled together using DI container, in my case similar to angular2 DI container, but a lot simplified for my specific needs.
Components, models and actions are created using DI.
On server side application is like this:
var rootInjector = new Injector();
// setup server specific providers
rootInjector.provide(..., ...)
app.get('/*', function(req,res){
var injector = rootInjector.createChild();
// setup request specific providers
injector.provide(..., ...);
injector.get(Router)
.first()
.subscribe(function(routingResult){
res.render('app', {
title: routingResult.title,
content: React.renderToString(routingResult.content)
});
});
}
and similar on client side:
var rootInjector = new Injector();
// setup server specific providers
// actually this is omitted in my case because default providers are client side
rootInjector.provide(..., ...)
contentElement = document.getElementById('#content');
rootInjector.get(Router)
.subscribe(function(routingResult){
document.title = routingResult.title;
React.render(routingResult.content, contentElement)
});
In comparison to flux, it is more declarative and more powerful way to organise app. And in case of isomorphic app - for me, it looks much better that various hacks with flux. But of course there is drawbacks... - it is more complicated.
Likely later, I will opensource all this, but for now - it is not quite ready to be published.
UPD1:
Original answer is a bit outdated(later I plan to update it), and I have some progress in this area.
Links to code mentioned above, already opensourced:
DI container: di1
Container for react componentns(connecting view to observables and obsrvers): rx-react-container
Starter template, for implementing isomorphic widgets, using RxJS and React, and libraries above: Reactive Widgets
About complete application(work still in progress, and documentation there is not quite good, but in general it should be clear):
Router built especially for isomophic reactive applications router1 and react components to use it router1-react
Application template with router and all libraries mentioned above: router1-app-template
React's renderToString method (for rendering components on the server) is synchronous. Therefore, any sort of async task, such as your api request, will still be pending by the time the component has rendered.
There are a couple of ways you can go about fixing this, depending on whether or not you want to fetch your data on the server or client.
If you choose to fetch the data on the server, first move your api-request logic outside of your component. Then, render your component in the callback, passing the fetched-data as a prop. It would look something like this:
response.on("end", function (err) {
var data = JSON.parse(buffer);
var markup = React.renderToString(Component({categories: data}));
});
Inside your component, you'd be able to access the data via this.props.categories.
The other option is to handle the api request on the client. You would make an AJAX request in componentDidMount, and set the component's state from the fetched data. It would look very similar to what you have now, the key difference being that your request logic would live in componentDidMount (async, called on the client) rather than componentWillMount (not async, called on the server).
You should use superagent, works really good for me, also you are missing the most important part, you should use flux to fetch data from a server, flux is the way that facebook strongly recommended, it's pretty easy to use flux architecture.
There are many code examples using $resource out there. I came across this one, and the code is quite clearly laid out: https://github.com/apotry/stockwatch
I like this example because:
It interacts with a Rails backend, which is how I am using Angular
It uses $resource
It is using a non-rest route (ohlc)
The code is quite clean
To call the functions defined is quite simple as shown in the controller code below, but is it a good idea to embed the save in the factory?
My question is: now that Angular 1.2+ includes promises, is this type of code still valid and considered a good practice? How would this code respond to an error state?
Here the resources are defined
app.factory('Stock', ['$resource', function($resource) {
function Stock() {
this.service = $resource('/api/stocks/:stockId', {stockId: '#id'}, {'update': { method: 'PUT' }});
};
Stock.prototype.all = function() {
return this.service.query();
};
Stock.prototype.delete = function(stId) {
return this.service.remove({stockId: stId});
};
Stock.prototype.create = function(attr) {
return this.service.save(attr);
};
Stock.prototype.update = function(attr) {
return this.service.update(attr);
};
Stock.prototype.ohlc = function(stId) {
return $resource('/api/stocks/:stockId/ohlc', {stockId: '#id'}).get({stockId: stId});
}
return new Stock;
}]);
Here is an example of the delete, create and a custom route (ohlc):
$scope.requestOHLC = function (stockid) {
return Stock.ohlc(stockid);
}
$scope.createStock = function() {
$scope.fetchYahooFinanceData($filter('uppercase') ($scope.newCompany["symbol"])).then(function(result) {
$scope.error = false;
$scope.stocks.push(Stock.create(result));
$scope.newCompany = '';
}, function(error) {
$scope.error = true;
});
};
$scope.deleteStock = function(id, idx) {
Stock.delete(id);
$scope.stocks.splice(idx, 1);
};
EDIT
I am trying to work out a simple and clear practice for using $resource based rest routes in angular.
Different from the above code, but based on it. Assume the code below uses a service which is basically the same as the factory above. In this example I call one REST resource to create a new resource (rails table entry), and then pass the newly created id to another call. Note that createPanelHeader references the $scope.selector.paneldata.primer3_parameter_id scope variable. Not sure if this is a good practice either.
I found this would not work unless I uses $promise.then but this seemed a bit convoluted. Am I on the right track?
// Section: Create Panel header
createPrimer3Parameter = function() {
primer3_parameter = Primer3Parameter.create().$promise.then(function(primer3_parameter){
$scope.selector.paneldata.primer3_parameter_id = primer3_parameter.id;
createPanelHeader();
}, function() {
alert('Error creating primer3parameter');
})
};
COMMENT
I am really just trying to work out a simple method for accessing REST resources from a Rails API, with at most 1 level of nesting. I think I am missing something as it seems remarkably difficult.
What I am hearing so far is not to use $resource, even under 1.2. That I should instead use raw $http or Restangular.
Also, there seem to be some 1.2 changes that affect Restangular. The solution feels a bit like a hack to me:
https://github.com/mgonto/restangular#using-values-directly-in-templates
UPDATE
I didn't really come away 100% clear, so I have posted a Bounty: https://bountify.co/write-an-angular-service-for-these-rails-routes-using-restangular
is this type of code still valid and considered a good practice?
This code is valid, but considered deprecated as of 1.2.0-rc3. It will work in all version of angular 1.2 and 1.3 up to but not including 1.3.0-beta10, where automatic promise unwrapping has been removed.
$scope.stocks.push(Stock.create(result));
In the line above you've created an array of promise objects on $scope. Then in index.html.erb you are referencing the promise directly via the stock iterator:
<li ng-repeat="stock in stocks">
<div id='symbol'>
{{stock.symbol}}
</div>
The non-deprecated way of handling promises does not allow you to bind directly to the promise like this.
How would this code respond to an error state?
The app is handling errors here:
}, function(error) {
$scope.error = true;
});
and here:
<div ng-show="error">
There was a problem with the Yahoo Finance API. Please try again later.
</div>
For error handling, you're not binding with a promise directly so this works fine in all versions of angular.
A better way?
First, shelf the javascript directory structure in the stockwatch example. Then, follow this directory structure instead. Finally, integrate Restangular into your project. Create a stockModel factory that internally instantiates a Restangular object, but returns the object that will be populated later after the promise resolves (model). Instead of binding a promise in your partial, bind the unpopulated result object.
.factory('stocksModel', function (Restangular) {
var model = {};
var rest_stocks = Restangular.all('stocks');
model.doSomethingRESTful = function (...) {
// return a promise in case the controller needs it
return rest_carts.some_restangular_method(...)
.then(function() {
model.some_data_which_was_acquired_RESTfully = ...;
});
};
return model;
});
In your controller:
$scope.stocks = stocksModel;
In your partial:
{{stocks.some_data_which_was_acquired_RESTfully}}
To be blunt
No. If possible, I would improve the code so that is it more maintainable and update to date with Angular. Let me explain why it is a good idea. . .
Why are you making your life so hard?
Angular is a wonderful framework that lets you tackle fairly complex problems with simple solutions. The dark side of Angular is it is easy to over engineer a solution if you go off the beaten path. Angular exposes a lot of the internals so it is tempting (and fun!) to muck around with it instead of going for the simple route.
Growing pains
I believe what you are dealing with is due to the growing pains Angular has had as it matured as project. A lot of side projects sprung up to fix the leaky holes that have since been plugged.
Rails and Angular, fast friends
So I am basing this off of how I have grown to use Rails with Angular. The examples are pulled from a pet project that is not going anywhere. Unfortunately, this is all in coffeescript, so hopefully that wont cause you problems. It is structured how I found works best with Rails, while keeping the spirit and fun of Angular.
* app/assets/javascripts
* app
* project_name_app.js.coffee.erb
* controllers
* controllers.js
* directives
* directives.js
* filters
* filters.js
* resources
* resources.js
* services
* services.js
Rails' asset pipeline wraps everything up using the application.js with the include:
//= require app/project_name_app
In the app/project_name_app.js.coffee.erb, it loads all of the directories with the includes
#= require_self
#= require app/controllers/controllers
#= require app/directives/directives
#= require app/filters/filters
#= require app/resources/resources
#= require app/services/services
Last, each of the sub directories js (controllers.js, directives.js, filters.js, resources.js, services.js) simply loads everything in that directory:
//= require_tree .
With this setup, the project_name_app.js.coffee.erb is loaded first, setting up Angular, the dependencies, and the app configuration. Then the controller, directives, filters, etc are loaded. One of the perks is new javascript added to a sub directory is automatically included thanks to the require_tree.
Keeping the $resource simple
The best $resources are RESTFUL. Meaning that it is the sample URL and the functionality changes based on the http method of the request. The downside is, if you have different URLs, you will probably need multiple resources. An example from the users_resource.js.coffee:
angular.module("DeployerApp.resources").factory "Users", ($resource) ->
$resource "/users.json", {},
index:
method: "GET"
isArray: true
type: "User"
angular.module("DeployerApp.resources").factory "User", ($resource) ->
$resource "/users/:user_id.json", {},
show:
method: "GET"
update:
method: "PUT"
angular.module("DeployerApp.resources").factory "CurrentUser", ($resource) ->
$resource "/users/current.json", {},
show:
method: "GET"
To get all users, you call Users.index(). To get a single user, you call User.show( user_id: 1 ). Last, a convenience $resource I that often use, to get the current authenticated user, CurrentUser.show().
Pretty straight forward and easy to read, avoiding have to have thick models. Each of the User $resources can be tested separately.
Angular is ready to do the work
You really only need to start mucking with $promise if you have some complicated juggling act when dealing with multiple responses. I have found it is simpler to just pass in the success and error callbacks to the $resource, for example:
CurrentUser.show success = (user) ->
$scope.currentUser = user
, error = (data,status) ->
# redirect to login
The variable names help make the code more readable, without them the coffeescript function definitions kind of blend together.
Simpler is always better
You do not need to worry about the $promise.then of the $resource, allowing you to tidying things up.
So I'm working on building a dynamic model for a project that reacts to data sent from an API. The api will return, among other things, what your location should be and this in turn becomes the url. So, eg:
{
location: 'xyz'
(...)
}
So currently my router will transition to the right route dynamically. But I still have to hardcode each route ( IndexRoute, LocationXYZRoute, LocationABCRoute, etc).
My goal is to create a single route that handles things dynamically. We'll call it App.LocationRoute and my routes would look something like:
App.Router.map(function() {
this.resource(':location', function() {
this.route(':subLocation')
}
}
Now, I have two architectural questions:
1) Whats a good way to handle this sort of dynamic routing? (I've read through the guide about dynamic routing using the ':post_id' type example, but I think I need a more holistic example to really grasp it.
2) The API sends back a whole host of other data as well. I want to add this to the route's model but I also have some other static models. Doing...
this.controllerFor(location).set('content', APIdata);
... works, but it does not set for routes currently using static models. I tried something like:
this.controllerFor(location).set('apiData', APIdata);
and...
this.controllerFor(location).set('model:apiData', APIdata);
... but neither worked.
Any suggestions?
1) Yes, you should use dynamic segment
this.resource('location', { path: '/location/:location_id' }, function() {
this.resource('sublocation', { path: '/sublocation/:location_id' });
});
2) Are you using ember-data? You could check sideloaded data. Anyway, you could read the json and set the payload of each entity for each specific route.
this.controllerFor('location').set('content', APIdata.location);
this.controllerFor('user').set('content', APIdata.user);
People could help you better, if you separate your questions and create a http://emberjs.jsbin.com/ with isolated each specific case?
I have the below that is setup and working properly.
require(['models/profile'], function (SectionModel) {
var sectionModel = new SectionModel({id: merchantId, silent: true});
sectionModel.fetch({
success: function (data) {
$('#merchant-name').html(data.attributes.merchantName);
}
});
});
But it will only work in one instance. I am wondering how to correctly edit the above code to allow multiple instances.
<h3 id="merchant-name"></h3>
The content is generated within 'Save' function.
merchantName:$('#merchantName').val(),
What you want to do is set up the rest of the components for the Backbone application. The beauty of Backbone.js is it's ability to separate collections, models and views so your logic stays in a proper place.
You'll want to use an AJAX call to retrieve your models from the server using a Collection. Then, use the collection's reset function.
Here's an example of how you might fetch a collection of models from the server.
var MyCollectionType = Backbone.Collection.extend({
getModelsFromServer:function()
{
var me = this;
function ajaxSuccess(data, textStatus, jqXHR)
{
me.reset(data);
}
$.ajax(/* Insert the ajax params here*/);
}
});
var collectionInstance = new MyCollectionType({
model:YourModelTypeHere
});
collectionInstance.getModelsFromServer();
Then, to render each one, you'll want to make a View for each model, and a Collection View. There are a lot of resources though on learning basic Backbone.js and I feel that you might benefit from looking at a few of those.
Keep in mind that Backbone collections will by default merge models with the same id. 'id' usually references a model in the backend of an application, so make sure each id is actually what you want it to be. I work with an application that has a non-Restfull back end, and so ID's are never transferred to the front end.
There are some excellent resources available to begin starting with Backbone.js.
https://www.codeschool.com/courses/anatomy-of-backbonejs
(This is a free course up to a point, and a great starter.)
http://net.tutsplus.com/tutorials/javascript-ajax/getting-started-with-backbone-js/
http://javascriptissexy.com/learn-backbone-js-completely/