Emberjs - How to map api json response to a model? - ember.js

While trying to build an Ember.js sample application, to call a REST Api and display results. I was able to call, fetch and display the results. In the component I am reading through the json response results.
How can I map the response data to a pre-defined model and use that in the component instead? I could not find relevant docs at https://guides.emberjs.com/release/models/defining-models/ or https://guides.emberjs.com/release/in-depth-topics/making-api-requests/#toc_where-to-make-api-requests
Code details:
Using this sample API https://reqres.in/api/users
Model: (--update--)
export default class UserModel extends Model {
#attr('string') id;
#attr('string') firstname;
#attr('string') lastname;
#attr('string') email;
#attr('string') avatar;
}
Route:
export default class UsersRoute extends Route {
async model() {
let response = await fetch('https://reqres.in/api/users');
let data = await response.json();
return data.data;
}
}
Template:
<UserList
#title="List of Users"
#users={{#model}}
/>
Component:
{{#each #users as |user|}}
<tr>
<td>{{user.id}}</td>
<td>{{user.first_name}}</td>
<td>{{user.last_name}}</td>
<td>{{user.email}}</td>
</tr>
{{/each}}

Generate a user model:
ember g model user
import Model, { attr } from '#ember-data/model';
export default class UserModel extends Model {
#attr('string') email;
#attr('string') firstName;
#attr('string') lastName;
#attr('string') avatar;
}
Generate an application adapter and serializer:
ember g adapter application
ember g serializer application
Add your API URL to the adapter:
import JSONAPIAdapter from '#ember-data/adapter/json-api';
export default class ApplicationAdapter extends JSONAPIAdapter {
host = 'https://reqres.in/api'; // the `users` part of the path is autogenerated based on the model name
}
Update the serializer:
import JSONSerializer from '#ember-data/serializer/json'; // note that this is not the default serializer, JSONAPISerializer
import { underscore } from '#ember/string';
export default class ApplicationSerializer extends JSONSerializer {
// specify underscore-formatted keys
keyForAttribute(attr) {
return underscore(attr);
}
// use the `data` property in the payload as the serializable response
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
payload = payload.data;
return super.normalizeResponse(store, primaryModelClass, payload, id, requestType);
}
}
Update your route to use Ember Data methods instead of raw fetch:
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default class UsersRoute extends Route {
#service store;
model() {
return this.store.findAll('user');
}
}
And finally you can then use the data (using camelcase-formatted attributes):
{{#each #users as |user|}}
<tr>
<td>{{user.id}}</td>
<td>{{user.firstName}}</td>
<td>{{user.lastName}}</td>
<td>{{user.email}}</td>
</tr>
{{/each}}

Related

How to pass Error Message to Route from a different Controller in Ember js

Let, I have two routes and two controllers namely login and signup
If I signup successfully then I want to perform transition to the route login with a success message as parameter,
/app/signup/controller.js
import Controller from '#ember/controller';
export default Controller.extend({
actions: {
signup: function(){
let _this = this;
let successMessage = 'successfully registered';
var credentials = this.getProperties('name','identification','password');
let list = this.store.createRecord('user', {
name: credentials.name,
email: credentials.identification,
password: credentials.password
});
list.save().then(function(){
_this.transitionToRoute('login','successMessage');
});
}
}
});
app/login/template.hbs
<body>
{{successMessage}}
</body>
/app/router.js
import EmberRouter from '#ember/routing/router';
import config from './config/environment';
const Router = EmberRouter.extend({
location: config.locationType,
rootURL: config.rootURL
});
Router.map(function() {
this.route('login');
this.route('signup');
});
export default Router;
I think you sort of have 3 options:
Use a route param (query param or positional)
Use a service to manage login stuff, and read some computed property from the service representing your state / message from your login controller
Use a flash/toast style UI where the message lives outside of the app view/component hierarchy
Personally, for where you want to display your message, I'd go for #2, which would look like this:
// app/services/user-session.js
import Service from '#ember/service';
export default class extends Service {
successMessage = null;
signup(name, id, password) {
// logic
this.set('successMessage', 'yay');
}
}
// app/controllers/signup.js
import Controller from '#ember/controller';
import { service } from '#ember-decorators/service';
import { action } from '#ember-decorators/object';
export default class extends Controller {
#service userSession;
#action
signup() {
this.userSession.signup(...);
this.transition...
}
}
// app/controllers/login.js
import Controller from '#ember/controller';
import { service } from '#ember-decorators/service';
import { readOnly } from '#ember-decorators/object/computed';
export default class extends Controller {
#service userSession;
#readOnly('userSession.successMessage') successMessage;
}
Or, in the old syntax:
// app/services/user-session.js
import Service from '#ember/service';
export default Service.extend({
successMessage: null,
signup(name, id, password) {
// logic
this.set('successMessage', 'yay');
}
});
// app/controllers/signup.js
import Controller from '#ember/controller';
import { inject as service } from '#ember/service';
export default Controller.extend({
userSession: service(),
actions: {
signup() {
this.userSession.signup(...);
this.transition...
}
}
});
// app/controllers/login.js
import Controller from '#ember/controller';
import { inject as service } from '#ember/service';
import { readOnly } from '#ember/object/computed';
export default Controller.extend({
userSession: service(),
successMessage: readOnly('userSession.successMessage')
});
hope this helps

Fetching records and nested relations

I have a route in my app (repo) where i fetch bookings belonging to a user, I also include rentals and rentals.rental-ratings in the request. Now, a rental (which belongsTo a Booking) hasMany rentalRatings, and rentalRatings belongsTo Users. In the template I have a component that accepts booking and a rentalRating as arguements. How can I get a rating that belongsTo the booking and the currentUser?
Bookings route:
import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
sessionAccount: Ember.inject.service(),
model() {
const currentUser = this.get('sessionAccount.currentUser');
return this.get('store').query('booking', {
filter: {
'user_id': currentUser.id
},
include: 'rental,rental.rental-ratings'
});
}
});
Bookings template:
<h1>Your bookings</h1>
{{#each model as |booking|}}
{{booking-card booking=booking rating="??? what goes here???"}}
{{/each}}
Easiest way to achieve this, is to use the ember-composable-helpers.
Move the sessionAccount service into the controller. In your template you can just use the filter-by helper:
{{#each model as |booking|}}
{{booking-card booking=booking rating=(get (filter-by "user" sessionAccount.user booking.rental.rentalRatings) "firstObject")}}
{{/each}}

Not able to push the form data in to the ember store

this is my controller code
controllers/new-user.js
import Ember from 'ember';
export default Ember.ObjectController.extend({
actions:{
register:function(){
var person=this.store.createRecord('person'{
firstName:fname,
lastName:sname,
email:email,
password:password,
confirmPassword:confirmPassword
});
person.save();
}
}
});
I am working with ember-cli and i am new to ember so could you please tell me what is wrong in the code.
First there is to notice you should not use Ember.ObjectController but directly extend Ember.Controller. Checkout the deprecation.
Next all your variables fname, sname and so on are not declared. Probably you want to access the attributes on the controller. So either do this:
import Ember from 'ember';
const {get} = Ember;
export default Ember.ObjectController.extend({
actions: {
register(){
let person = this.store.createRecord('person', {
firstName:get(this, 'fname'),
lastName:get(this, 'sname'),
email:get(this, 'email'),
password:get(this, 'password'),
confirmPassword:get(this, 'confirmPassword')
});
person.save();
}
}
});
Or use .getProperties:
import Ember from 'ember';
const {get} = Ember;
export default Ember.ObjectController.extend({
actions: {
register(){
let person = this.store.createRecord('person', this.getProperties(
'fname',
'sname',
'email',
'password',
'confirmPassword'
));
person.save();
}
}
});
However my personal recommendation is to call createRecord in the model hook of the route and then directly bound your template to your model. Later just call .save() in your action.

How can I use ember-simple-auth to set header in custom url api with ember data?

My ember data model:
import DS from 'ember-data';
import config from './../config/environment';
export default DS.Model.extend({
...
useRepairPackage(repairPackageId) {
this.get('session').authorize('authorizer:digest', (headerName, headerValue)=> {
const headers = {};
headers[headerName] = headerValue;
Ember.$.ajax({url: `${config.host}/${config.namespace}/quotations/${this.get('id')}/use_repair_package.json`, type: "PATCH", headers: headers}).then((result)=> {
return this.reload();
});
});
}
});
I check ember-simple-auth document, I found this way to add session in header. But it can not work in model, and how can I add the seesion in this action? Thanks.
You can expose the session service to the model and then access it.
export default DS.Model.extend({
session: Ember.inject.service('session'),
And now access the session below inside to send the session data.

Accessing a property from a service in a template

I have a user service that manages reading data from localstorage, it has a property that reads from the local storage a certain object property, the service is setup like so
import Ember from 'ember';
import LocalUser from 'bidr/models/user-local';
const {
computed: {
alias
}
} = Ember;
export default Ember.Service.extend({
localUser: LocalUser.create(),
user_id: alias('localUser.user.id'),
active_auction: alias('localUser.user.active_auction')
});
In my route.js file for my item route I inject the service like so
user: Ember.inject.service('user'),
And in the template I'm attempting to access it like so
{{user.active_auction}}
I was under the impression I could do that but is that not the case? Do I need to set a property on the item route that is equal to the service property to make this work?
If you don't want to create a controller for this template's scope, you can expose the service to the template by setting a property in the auto-generated controller via the route's setupController hook, like so:
// application/some-route.js
export default Route.extend({
user: service(),
setupController(controller, model) {
this._super(controller, model);
set(controller, 'user', get(this, user))
}
});