Component to be notified on route change in EmberJS 2 - ember.js

My task is to develop a menu component for an Ember 2 app. This is going to be a complex component whose visual representation changes as the user goes through routes. For instance it should disable particular menu and all its items when on "/index" route but enable it and some of its items when on "/details" and so on.
So, I've got a component that is passed a singleton model (stored in a Service currently, btw, is it a right place to store globally available singleton models in Ember?). It displays the stuff well but it does not respect the current route nor catches the route changes as user goes through the app. How can I achieve it?
Summing it up:
The component needs to get current route somehow to be able to display its initial state, for instance the user bookmarked the "/details" page and visited it.
The component has to deal with route changes somehow.
Is a Service a good place to hold a singleton model (which could potentially be fetched from server).
Can you provide your thoughts on how to tackle the three above?
SOLVED: Ok, here's how it is done, thanks to #Bek's suggestions:
import Ember from "ember";
export default Ember.Component.extend({
router: Ember.inject.service("-routing"),
didInsertElement: function() {
let r = this.get("router");
console.log("Initial route", r.get("currentRouteName"));
r.addObserver("currentRouteName", this, "currentRouteNameChanged");
},
"currentRouteNameChanged": function(router, propertyName) {
console.log(router.get("currentRouteName"));
}
});
MORE QUESTIONS :) - I had to surround the currentRouteNameChanged function name with quotes (to make it a string) otherwise it was not called. I assume I miss something very basic and obvious here?
One more issue is the funky service name -routing - #Bek, any hints on how could I figure it out myself, is there a list of injectable stuff I could look up information in? It is not yet in Ember documentation I assume but where in the source code of it to check it out? How stable -routing name in general, would it become *routing or something in final version?

Answer to 1 and 2:
In latest versions of ember 2.x (in 2.2 at least) router is available as service so you can inject it to component router: Ember.inject.service('-routing') and observe changes on currentRouteName, but it is currently private service so should be used with caution as it might change (might be renamed to routing), there is also rfc https://github.com/emberjs/rfcs/pull/38 which proposes routable components which will be part of ember in the future.
Anser to 3:
Services usually stateless, but there can be exceptions and services made to share global logic/objects so it is not a bad idea

Related

Ember: Re-render a route's template when the model changes

