Here's the route:
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
closeModal: function () {
alert('asdf');
}
}
});
And the component js code:
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
closeModal: function () {
this.sendAction('closeModal');
}
}
});
What I'd like to do is to (as the code may suggest ;) ) send an action from component to route, so that the route could act upon it. However the code above doesn't work - component properly handles the action, but the sendAction call inside it's method doesn't do anything.
EDIT:
I solved this problem using:
this._controller.send('closeModal'); inside component's action method however this solution doesn't satisfy me. Ember authors are saying that controllers will be removed in ember 2.0, so in my code I don't want to have any references to them. Suggestions?
A component has a isolated context. So it knows nothing about anything (route or controller) outside of the component. In order to send an action from your component to your route, you should pass the route's action to the component in your template like so:
// your template
{{your-component closeModal="closeModal"}}
Now when you call this.sendAction('closeModal') in your component, it will trigger the action given to the component within your template, which in this case is the closeModal action of your route.
For more information, see the docs (http://emberjs.com/api/classes/Ember.Component.html#method_sendAction)
UPDATE August 3 2016
For those who encountered closure actions in newer versions of Ember, you might also make use of such actions here by doing:
// your template
{{your-component closeModal=(action "closeModal")}}
This action helper will point to an action of your controller, in your component you can call this.attrs.closeModal() or this.get('closeModal')() to trigger the action instead of calling sendAction.
The benefit of these actions is that the action might return a value which can be used in the component. In case of a modal this can for example be used to determine if a modal may be closed or not if the closeAction is called, if it returns false for example you might decide to prevent the modal from closing.
As a side note, the closure actions always point to your controller, in order to let it point to a route action, you might take a look at this addon:
https://github.com/DockYard/ember-route-action-helper
Related
I am trying to define a custom action to my Ember component & want to communicate between parent/child.
In my-child.js, I have
actions: {
someCustomAction: function() {
let self = this;
self.sendAction('someCustomAction');
},
}
And I catch the same in my-parent.js as below;
actions: {
someCustomAction: function (){
console.log("Inside someCustomAction....");
}
}
Now, with the above code, control/action does not come to my-parent.js
I have to add "someCustomAction" to the template as below
In my-parent.hbs
{{my-child someCustomAction="someCustomAction"}}
I wanted to know the exact reason for the same. Why does just doing sendAction not automtically work ?
Here's how I would go about it while following the data down, actions up (DDAU) pattern.
First, you pass the action you want to fire in your child using the action helper.
{{my-child someActionName=(action 'parentAction')}}
Then, you retreive the action and fire it from the child component.
this.get('someActionName')(params);
Ember Twiddle Complete Example
This is because components are not contextually aware. So the actions need to be tied together manually. But this also gives you the ability to send different actions on the same component and even additional args as well.
Components are isolated from their surroundings, so any data that the
component needs has to be passed in.
https://guides.emberjs.com/v2.15.0/components/passing-properties-to-a-component/
If the component is truly isolated, actions are as well.
https://guides.emberjs.com/v2.15.0/components/triggering-changes-with-actions/#toc_passing-the-action-to-the-component
I have a component, that's actually a modal dialog.
When I am done with that dialog and press the "Ok" button, I want to stay on the stay page from where I opened that dialog.
Which isn't difficult.
But the problem is that the dialog changes the data (I am getting data through a REST call) so I need to refresh the route that I already am on to reflect the data changes.
Since, I am calling it from a component, I don't have Route so can't call route.refresh().
I tried to get the router:
this.set('router', Ember.getOwner(this).lookup('router:main'));
and did transition to the same page:
_this.get('router').transitionTo('my-route')
But since the route hasn't changed (I only opened a dialog), transitionTo doesn't get triggered!
Is there a way I can force trigger transitionTo or refresh the page that I am on?
Thank you!
First, you can easily get the current route name by injecting the routing service to the component.
Then, you can get the current route instance and apply its refresh method:
// app/components/a-component.js
import Component from "ember-component";
import service from "ember-service/inject";
import getOwner from "ember-owner/get";
export default Component.extend({
routing: service("-routing"),
actions: {
refresh() {
const currentRouteName = this.get("routing.currentRouteName");
const currentRouteInstance = getOwner(this).lookup(`route:${currentRouteName}`);
currentRouteInstance.refresh();
}
}
});
For this, define refreshCurrentRoute method in nearest route or in application route file.
actions:{
refreshCurrentRoute(){
this.refresh();
}
}
From your component, you need to call refreshCurrentRoute action. either you can use ember-route-action-helper or by passing the closure action.
In my index route, I have an observer that fires an action when the user object is set in the session service, as shown below. This works fine- the console logs 'index route observer fired' and the action fires.
routes/index
session: Ember.inject.service(),
sendTheAction: function() {
console.log('index route observer fired');
this.send('checkLicense');
}.observes('session.user'),
actions: {
checkLicense: function() {
if (this.get('session.user.email)) {
//get the user's email and send off an AJAX request.
}
},
}
I also have a logout route which (among other things) sets session.user to an empty object, and then transitions to the login route. I simply use a link-to helper pointing to 'logout' to initiate this.
routes/logout
session: Ember.inject.service(),
actions: {
didTransition: function() {
this.set('session.user', {});
this.transitionTo('login');
}
}
If I am in the index route and I click logout, the transition to logout begins. Then, when session.user is set to {} in the logout route's didTransition hook, the observer in the index route fires. The console logs 'index route observer fired' but then I get a console error saying
Error while processing route: logout Nothing handled the action 'checkLicense'.
I'm not sure how the observer in the previous route can still be fired by a change in the logout route's didTransition hook, as I thought didTransition only fired when the transition was fully resolved.
I think that it is then looking for the action "checkLicense" in the logout route.
Can anyone clarify why that observer still fires, and how to get around this?
The observer still fires and this is the expected behavior. The reason is; routes are SINGLETONs; they are not destroyed upon transitions. So; if you define an observer within a route and watch for values that might be updated by other routes, components, controllers, etc. then your observer will still work!!! So; be careful about using observers within singletons (controllers, services, routest, etc.).
I created a twiddle that shows the exact case you mentioned. The valid question here in fact is "why is the send action within index.js is delegated to the other route?" Here is the answer from the Ember.js api. It says "Sends an action to the router, which will delegate it to the currently active route..." in send method definition. So; the error occurs; because current active route is not index anymore; action is not found!
So; what can you do? Why are throwing an action to yourself within index.js? Just make a normal function call!!! If you still need the action; you can also call the same function from within the action. See the modified twiddle in order to see what I mean.
To sum up; observers within singletons will work no matter what if the dependent key(s) get(s) updated! Hope; it is all clear now. Best Regards.
Edit
Actually, I'm fairly certain that this is because my action creator doesn't return an action and the first property of an action is supposed to be a type and that is therefore undefined...
I have a React/Redux SPA that I want to register page views on with a custom analytics engine (ie, not Google Analytics). I'm trying to register page views.
So I have attempted to do this by setting lifecycle hooks in React to fire a Redux action:
class ConfirmationPage extends Component {
...
componentWillMount() {
this.props.registerPageVisited('confirmation');
}
}
However, I receive a Cannot read property 'type' of undefined error presumably because I am modifying the state via the props. Looking at the stack trace, it brings me to that hook. However, I've tried other hooks such as componentDidMount and even componentWillUnMount and I get the same error.
For context, my action creator is this:
export function registerPageVisited(page) {
DB.child('visits')
.child(store.getState().visit)
.update({ [page]: true });
}
where the DB is a firebase reference.
So, how should I keep track of page views?
Could you provide code where you instantiate ConfirmationPage component?
In the componentWillMount function what you are actually doing is calling registerPageVisited function which should be passed as props which should look something like: <ComponentPage registerPageVisited={registerPageVisited} />
As you are suspecting, since your registerPageVisited does not return action object it is not action creator. If you will make it an action creator, then you should use mapDispatchToProps function to use it like any other props as sen in your example.
An alternate to this is have a different object listen to notifications from the store and update the DB based on the changes it's seeing. That will isolate any metrics collection from your view hierarchy.
You'd have to replicate some logic that determines which React components are getting shown into that object, but you could pull that into a separate class and use it in both places.
If you're using react-router, there might be hooks that fire when the active route changes. However, I haven't investigated that.
I'm using on a project using Angular 2 and #ngrx/store. I also use #ngrx/devtools to debug and help me work with this redux implementation.
I would like bind the angular's router with the ngrx's store. So I'll be able to update current route by dispatching an action.
Context
This is what I've done (it works but this is not what I want) :
// imports
// ....
// #Component
// ....
#RouteConfig([
{ path: '/home', name: 'Home', component: HomeComponent, useAsDefault: true },
{ path: '/medias', name: 'Medias', component: MediasComponent }
])
export class MainComponent {
constructor (private _router:Router, private _store:Store<AppStore>) {
_router.subscribe(url => _store.dispatch(changeUrl(url)));
}
}
changeUrl is an action creator.
So when Angular detects a route update an action is dispatched and my state is updating.
And it works well ! Using #ngrx/devtools, I can see the actions correctly dispatched, the store is updated but.. This is the opposite of what I want because angular do the job and update redux. So if I rollback an action, my view is not updated.
It's simple !
"All what I have to do is to dispatch an action when click on a nav link inside the action creator, redirect using Angular's methods."
Yep. But no, I think it's not a good way to go because of :
<a [routerLink]="routeName">Link</a> provided by Angular becomes useless.
It seems difficult to use Angular's DI to inject the Router inside my actions creator because actions creators are basically functions. Not angular's components.
What to do ?
This is the question. I don't really know how to bind Angular's router & #ngrx/store correctly.