Multiple component with multiple models - ember.js

So, I'm building an EmberJS application and I have a route, containing multiple components (e.g. messages and questions).
Each component is extended with a dyn-widget mixin that allows it to be dragged by the user across the screen, as they are presented to the user as windows on a canvas.
To store the information about each dyn-widget window, I use three Ember Data models: a dyn-widget that stores values shared by any widget (position, size...), and a dyn-widget/questions and a dyn-widget/chat that contain additional information about how the widget is set up and a one-to-one relationship to the 'parent' dyn-widget model.
The route has a corresponding controller that allows the user to instianciate new windows. The controller then creates a dyn-widget/whatever model along with the associated dyn-widget model, and everything is stored in the local storage.
However, when I try to implement the model() function into the route, I've found that I can't really return multiple, different models without hacky solutions. I thought of putting the models directly into my components but that breaks the following pattern:
Components are isolated entities that consume data through their
interface, react to data changes that flow via data binding, and
possibly send up named actions.
So how should I approach this? What would the a non-hacky, Ember way to this issue?
Thanks!

It is not true that model in ember by convection cannot return multiple models for a route. If you look at the documentation
https://guides.emberjs.com/v2.6.0/routing/specifying-a-routes-model/#toc_multiple-models
you can use Ember.RSVP.hash to resolve multiple resources
sample router code:
model(params) {
return Ember.RSVP.hash({
messages: this.store.findAll('message'),
questions: this.store.findAll('question')
});
},
In this way you can feed your components with data from multiple models directly from router - so the pattern is that components gets the data and send the actions to route or controller.
This is a standard way of doing things and it was in ember from 1.x release
I hope this will help you.

Related

EmberJS "detail" page fetches the model automatically - is this normal?

Long time reader - first time questioner here.
I am developing a medium sized application in EmberJS which is a framework that I have been using for a while.
Today I have realised that it fetches model data from the server without me writing any code.
I have a main route called "students". Then there is a "list" sub route where the model() function of this route calls the store to fetch all the students and lists them on a table.
On each row of this table I link to another sub route called "detail" where it accepts the ID of each student as an argument. However inside the route.js file for this route there is no model() function querying any information about the specific student from the server.
Ember does this automatically somehow as I can see the appropriate network request being made using chrome dev tools.
How is this happening and is it normal?
Thank you in advance.
The Ember router will automatically load a model if it detects a dynamic segment that ends in _id.
Ember follows the convention of :model-name_id for two reasons. The first reason is that Routes know how to fetch the right model by default, if you follow the convention
You mentioned that your api route is /api/student/details/:student_id and I would expect that your ember route is fairly similar.
It detected _id, and called store.find('student', params.student_id) automatically for you when you navigated to that route.
This is completely normal, and is one of the ways Ember encourages you to follow convention - If you do, you don't have to create as much boilerplate.
If you want to avoid the second request, possibly because the list route pulls all relevant data, you can pass the student model instead of the student id.

preventing a duplicate request in Ember Data when trying to set the route model to the result of an association

I have a route where I need to fetch associated data that is not available in the parent routes, so I need to basically reload the model and in the process provide JSONAPI with the include directive to embed other models. So the route looks like this.
import Ember from 'ember';
// route for patients/1/appointments
export default Ember.Route.extend({
model: function() {
const query = {
id: this.modelFor('patient').get('id'),
include: 'appointments,appointments.practitioner'
},
success = function(patient) {
return patient.get('appointments');
};
return this.store.queryRecord('patient', query).then(success);
}
});
The success callback is fetching the appointments a second time, which maybe isn't surprising, but it should seemingly also know that it has those in the store locally. So, I'm trying to resolve a reasonable way to set the model to the set of appointment models coming back. For various reasons, we don't want the logic of this specific request to live in the adapters, since (for example) we may not always need the practitioner side loaded anytime we get the patient's appointments. Any ideas?
I think that there must be some issue with your response from server after first queryRecord.
I created simple Ember Twiddle that matches your example and is based on mockjax here. As you can see when you open Console, the mockjax is getting only two requests - first for /patient/1 and second for /patients?... (your queryRecord).
Even though I am rendering all appointments that are relationship to current patient, mockjax does not get any other query for relationships. You can check out the JSON responses I provided for mockjax. You should compare them with your API to see if there are different in structure.

Url not updating when new model is saved

