I’m trying to create a Ember service that calls an API to query data in a database. I want to create this service so I can inject a dataset (an array of objects) in various controllers, components and routes. I’m not sure if this is one of ember’s ‘best practices’ but it seems like it will solve an immediate issue while learning the framework. For this example I have a service called ‘get-data.js’ and I have a component called ‘responsive-table.js’ that i want to have access to an array of objects that I receive from my database. Should I be using a service to make a ajax request to the api every time i need this array? Should I be using ‘ember-data’ and calling to the ‘store’ and use the ‘findAll’ method? Whenever I try to call to the store and comment out the response from a ‘findAll’ I get a class object? Whats the best way to access your server data in components and controllers with ember.js and ‘ember-data’
service
// service
import Ember from 'ember';
const {
Service, inject: { service }, computed, run, set, get
} = Ember;
export default Service.extend({
ajax: service(),
usingJQueryAjax() {
let data = $.get('/api/data').then(function(result ) {
console.log("right here: ", result );
return result;
});
return data
}
});
componet
// component
import Ember from 'ember';
const {
Component, inject, get, set, $
} = Ember;
export default Component.extend({
store: inject.service(),
actions: {
usingStoreDoesntWork() {
var data = get(this, 'store').findAll('nba');
return data;
},
usingJQueryAjax() {
var data = $.getJSON('/api/data').then(function(result) {
console.log("Array I want to return: ", result );
});
return data;
}
}
});
Whats the best approach to get data into a component from an API ?
Ember Components are designed to have no connection to a source of data. This increases their reusability, since all data that a component uses must be passed in.
Typically, the bridge from your server application and the Ember model is the Route. Here, you do what is necessary to retrieve data and return it in the model() hook. This is true whether or not you use Ember Data.
Your route (which is configured in router.js), will be called when necessary to get a model. Your template will have access to that via the variable model. You would then include a component in your template and pass data to the model via attributes.
Route
model() {
// retrieve data here, using whatever technique you want. It could be
// constant data or retrieved via jQuery, or the Ember Data mechanism (e.g.
// the "store"
return {
someList: [1,2,3,4,5]
};
}
Template
Here's your data:
{{list-display list=model.someList}}
Component template
<ul>
{{#each list as |item|}}
<li>{{item}}</li>
{{/each}}
</ul>
The component, list-display can now display any list, so it is nicely reusable. The connection with data is made in the route template, and the data is retrieved by the Route. If you want to create a Service to retrieve data, then use the service in the Route to return data from the model() hook.
Hope this clarifies it for you.
Related
I am navigating into a view from another view and passing the itemdId as param value to vue router.
I want to be able to call firebase with that itemId so that I can filter the data and the filtered result/data is used in the UI. I am using vuefire.
What is happening is that vue starts rendering before the data is available in created() and I see error is the console saying view is referring to undefined property values.
Is there a way to render the view after the data is available?
I have tried the using beforeMount as well as created and beforeCreate approaches.
See code below:
<h1>{{projects[0].project_name}}</h1> //it says type error. cannot read property project_name of undefined. Sometimes on refresh it works but most times it does not.
Script code below:
let projectsRef = db.ref('projects');
export default {
name: 'ProjectDetailsOpenInvesting',
props: {
data: Object
},
firebase:{
projects: projectsRef
},
data(){
return {
projects:[],
.....
}
},
created(){
var that = this;
console.log(this.$route.params.itemid) //this works
this.projects = this.projects.filter(function(p){
return p.project_id == that.$route.params.itemid //this works as well
})
}
Firebase screenshot here
As you have mentioned, one approach is to fetch after navigation, i.e. fetch the data in the component's created hook.
To do that with vuefire you need to programatically bind the Realtime Database projectsRef Reference to the projects property in your Vue application, as follows:
created(){
console.log(this.$route.params.itemid)
const itemId = this.$route.params.itemid;
this.$rtdbBind('projects', projectsRef.orderByKey().equalTo(itemId)).then(projects => {
this.projects === projects;
});
}
As explained in the API doc:
$rtdbBind returns a Promise that is resolved once the data has been
retrieved and synced into the state.
Note that you need to install the rtdbPlugin plugin: https://vuefire.vuejs.org/api/vuefire.html#rtdbplugin
Also note that instead of filtering the desired project item in the front-end (with filter(function(p){return p.project_id == that.$route.params.itemid}))), we filter it in the back-end, at the level of the database (projectsRef.orderByKey().equalTo(itemId)) which is more efficient and which avoids transmitting the entire set of objects from the back-end to the front-end (and avoids paying for this downloaded volume, see https://firebase.google.com/pricing?authuser=0).
In my angular project I currently have a service that uses http calls to retrieve data from my java code. Every 10 seconds the service calls the Java side and gets new data from it. I now have another component that needs the data in my service. I need this component to have a field called 'data' that just gets updated automatically when the service gets new information.
How can I set them up so that the service pushes the new information to my other component? I would like to be able to use {{data}} in my component's html and have that be automatically updated without having to reload the page.
My component does have the service 'Autowired' in already. So currently I can just call 'this.data = this.service.getData()' but that call is within my ngOnInit method so it only happens once, and the data field does not get updated when the service's data field gets updated.
You can create a messaging service that publishes data for subscribers or implement this functionality in your original service.
I would suggest having a separate messaging service and have relevant components or services publish/subscribe to it.
messaging.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class MessagingService {
private sharedValue = new Subject<string>();
// Observable string streams
sharedValue$ = this.sharedValue.asObservable();
// Service message commands
publishData(data: string) {
this.sharedValue.next(data);
}
You would inject the service like this:
constructor(private messagingService: MessagingService ) {}
Publish to the service:
this.messagingService.publishData(sharedValue);
Subscribe to the service:
this.messagingService.sharedValue$.subscribe(
data => {
this.localSharedValue = data;
});
FROM ANSWER BELOW BY: DeborahK (who's courses on Pluralsight everyone should watch)
Actually, all you need is a getter.
Change your 'data' property to a getter and that will do the trick:
get data(): any {
return this.service.getData();
}
Angular change detection will detect any time that data is changed in
the service which will cause it to re-evaluate its bindings and call
this getter to re-get the data.
No need for a fancy service or Subject. :-)
Actually, all you need is a getter.
Change your 'data' property to a getter and that will do the trick:
get data(): any {
return this.service.getData();
}
Angular change detection will detect any time that data is changed in the service which will cause it to re-evaluate its bindings and call this getter to re-get the data.
No need for a fancy service or Subject. :-)
So, I have two paths in my route. I created the two routes as the doc recommends.
My router is the following:
// router.js
Router.map(function() {
this.route('photos');
this.route('photo', { path: '/photo/:photo_id' });
});
If I visit firstly the route /photo/ID and then go to /photos, it will only show one object on the latter. (wrong)
If I visit /photos first it shows all the objects and I can go to /photo/ID later on and it will be fine. (right)
I want to make it work both ways. How to do this? You can see my code for each route down below:
// photo.js
export default Ember.Route.extend({
model(params) {
return this.get('store').findRecord('photo', params.photo_id);
}
});
// photos.js
export default Ember.Route.extend({
setupController(controller, model) {
let photos = this.get('store').findAll('photo');
console.log('add all...');
// convert to an array so I can modify it later
photos.then(()=> {
controller.set('photos', photos.toArray());
});
},
});
I can always call the findAll() function regardless where the user goes, but I don't think this is smart.
The way I am dealing with the page transitions:
To go to photos I use:
{{#link-to 'photos'}}All{{/link-to}}
To go to /photo/ID I inject the service '-routing' and I use in one event click like this:
routing: Ember.inject.service('-routing'),
actions() {
selectRow(row) {
this.get("routing").transitionTo('photo', [row.get('id')]);
}
}
findAll will get it from a store and return immediately and later on it will request the server and update the store. but in your case, as you are not using route model hook, so this live array will not be updated so it will not reflect it in the template.
If I visit firstly the route /photo/ID and then go to /photos, it will
only show one object on the latter.
In the above case, store will contain only one reocrd, so when you ask for store data using findAll it will return the existing single record.
Another option is,
avoiding this photos.toArray() - It will break live-array update, I am not sure why do you need it here. since photos is DS.RecordArray.
Note: It's important to note that DS.RecordArray is not a JavaScript
array, it's an object that implements Ember.Enumerable. This is
important because, for example, if you want to retrieve records by
index, the [] notation will not work--you'll have to use
objectAt(index) instead.
I am trying to limit the number of unnecessary HTTP calls in my application but everytime I subscribe to an Observable there is a request being made to the server. Is there a way to subscribe to an observable without firing http request? My observable in service looks like that:
getServices(): Observable<Service[]> {
return this.http.get(this.serviceUrl).map(res => res.json())._catch(err => err);
}
Then in my component I subscribe to that observable like this:
this.serviceService.getServices().subscribe(services => this.services = services);
What I would like to achieve is to store data somehow on the service itself (so that I can use that data throughout the whole application without making requests on every component separately, but I would also like to know when that data is received by components (which I usually do by subscription).
Not sure if I understand your question correctly, but it seems that you want to cache the results of the first HTTP request in the service, so if the first component fetch the data, the second one (another subscriber) would retrieve the cached data. If that's the case, declare a service provider on the module level so Angular creates a singleton object. Then inside the service create a var that stores the data after the first retrieval.
#Injectable()
export class DataService{
mydata: Array<string>[];
constructor(private http:Http){}
getServices(): Observable<string[]> {
if (this.mydata){
return Observable.from(this.mydata); // return from cache
} else
{
return return this.http.get(this.serviceUrl).map(res => res.json())._catch(err => err);
}
}
}
I would like to know what is the correct way to make an ajax call from an ember component. for example
I want to create a reusable component that makes an employee search by employee's Id, then when the response comes back from the server I want to update the model with the data from the ajax response.
I don´t know if this is the correct way to do it, I'm really new on emberjs.
export default Ember.Component.extend({
ajax: Ember.inject.service(),
actions: {
doSearch() {
showLoadingData();
var self = this;
this.get('ajax').post('http://server.ip.com/api/v1/getEmployee', { "id": this }).then(function(data) {
self.set('model.name', data.name);
self.set('model.position', data.position);
hideLoadingData();
});
}
}});
EDIT: I misunderstood the question, so here's an updated version of my answer:
First, I think you should switch to using ember-data. Then fetching an employee by id would just resolve to calling this.get("store").find("employee", id).
If you wish to use plain ajax, I suggest that you create a Service that encapsulates specifics (API endpoint URL, data format, and so on) and only exposes simple methods for finding and updating models.
And finally, to comply with the "data down, actions up" pattern, you shouldn't update the model in this component. Instead send an action to the parent controller/component. Like so:
app/components/employee-selector.js (the component you're writing):
export default Ember.Component.extend({
actions: {
updateId(id) {
Ember.$.post("http://server.ip.com/api/v1/getEmployee", { id: params.id }.then((response) => {
this.sendAction("select", response);
});
}
});
app/templates/new/it-request.hbs:
{{employee-selector select=(action "selectEmployee")}}
app/controllers/new/it-request.js:
export default Ember.Controller.extend({
actions: {
selectEmployee(employeeData) {
this.set("model.name", employeeData.name);
this.set("model.position", employeeData.name);
}
}
});
Old answer:
An idiomatic solution would be to do this in a Route.
First you should add a route in app/router.js:
this.route("employees", function() {
this.route("show", { path: ":id" });
}
Than define the route in app/employees/show/route.js:
import Ember from "ember";
export default Ember.Route.extend({
model(params) {
return new Ember.RSVP.Promise((resolve, reject) {
Ember.$.post("http://server.ip.com/api/v1/getEmployee", { id: params.id }.then(
(response) => { resolve(response) },
reject
);
});
}
});
(The only reason I wrapped everything in a new promise is to allow response customization - just replace resolve(response) with a code that transforms the raw response from the server and invoke resolve with this transformed version).
But if you'll have more communication with the API, and I suppose that you will, I suggest that you try using ember-data or any other data layer library for ember (probably Orbit).
Or at least write a service that abstracts away all communication with the API and use it anywhere you'd use raw ajax requests.
I was using Ember class directly in action so it looked like this
actions: {
doSomething() {
Ember.$.post('http://your-api-endpoint', data).then(function(response){ /* your callback code */});
}
}
And other way to communicate with BE is to use Ember Store (as you said) then in route you can get model from BE
example
App.PressRoute = Ember.Route.extend({
route: "press",
controllerName: 'press',
model: function(params) {
var controller = this.controllerFor("Press");
if(controller.get("loaded") == false) {
controller.set("loaded",true);
return this.store.find('Article',{limit: 200});
}
else return this.store.all('Article');
},
renderTemplate: function() {
this.render('press');
}
});
There are several ways you can do this!
Firstly, Ember has a convient alias for jQuery: Ember.$. So, if you're familiar jQuery, this should be easy enough.
You can also use Ember's RSVP package. There's a good example here on how to make a request and do something with the response.
Thirdly, you can the ember-ajax service.
But what you're asking (update the model with the data from the ajax response) is already built into Ember Data. You'll need to map your API into what ember expects with an adapter and/or a serializer. Once your service is transformed into what Ember expects, you can query your server for a single record then save it.