Today we'll see a solution to the previous exercise, as well as a few resources to help prepare you for the upcoming drips.

Solution

In the last exercise you were tasked with creating an app that has a component which displays an attribute that you can modify and whose changes will be persisted when you navigate away from the component. This was a pretty open ended task and there are many ways to solve the issue. Let's look at one possible solution.

First, we'll need an Ember app.

ember new exercise-002-5
cd exercise-002-5

Generate a couple of routes and a component:

ember g route index && ember g route about
ember g component example-component

Add our component and a header to the index template (app/templates/index.hbs):

<h1>Index Route</h1>
{{example-component}}

Add a header to our about template (app/templates/about.hbs):

<h1>About Route</h1>

Add a few link-to helpers in the application template (app/templates/application.hbs):

{{link-to 'Index' 'index'}}
<br>
{{link-to 'About' 'about'}}
<br>
{{outlet}}

Now, let's go ahead and display an attribute in the component's template and bind the value of that attribute to an input helper. In (app/templates/components/example-component.hbs):


{{input value=someAttr}}
<br>
<br>
<strong>someAttr:</strong> {{someAttr}}

At this point if we enter a value in our input in the index page, and navigate to the about page and back to the index page, our value would not persist. So, how do we persist it? We could just pass in the attribute from the controller to the component like this:

{{example-component}}

This binds the value in our component to a value in the controller. Since the controller is a singleton and the value is bound both ways from the controller and the component (two-way data-binding), the value lives on after the component has gone away. But, this isn't really a great way to operate. Two-way data-binding can become awkward and unpredictable, particularly in complex applications. Also, in future versions of Ember (maybe who knows?), when/if glimmer components land, two-way data binding will no longer be the default behavior for attrs passed to components. For now, we can enforce some level of unidirectional data flow by using Ember's unbound helper:

{{example-component someAttr=(unbound someControllerProp)}}

Let's try setting up an action in our willDestroyElement hook on our Component that will send the attribute to be stored on the controller.

willDestroyElement(){
  let attr = this.get('someAttr');
  this.get('setPropOnController')(attr);
}

Then we will pass an action from the controller to the component:

{{example-component someAttr=(unbound someControllerProp) setPropOnController=(action 'persistComponentAttr')}}

Finally, let's create that controller action that we are passing into the component.

ember g controller index

In app/controllers/index.js:

import Ember from 'ember';

export default Ember.Controller.extend({
  actions: {
    persistComponentAttr(prop){
      this.set('someControllerProp', prop);
    }
  }
});

That about does it. For what it's worth, this isn't really the way I would build this (we will learn about services this week), but it should show just what is available and some of the limitations of components. To address some of these limitations, we might turn to services.

Preparatory Miscellanea

This week we are going to be going over services, initializers and dependency injection. Here are some relevant articles, guides, and docs.