I moved state properties from controller to service except queryParams. now I would like to have one of the query param property to service.
Is there any way to achieve this without using observer ?.
I am currently doing it using observer.
controllers/application.js
import Ember from 'ember';
const {computed,observer} = Ember;
export default Ember.Controller.extend({
myService:Ember.inject.service(),
appName: 'Ember Twiddle',
changedAppName:observer('appName',function(){
this.get('myService').changeAppName(this.get('appName'));
}),
actions:{
changeApp(){
this.set('appName','NewEmberApp');
}
}
});
templates/application.hbs
<h1>Welcome to {{myService.appName}}</h1>
<br>
<button {{action "changeApp"}}>Change</button>
<br>
{{outlet}}
<br>
<br>
services/my-service.js
import Ember from 'ember';
export default Ember.Service.extend({
appName:'SSS',
changeAppName(param){
this.set('appName',param);
}
});
EmberTwiddle
Why do you need the observer at all? Just set the appName of the service directly inside your changeApp action of the controller: this.set('myService.appName','Kumkanillam');
and remove the observer completely.
Your question became sth. different after your comment. Let me clarify if I understand your question correctly with the following twiddle. In that case; you are modifying appName both at controller and component; which is an illustration of violation of DDAU principle. In this situation you need observer inside the controller in order to be notified about appName change at component level.
In order to solve this problem; you can create an aliased computed property at controller level and pass it to component (or other components or route templates) and you will get rid of the observer. This twiddle explains what I mean.
Even if what I understood is correct and what I provided might be a valid answer; do not forget that violating DDAU will hurt you at some point and you are going to have to refactor your code in future. Hope this helps.
Related
Reading up on the documentation for Ember, I was under the impression that when an action is triggered by a component, it will go up the hierarchy until it hits an action with that name. But here's what's happening right now. I have a game-card component written like so:
game-card.hbs
<div class="flipper">
<div class="front"></div>
<div class="back">
<img {{action "proveImAlive"}} src={{symbol}} />
</div>
</div>
game-card.js
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['flip-container'],
actions: {
//blank for now because testing for bubbling up
}
});
Now according to what I've read, since game-card.js does not have a 'proveImAlive' action, it will try to bubble up the hierarchy i.e. the controller for the particular route.
play.js (the route /play)
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
proveImAlive() {
console.log('Im aliiiiveeee');
}
}
});
But when I finally run my application, I get this error:
Uncaught Error: Assertion Failed: <testground#component:game-card::ember483> had no action handler for: proveImAlive
Now my question is twofold:
Why is this error happening?
I want some of my component's actions to bubble up to the route's controller. For example, when a game-card is clicked, i'd like to send the id value (to be implemented) of that card up to the controller so it can store it on an array.
game-card is clicked --> sends value of 1 --> arrayinController.push(1)
How can I achieve this?
First, I'd like to point out that you linked to the documentation of Ember v1.10.0. You should consult the documentation for the version of Ember you are utilizing, which you mention is v2.8.0.
Now according to what I've read, since game-card.js does not have a 'proveImAlive' action, it will try to bubble up the hierarchy i.e. the controller for the particular route.
This isn't quite what happens because components are isolated, so there is no implicit bubbling. When the Guides say "actions sent from components first go to the template's controller" and "it will bubble to the template's route, and then up the route hierarchy" they mean that you have to explicitly send an action up from the Component. If the component is nested inside another component, you have to do this for each layer, until you reach the Controller.
Why is this error happening?
You need to bind the action in the template: {{game-card proveImAlive="proveImAlive"}}
i'd like to send the id value (to be implemented) of that card up to the controller so it can store it on an array.
I am going to be using closure actions for this part of the answer. As mentioned by #kumkanillam, they have better ergonomics, and they are the current proposed way to use actions if you consult the Guides.
I have prepared a Twiddle for you.
a) Initialize array in the controller
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
gameCards: null,
init() {
this.set('gameCards', []);
}
}
b) Implement the action that pushed to the array
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
gameCards: null,
init() {
this.set('gameCards', []);
},
actions: {
proveImAlive(cardNo) {
this.get('gameCards').pushObject(cardNo);
console.log('Im aliiiiveeee - cardNo', cardNo);
}
}
});
c) Bind the closure action down
{{game-card proveImAlive=(action 'proveImAlive')}}
d) Trigger the action passing the arguments
<div class="flipper">
<div class="front"></div>
<div class="back">
<button {{action proveImAlive 1}}> ProveIamAlive</button>
</div>
</div>
You need to explicitly set the action handler:
{{component-name fooAction=fooHandler}}
This is required because it helps keep components modular and reusable. Implicit links could result in a component triggering unintended behavior.
Your code should work, only if you have included game-card component into play.hbs. I doubt the controller for the particular route is not play in your case.
Here is the working-twiddle
Instead of bubbling actions, use closure actions. For better understanding you can go through the below links,
https://dockyard.com/blog/2015/10/29/ember-best-practice-stop-bubbling-and-use-closure-actions
http://miguelcamba.com/blog/2016/01/24/ember-closure-actions-in-depth/
https://emberigniter.com/send-action-does-not-fire/
I have a component that should bubble an action up to its template router.
I pass the name of the action to the component:
{{project-table projects=model viewProject="viewProject"}}
Inside my component (project-table), I have:
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
viewProject: function (project) {
this.sendAction('viewProject', project);
}
}
});
Inside the component template, I have:
<button type="button" {{action "viewProject" project}}>
My Button
</button>
Last but not least, I have my router:
actions: {
viewProject: function (project) {
this.transitionToRoute('project', project);
}
}
The component's action gets invoked correctly. However from there on, the action does not bubble up. Any ideas as to what I might be going wrong?
#JB2, your code is almost perfect, and I'm sure that the action is bubble up to the Route level.
However, please note, that Route has transitionTo method only. http://emberjs.com/api/classes/Ember.Route.html#method_transitionTo
In a Controller, you can use transitionToRoute method.
http://emberjs.com/api/classes/Ember.Controller.html#method_transitionToRoute
It is easy to mix. :) I check the API doc also quite often, which one could I use and where. :)
I have a component which has a button with an action like
{{action 'create'}}
and inside the action create i wrote like this.transitionTo('page.new');
But i am getting an exception like Cannot read property 'enter' of undefined can anyone answer please?Just want to know is that possible to route from a component?
The way to do that is to use this.sendAction() from your component and bubble it up to the router. The router can then call this.transitionTo().
The way link-to does it is by injecting routing _routing: inject.service('-routing'),
https://github.com/emberjs/ember.js/blob/v2.1.0/packages/ember-routing-views/lib/components/link-to.js#L530
Ember.Component is extended from Ember.View and you cant use this.transitionTo in a view. It can be done only through a controller/router.
If you want a transition inside the component on clicking, you could use the link-to helper, but if you still want to be able to handle that action, read: http://emberjs.com/guides/components/handling-user-interaction-with-actions/ and the guide after it.
I found out the answer it is possible.we can use simply use the following code from our components action
App.Router.router.transitionTo('new route');
and we will get a call back for this,in which we can set the new route's model.Use the following code for that.
App.Router.router.transitionTo('your route here').then(function(newRoute){
newRoute.currentModel.set('property','value');
});
Injection is the last thing you wanna do. The way you communicate actions between routes and components is to use the sendAction Method Send Action
template.hbs
{{your-component action="nameOfYourRouteAction" }}
route.js
export default Ember.Route.extend({
ratesService: Ember.inject.service(),
model() {
//return yourdata
},
actions: {
nameOfYourRouteAction(...args){
this.transitionTo(...args);
}
}
});
in your component.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
toggleTransition: function(...args) {
this.sendAction('action', ...args);
}
}
});
component.hbs
<button {{action "toggleTransition" 'your route'}}>Change Route</button>
How do I transition to a route pragmatically from inside a component action?
I tried to use #get('controller').transitionToRoute('images'), but the controller refers to the component itself. I understand that components should be self contained, so should I be using a view instead to interact with controllers/routes better?
Example
App.ImageEditorComponent = Ember.Component.extend
...
actions:
delete: ->
App.Files.removeObject(object)
#transitionToRoute('images') # This throws an exception
...
You could pass the controller in via a binding and then access it inside your component like so:
{{image-editor currentControllerBinding="controller"}}
App.ImageEditorComponent = Ember.Component.extend
...
actions:
delete: ->
App.Files.removeObject(object)
#get('currentController').transitionToRoute('images')
...
Create action on parent controller.
export default Ember.Controller.extend({
actions: {
transInController() {
this.transitionToRoute('home')
}
}
});
Specify this action on component call.
{{some-component transInComponent=(action "transInController")}}
AFTER v3.4.0 (August 27, 2018)
some-component.js
export default Component.extend({
actions: {
componentAction1() {
this.transInComponent();
}
}
});
OR simpler in some-component.hbs
<button onclick={{#transInComponent}}>GO HOME</button>
BEFORE v3.4.0
Ember.component.sendAction
"Send Action" from component up to controller
export default Ember.Component.extend({
actions: {
componentAction1() {
this.sendAction('transInComponent');
}
}
});
A component is supposed to be isolated from its context, so while you could pass in a reference to the controller, that's probably outside the scope of what a component is for. You might want to just stick with using a view with its own controller instead. Check out Views Over Components - An Ember Refactoring Story.
From Ember.js, Sending Actions from Components to Your Application, there's discussion about sending actions from a component up the route hierarchy.
A lot of things changed in Ember since the original post. So maybe today the best option would be to pass down to the component a route action that takes care of the transition (maybe using the fancy addon ember-cli-route-action.
Otherwise you can create an initializer with ember g initializer router and inside put in there a code like this one
export function initialize (application) {
application.inject('route', 'router', 'router:main')
application.inject('component', 'router', 'router:main')
}
export default {
name: 'router',
initialize
}
This way you can access the router in your component with this.get('router') and, for instance, perform a transition
this.get('router').transitionTo('images')
At component.HBS component file make a
{{#link-to "routeDestinationYouWant" }}
For Example:
<section class="component">
{{#link-to "menu.shops.category.content" "param"}}
<figure id="{{Id}}" class="list-block yellow-bg text-center" {{action "showList" "parameter"}}>
<section class="list-block-icon white-text my-icons {{WebFont}}"></section>
<figcaption class="list-block-title black-text">{{{Title}}}</figcaption>
</figure>
{{/link-to}}
</section>
I've written this answer for another similar question.
If you want to use the router only in a specific component or service or controller, you may try this:
Initialize an attribute with the private service -routing. The - because it's not a public API yet.
router: service('-routing'),
And then inside any action method or other function inside the service or component:
this.get('router').transitionTo(routeName, optionalParams);
Note: It'll be transitionToRoute in a controller.
Link to question: Ember transitionToRoute cleanly in a component without sendAction
Link to answer: https://stackoverflow.com/a/41972854/2968465
this question is slightly related to How to display the “content” of an ObjectController?
However, in the provided solution and all other examples I can find the controllers are always created explicitly. The nice thing about Ember.js is that the Route takes care of mostly everything. So I don't want to create the controller but want to bind it to a view:
{{view App.myview controllerBinding="App.applicationController"}}
You can see the complete example in this fiddle. The example is not that great because Ember usually sets the controller of a child view to its parent view.
In the end I need to know, how I can access a controller which is created by Ember from a view.
Thanks for any help!
Update:
I provided the wrong fiddle or it did not save my changes. Here is the link to the right version: http://jsfiddle.net/ncaZz/1/
What should I provide in line 9 in the templates?
From the view you can access the controller with
this.controller
If you need other controllers than your view controller you can use the needs in the viewcontroller:
App.DatasetEditController = Ember.ObjectController.extend({
needs: ['mappingIndex']
});
and then use:
this.controller.mappingIndex
You don't really need to bind to it. You can access the controller from the view by calling it like this.
this.get('controller');
Updated Answer:
You really should not have your click event inside your view. Your actions should either be in your controller or your router.
Your template should become
<span style="background-color: green" {{action doStuff}}>
Click
</span>
and you should have a controller that should have this
App.MyController = Em.Controller.extend({
needs: ['application'],
doStuff: function(){
this.get('controllers.application').foo();
}
});
Also, the MyView and MyController should be capitalized, because when extending these items from ember that are not instances, and the capitalization is required. The view should only really have stuff in the didInsertElement that handles special things like any kind of jquery animations or initializing a date picker. But, the "ember way" is to have action in the router or controller.