router.js
this.route('claim', function() {
this.route('new');
this.route('edit', { path: '/:claim_id' });
});
claim.new -> redirect to claim.edit with an empty model. this.transitionTo('claim.edit', model);
Initially, because the model is not saved we get: index.html#/claim/null
When we save the model, it continues to display as index.html#/claim/null instead of say index.html#/claim/87
Is there a way to force the url to refresh?
The only way to force the URL to refresh is to force the route/page to refresh. You can use the refresh method in your route to do that.
To understand why it doesn't update automatically, you have to understand how routes work and how Ember is separate from Ember Data (or any data persistence library).
First, you have to remember that Ember has no knowledge of Ember Data and the specifics of its object model. It just sees plain Ember objects. Ember Data models are more important than other models to you, but Ember isn't aware of that distinction.
Second, the routing in Ember is binding/observer aware. It doesn't watch for changes and update URL state accordingly. It calculates the URL once using the serializer hook, then it leaves it as that. It's not going to recalculate if your model changes in any way. It will only recalculate when it's refreshed.
Short story long, the part of your application that changes the ID of your model and the part that calculates the URL to use aren't aware of each other. You'll have to manually sync them up. Using the refresh method is probably easiest way to do that.
UPDATE: As Kitler pointed out, you can also transition to your route using the new model. It won't fire the model hook so it won't have to do any asynchronous work, but it will still update the URL.

Reuse ember controller for two routes, but pre populate part of model on one of the routes

Lets say we were modelling repositories in an app. We would want to be able to see all of our repo's at /repos/.
We can create a repo at /repos/create. In our model a repo has a owner (user), going down this route I would want my first form field in my create.hbs template to be selecting a user.
But it may also make sense to create a repo through viewing a user at /users/userId/repos, and then perhaps the route being /users/userId/repos/create. The difference here would be that the first field of my form would have the user pre populated.
I know that I can reuse the same controller by specifying this in my route:
controllerName: 'repos/create'
(what would the ember generate statement/ the place to put this route in my folder structure be, for this complex route?)
But how would I pass in the user to the repos/create controller?
The comment by #Artych provided the best way to do this currently, so to expand on this.
In the end I created a repo-create component that may or may not be passed a user object. The logic for handling what to pre-populate can then be set in the logic of the component itself.
After this is was simply a case of having a one line template at both /repos/create.hbs and /user/repos/create.hbs, with the component.
I then followed the approach of using my user as my model in the routes/user/repos/create.js route and passing it to the component in my template as
{{repo-create user=model}}
With this approach I avoided doing anything explicitly on controllers, and moving up to Ember 2.x will be alot less painful.

Namespacing, sharing models, reusing controller in Ember

I have an Ember 1.10 application with api support for 2 models, Students and Parents, and basic CRUD setup for each model, as well as the ability to read and write messages to/from Students and Parents when visiting their respective /show pages.
I must create an Inbox in which the messages as well as all the other data on Students and Parents is available and must do so without the ability to change or add to the api (i.e. I can' create a Messages endpoint).
Basically, I need to keep the Student and Parent models and controllers as they are, but need to be able to access them from routes namespaced under the Inbox like so:
Students
-inbox
-index
-edit
-show
Parents
-inbox
Inbox
StudentInbox <-- should use StudentsRoute and StudentsController
-index
-show
-edit
ParentInbox
-index
-show
-edit
So that visiting /inbox/students/123 will be using the model defined in the StudentRoute and the actions and properties defined on the StudentController. The InboxStudentsShowRoute should similarly have access to the StudentsShowRoute and Controller.
Instead of copying and pasting the existing code from the Student and Parent routes and controllers, is there a way to proxy actions fired from templates under the Inbox namespace to the relevant models and controllers and maintain the ability to pass in params.
For example, passing in params like ?unread=true while on the InboxStudentsShow route would pass these to the StudentsShowRoute and Controller (and bubble appropriately to it's parent route and controller) or editing a Student from student/inbox/123/edit template would work just as it does on the student/123/edit template?
New to Ember and know there has to be a better way to reuse existing code and proxy actions from a namespace. Any advice would be greatly appreciated!
I'd go here with creating a Mixin which holds same code for Route and specify controllerName, templateName in InboxStudentsShowRoute. Then, you extend from this Mixin in both routes. Model, template, actions, properties will be the same.
For example, passing in params like ?unread=true while on the
InboxStudentsShow route would pass these to the StudentsShowRoute and
Controller (and bubble appropriately to it's parent route and
controller)
I'm not sure what do you mean here by pass these to the StudentsShowRoute and Controller, but CONTROLLER properties changed in one route should remain the same in another route, because controllers are singletons in Ember. I don't know what do you mean by passing query param to another route, because if you transition to other part of your app you change URL and you can't have 2 urls at the same time, so how could query params be passed to another route?
In Ember 1.10 query params are defined in Controllers so I guess behaviour will remain the same.
Example of InboxStudentsShowRoute(move code from StudentsShowRoute to StudentsShowMixin, define controller and template name):
import Ember from 'ember';
import StudentsShowMixin from '../mixins/students-show';
export default Ember.Route.extend(StudentsShowMixin, {
controllerName: 'students/show',
templateName: 'students/show'
});