I've noticed that a route doesn't re-render when the model changes (i.e. transitionTo the same route with a different model). I have some jQuery plugins set up on a particular page and I need them to re-render when the model changes, so it appears as a fresh page.
Is there a way to do this? Perhaps by observing the model's ID and firing a re-render of the route somehow?
Thanks in advance
I have an ember twiddle that, I believe, does what you're looking for, but first I would like to argue there are no straightforward ways to do what you're asking because it is the opposite of what an SPA is designed to do.
Data binding (without refreshing the view) is typically a boon of an SPA, and the SPA works hard to avoid brute force reloading/refreshing/rerendering the view at all costs. It took me a while to find a solution to your question as it is stated because it seems to go against Ember design principles. Even hooks like route refresh() are meant to update the model and bound data, not reload the template.
Although other people have asked the same question before, it seems that most answers guide users towards not refreshing the whole view. More often than not, the ideal of refreshing the view is an incorrect assumption.
Following previous examples, I would suggest that your goal shouldn't be to refresh the template completely, but rather, figure out how you can make your jQuery plugin better fit in to a Single Page App/client-side JS friendly design and have it reload as a natural part of the route lifecycle.
For instance, maybe the plugin can be reloaded/reset/re-run in afterModel() or somewhere similar.
That said, I was able to accomplish what you asked for using (in my opinion, a hack) Ember.run.later() so that I could invalidate an if block and force the content to rerender. Note, this is typically not what users want since (aside from design principle reasons) it causes UI flicker.
I have a component like so.
/* will-rerender.hbs */
{{#if show}}
{{yield}}
{{/if}}
And it has a JS file like so.
/* will-rerender.js */
import Ember from 'ember';
export default Ember.Component.extend({
show: false,
didReceiveAttrs() {
this._super(...arguments);
/*
Ugly hack, but we need to reset `show` in
a separate run loop in order to force the view
to rerender.
*/
this.set('show', false);
Ember.run.later(() => {
this.set('show', true);
});
}
});
You can invoke it like this...
/* your template */
{{#will-rerender cacheKey=model.id}}
... your content to rerender ...
{{/will-rerender}}
Whenever the model.id changes the component will invoke didReceiveAttrs() causing show to invalidate and the view to refresh.
As an aside, I think the behavior of switching between models would be much more natural with {{link-to}} rather than calling transitionTo yourself.

When should I use controllers vs routes in EmberJS?

I know this question might seem a little duplicate but the other version of this question is old and some of the content (such as Views) aren't even a part of ember anymore.
I'm about 4 weeks into my internship as a front-end developer working with EmberJS. I still don't understand when it's better to use the route over the controller or vice-versa. It seems to me that every action in the route can also be used in the controller.
The one recent thing I heard was that ember routes should be stateless where as controllers should be stateful.
What is the current state of controllers and routes. When should one be used over the other?
Consider the following example to understand the state of a controller (or route, or anything), in simple terms and in current context -- lets say you have a page (like a form) with three tabs; each tab can be considered as a state - it would call different components based on the state (or the tab you are in). Now if you would happen to go back for some reason, and hit the form link again, you would see that the state would remain the same. (if you were on tab 2 when you hit back, on returning to the form, you would still be on tab 2).
So to maintain these states, controllers are the way to go, since they are singletons. Route would have lost that information, and started fresh. So basically your variables/objects in a controller would define the 'state'.
Route-actions can be as easily used as controller actions- see https://github.com/DockYard/ember-route-action-helper. So if your template for this route is just using model as the object directly, and you don't need to maintain the 'state', you can pretty much do without your controller.
But if your template was using variables which needed manipulation, you would need controller.
Hope this helps!

Ember 2 - Transition to route through component

I am trying to make a pagination component for ember 2 app.
Since it is a general purpose pagination component, I wanted to use it on many different routes. Because of that I need to somehow (in my mind) provide a name of my route for pagination links to work.
I can pass in my route name (string) to the component easily:
{{pagination-component myRoute='myCurrentRouteName'}}. And
inside of component: {{link-to myRoute (query-params page=1)}}
It works well for going to pages like: << First, < Previous, Next > , Last >>.
But I also wanted to have a select box with options pointing to all the pages, where if user selects a page, I can transition into that route with queryParams similar to this: myRoute?page=selectedPage.
All the tutorials for ember say that transitioning within the component is a no-no.
But how do I do it instead, given that I want my pagination to be generic and I don't want to have action within every single route that deals with pagination and provide same exact transitioning?
So far I found that I can inject the '-routing' into the component, which can work for transitioning within the component, but it also doesn't quiet work for some reason. Plus people say that it is private and unreliable.
I also tried making a Route Mixin with action so I can simply sendAction from the component with the selectedPage, but I don't know how to get the router (in order to call router.transitionTo) within my Mixin.
We should use public API for transitioning, that is provided in Route transitionTo and in Controller transitionToRoute. this is the recommended approach.
As you said, you can make it work in the component using Ember.getOwner(this).lookup('router:main').transitionTo('myRouteName') but that's strongly against good design.
You can define method named myTransitionToUrl inside actions hash of the Application route, and install ember-route-action-helper addon to call route action method myTransitionToUrl from component.
If you don't want to define method in application route, then you can define it in mixin and implement it in the required route. that's also will work.
My choice and Recommended approach would be, Say pagination-component has prev,next,transtionTo actions, then I will implement all those actions in Mixin and extend it in all route which uses pagination-component. and will inform any data changes to route through actions from component. that will ensure Data Down Actions Up strategy too.
If you are just transitioning to a different route, then the Latest possible way is,
If ember version is greater than 2.15, then you can inject RouterService and and call transitionTo method directly.
If you are using the ember version 2.15 or below, then you can use the router service polyfill to use RouterService.
import Ember from 'ember';
export default Ember.Component.extend({
router: Ember.inject.service(),
actions: {
next() {
this.get('router').transitionTo('other.route');
}
}
});

Ember Data and Northwind OData

Could Ember-Data b3+ be used for work against a service like this, http://services.odata.org/V2/Northwind/Northwind.svc. If so, could anyone provide an example on how to use it to read OData.
For example a JSBin showing a list of customers where a customer list item can navigate to the orders of a clicked customer
I think this would be a great boon for developers working against different data protocols to wrap their heads around how to wire up an Ember application with Ember-Data.
I've done this with fixtures but just couldn't really wire it up with actual server data.
Edit
Since I wrote this, I have abandoned Ember and fell back to angular for my SPA data apps. The main reason behind this is Ember Set which you should use for Ember to wire up all its binding internals.
Unfortunately, this is not compatible with most libs like Jaydata or Breeze. At least not when you wish to make changes/saves to your entity service.
Jaydata and Breeze both use a propertyChanged property to monitor changes to your entity and Ember will prevent these changes with a You should use Ember Set error.
I could probably have written some sort of adapter to overcome this problem but really I didn't have time and I use a library "Ember" to make my life easier... not to have headaches on basics such as Data Service Queries.
So... I really love Ember, but unfortunately as long as they dont enhance "Ember Data" or drastically change the Ember Set policy, I can't use it!
Basically, if you plan to use a data library (JayData, Breeze) to update a backend...
DON'T USE EMBER!
Original
I had a look (very quickly!) at ember-data and wasnt thrilled really! It looks promising for Standard REST service which IMHO is not WCF's case.
I ended up using JayData for that purpose and I must say it integrates very well with Ember.
here is a quick snippet to get you going:
//Instanciate the Ember APP
App = Ember.Application.create();
//Tell the APP to initialize but to wait before launching
App.deferReadiness();
//Setup your JayData Store
//Entities.EntityModel was generated by JaySvcUtil
App.myStore = new Entities.EntityModel({
name: 'oData',
oDataServiceHost: <YOUR_WCF_ENDPOINT_URL>
});
//Create your route and populate model data
App.IndexRoute = Ember.Route.extend({
model: function () {
//This passes the toArray() promise to the model
return App.myStore.People.orderBy('it.Name').toArray();
}
});
//When JayData Store is ready, Fire the App
App.myStore.onReady(function () {
App.advanceReadiness();
});
Ember Route Model actually handles the promise given by JayData which allows us to just pass the query. see: http://emberjs.com/guides/routing/asynchronous-routing/#toc_the-router-pauses-for-promises

accessing the model from the template

Playing around with ember, I found that sometimes the model is stored on the controller's content property, sometimes the model is directly available on the controller as well. I do not understand however, when this is the case.
Let me explain it by an example which I found when assembling my ember MVC.
Setup A - The start
I defined a custom Member object, corresponding MemberRoute, MemberView classes and a template with the name member.
The Member object had some attributes such as id, nickname, etc.
NOTE: no controller of the form MemberController was defined, thus by ember's convention, it provides the controller on its own.
Setup B - The customization
Same as setup A, but now there is a MemberController defined that contains some action methods that are triggered from within the template.
The strange behaviour (resp. what I do not completely understand)
in setup A, I can refer to the Member's attributes directly with {{id}} or {{nickname}}.
in setup B, I have to use {{content.id}} or {{content.nickname}}
As documented in ember's documentation, MemberView does
setupController : function(controller, member) {
controller.set('content', member);
},
So, could somebody help me to understand why the difference and where the difference is? Currently, my guess would be either
that the context of the template is different (possibly there is a code piece missing in the setup of the controller?)
or
the default controller that is provided by ember automatically, has some additional magic that is not directly avaiable for customized controllers.
Any help to understand this is highly appreciated. It already took my quite a while to come as far as this. I first thought it could be the modularization introduced by the project setup with requireJS (well, I still think that could have a influence). Ember is v1.0pre4.
Thanks in advance!
Patrick
So, could somebody help me to understand why the difference and where the difference is? Currently, my guess would be either
that the context of the template is different (possibly there is a code piece missing in the setup of the controller?)
or
the default controller that is provided by ember automatically, has some additional magic that is not directly avaiable for customized controllers.
It's hard to say for sure without seeing your code, but my best guess is that your MemberController extends Ember.Controller. The default provided by ember (in this scenario) would have been an Ember.ObjectController. If that's what you want, change your MemberController definition to:
App.MemberController = Ember.ObjectController.extend({
myProperty: 'value'
});
An objectController acts as a proxy to it's content property, typically that is an ember model. So if things are wired up correctly you should never need to access a model via the 'content` property. If you ever see something like:
{{content.id}} or {{content.nickname}}
it's a sign that you should change to an ObjectController. See EMBER GUIDES: REPRESENTING A SINGLE MODEL! for a more detailed explanation.
an ObjectController acts as proxy to the object set to the controller's content. When no controller is defined, Ember will create a controller for you and set its content by default to whatever object is returned by the model() function, if defined, in the route. The behaviour should be the same whether you define your own controller or let Ember define one for you
The default context in the template is the controller itself i.e. this = an instance of your controller or the generated one. When you try to access nickname in that context, Ember will first try to resolve it against the controller itself and if nothing is found, it resolves it against its content, i.e the object if you already manually set it to the controller's content.
Finally, there is no default implementation of the model() function in the Route except when you're using dynamic urls, say /foo/id that resolves against /foo/:id, Ember uses the id provided to load a Foo object with the id provided, thus providing a default implementation to the model() function. At the end it boils down to the same mechanism, only automated for your convenience.
I suggest you listen to this for more insights on how things are automated for you by Ember. But when it comes to the content being displayed, there is no magic you have to manually wire the content of the controller.