How to reset attributes on a controller


#1

Maybe I’m looking at this the wrong way, but I have a modal dialog backed by a controller and I would like to reset the properties (input values in forms, error messages, etc.) on that controller when the dialog closes. Is there an easy way to do this? Of course I could just use this.setProperties({ … }), but then I’d have to enumerate all the properties and their default values which would mean a lot of duplication. I think there has to be an easy way to do this?

Thanks in advance.


#2

Could you provide a JS Bin or code samples you could show us for how you’ve implemented the modals? There are a few ways to do this, so it will be easier to help you if we have that :smile:


#3

Yes sure. I am basically just using the method described in the EmberJS cookbook.

That is I have

 openDialog: function(name, model) {
   this.render(name, {
     into: 'application',
     outlet: 'dialog',
     model: model
   });
 }

as an action in my ApplicationRoute.

Then in my application template (or anywhere) I have something like {{ action 'openDialog' 'dialog-settings' }}.

The dialog then has a template dialog-settings.hbs and a controller in controller/dialog-settings.js.


#4

I guess I could do something like:

openDialog: function(name, model) {
  var controller = this.controllerFor(name).constructor({ model: model });
  this.render(name, {
    into: 'application',
    outlet: 'dialog',
    model: model,
    controller: controller
  });
}

which just instantiates a new controller every time. This seems to work, but I don’t know whether it’s good style, and moreover I worry that this will end up creating lots of controller objects in memory that are never cleared up.


#5

Controllers are singletons in Ember, so I would stay away from that.

One thing you could do is have a reset method on the controller, and before you render the modal you could do something like:

openDialog(name, routeName, model) {
    let controller = this.controllerFor(routeName);
    
    controller.myResetMethod();
...

I prefer making sure the thing I’m trying to use is ready for me to use it, rather than assume whatever used it last cleared it up properly.

– edit

Sorry I usually render an existing path into my modal, hence the routeName.


#6

Ok, but my problem is exactly the lack of a “myResetMethod”. If I just have to write that one myself, it’s just incredibly fragile. E.g. imagine I add a new input field to my template bound to a new controller variable - now I have to change the “myResetMethod”. Surely there must be a better way?


#7

How would Ember know what it should be resetting? And what values it should assign these things?

Generally what you would do is make sure your controller is setup correctly inside the setupController() hook on your route. And since you’re not using the route, you need to store this setup logic in another location.

The nature of singletons is that a single instance is kept throughout the lifetime of the application, so you would need to manually reset it if that needs to be done.


#8

IMO it should know that based on what I have stated in my controller class. That is, if a property has no default value, it should be undefined, and if it does, it should be that default value.

E.g. if in my controller I specify:

export default Ember.Controller.extend({
  firstName: Ember.computed.oneWay('model.contact.firstName'),
  lastName: Ember.computed.oneWay('model.contact.lastName'),
  address: Ember.computed.oneWay('model.contact.address'),
  ...
});

Then I would want those one-way bindings to be recomputed. And if I have a property “doValidate: false”, then I want it to be false again.


#9

The problem with that approach is still the singleton nature of the controllers. You can re-use controllers from many routes, and those routes could each have a completely different idea of what it means for the controller to be reset.

– edit

But with your specific example regarding the one way binds, all that would be required to reset that controller is to set the controllers model. This is the default behavior of the setupController() hook on the routes. But still, each route that uses the controller could have a different model that it wants to use.


#10

I’m not sure how “each route could have a completely different idea” about what it means to reset the controller, when I speficially stated that I want my controller to be just recreated from scratch.

It’s not an incredibly contrived use case. I have a form in my modal and when I close the modal I want the form to be reset, fields, validation errors and all. There must be an easy way to achieve this without me having to specify all the default values of those fields in two places.

But with your specific example regarding the one way binds, all that would be required to reset that controller is to set the controllers model. This is the default behavior of the setupController() hook on the routes. But still, each route that uses the controller could have a different model that it wants to use.

That doesn’t work. Since it’s a one way binding, the moment the firstName on the controller is changed it’s not going to recompute it. Which is fine as long as the dialog is open, but when I close (or reopen it, it doesn’t matter where that logic goes) it should recompute the binding.


#11

I’m guessing I must be going about this the wrong way and wondering if it would help if I just extracted everything the controller does into a component. After all, my understanding is that controllers are going away anyway.


#12

When I say “each route could have a completely different idea”, look at this example:

  1. Route R1 uses controller C, with template T1. It needs C.someProperty to be set to 1. This is the “reset” state of the controller according to R1.
  2. Route R2 uses controller C, with template T2. It needs C.someProperty to be set to 2. This is the “reset” state of the controller according to R2.
  3. Route R3 uses controller C, with template T3. It needs C.someProperty to be set to 3. This is the “reset” state of the controller according to R3.

Ember is based around it’s routing system. The routes hook up which controller to use with which template and what data. Controllers don’t really mean much by themselves.

But yes, moving everything into a component is the way to go.


#13

Route R1 uses controller C, with template T1. It needs C.someProperty to be set to 1. This is the “reset” state of the controller according to R1. Route R2 uses controller C, with template T2. It needs C.someProperty to be set to 2. This is the “reset” state of the controller according to R2. Route R3 uses controller C, with template T3. It needs C.someProperty to be set to 3. This is the “reset” state of the controller according to R3.

But if my controller reads:

Ember.Controller.extend({ someProperty: 1 })

Then that should be the “reset value”. How can this depend on the route / template?

Ember is based around it’s routing system. The routes hook up which controller to use with which template and what data. Controllers don’t really mean much by themselves.

Well then that’s the problem. The way modal dialogs are built in the cookbook uses controller (without routes), so it seems not to really be in line with how you would build things the Ember way. Which begs the question, what is the right way to do modal dialogs?

But yes, moving everything into a component is the way to go.

But then I’m having trouble to understand how to communicate between that component and the application, since components are completely isolated.

E.g. if I click on the “save” button, I want to save the user which, if my understanding is correct, would best be handled in a route or controller (in this case it probably has to be the controller since the modal dialog doesn’t have its own route). Fine, I could do this by calling this.sendAction() in the component. But then, I want to change the state of the component (e.g. error messages) depending on whether the saving was successful or not, and the way I understand it, there is no way I can get that information back into the component?

I must be missing something here.


#14

FWIW, I have now found an acceptable solution. I just wrapped my whole modal in a component. That is, the actual template file (let’s say templates/dialog-settings.hbs) looks like that:

{{dialog-settings closeAction='removeDialog' update='update' deactivate='deactivate'}}

And then I define the actual contents in templates/components/dialog-settings.hbs.

I keep everything related to internal state (input fields, validations, etc.) in the component, so it’s not persisted when closing the modal, since components are not singletons. Then for every action that changes application state, data, etc., I just do something like:

actions: {
  update: function() {
    this.sendAction('update', this, this.get('password'));
  }
}

and the real update action is handled by the controller (I guess it would be more proper to do it in a route, but with this solution, modal dialogs don’t have routes and I don’t want to throw everything in the application route).

I also pass in a reference to the component itself, so the controller can do something like:

user.save().then(...).catch(function() {
  component.set('error', 'update failed');
}

I think this works quite well and also keeps responsibilities isolated, but it needs some amount of boilerplate (especially passing in attributes and actions to components) which is a minor nuisance. Nevertheless it does solve the problem.

I guess some of this can be handled more nicely when routable components become a thing.