Lesson 8

# Creating the profile page

25

⚠️ This tutorial uses an old version of Ember. Some things might have changed!

In this chapter, we'll put together all the knowledge that we have gained about templating, routes, components and Ember Data by creating the profile page!

## Templating with Handlebars and CSS

Let's start by creating the basic markup and style that we need based on our mockups. We'll use only static data for now.

For the HTML-page, we'll just add some layout-tags and add an aside-part with the user info.

app/user/template.hbs

Let's fix it by sprinkling on some CSS magic. Create a new file called profile.scss in your app/styles-folder:

app/styles/profile.scss
app/styles/app.scss

Alright, that was just the basic HTML/CSS stuff. Now, on to the real fun: the Ember part.

Just like in "home" we want the user's data to appear on the profile page.

First of all, looking at our mockups, we can see that we lack two attributes in our user-model:

2. The date the user joined the service

Let's add these! We'll also add a chirps-attribute as a relationship that references the ids of the user's chirps, so that we can easily access them from the user object.

app/user/model.js

Of course, we also need to update our mock data in that case:

app/mirage/fixtures/users.js

Okay, now we have the data there. Remember what we need to do in order to fetch the data? That's right, update the route-file!

app/user/route.js

Next, we update the Handlebars-file to render the data in our template.

Try to do this on your own first, and peek at the code below if you get stuck (note: we only show the parts of the file that need to be updated).

app/user/template.hbs
app/user/template.hbs

Let's move on to our subroutes: chirps, following and followers.

Let's start with the data that we want to load in the chirps-subroute. Here we can do something nifty. Since we can already access the user's chirps through the parent route's model (with model.user.chirps), we'll just use a method called modelFor() to fetch the user route's model, and then get the chirps from there:

app/user/index/route.js

Going back to the browser, you'll now see, uh-oh, an error message again!

As you can see, our GET-requests for fetching specific chirps haven't been defined in the mirage/config.js-file yet, which makes the app crash. No worries, just like we did with our user, we add this line of code to give Mirage the ability to fetch a specific chirp record:

app/mirage/config.js

Back to our template. Remember that we made a chirps-list-component for the Home-page before? One of the great things with components is that they are reusable. And since the list of chirps on the profile-page looks exactly the same as the list of chirps on the home-page, it makes sense to just use the same component to render both!

app/user/index/template.hbs

Here we truly see the power of components. We only had to pass in our new route-data to the component, and the logic, markup and styling just comes for free thanks to our earlier work!

Let's move on to the following and followers subroutes.

## Followees and followers

Right now in our Fixture data, we actually don't have anything for linking up a user's followers and followees. Let's add that.

Remember: a user can both follow many users, and be followed by many users. Therefore, we need two hasMany()-relationships in our user model.

app/user/model.js

Since we only have one user right now, our mock data user will only follow (and be followed by) himself. Notice that when using hasMany() we have to use an array with the user-IDs.

app/mirage/fixtures/users.js

Back to the browser. Oh no. Blank page. What happened? Let's check the console.

The inverse relationship between our attributes in this case is pretty simple: the inverse of a follower is a followee, and the inverse of a followee is a follower:

app/user/model.js

That fixed the error! Now we need to update our two subroutes to fetch that data.

Right now, we'll just make these routes fetch all the users we have in our fixture data (without filtering). Don't worry, we'll improve this once we build our real REST API. The goal at the moment is just to make sure that the templates and styles render well.

app/user/following/route.js
app/users/followers/route.js

Since we're using findAll(), we also need to add the corresponding REST URL to our Mirage config-file:

app/mirage/config.js

And finally we update the route's template. Again, we'll use a component that we've already spent some time creating: the profile-glance-component.

app/user/followers/template.hbs
app/user/following/template.hbs

## Final touches with computed properties

One problem we still have with our current user model is that there's no relation between the chirps/followers/following and the number of them that we see in the profile glance.

For example, in our fixture data, we say that our user has 5 followers (in numberOfFollowers), although the actual relationship-data only contains one single user-ID (in followers).

Wouldn't it be great if our numberOfFollowers-property was automatically deduced from the length of the followers array? Well, once again, we can easily achieve that through computed properties.

With computed properties, you can create new attributes for your model based on one or many of the other attributes your model has. So let's replace the attributes numberOfFollowers and numberOfFollowing with computed properties!

app/user/model.js

Also, since we're using the Ember-keyword, we need to import the Ember library at the top of the file with:

The first argument of Ember.computed() tells Ember what property to observe in order to calculate the computed property. In this case for example, we've decided that as soon as followers changes, numberOfFollowers should change as well.

We can actually do the same thing for our chirps attribute! Since we already have a relationship between our user model and the chirp model, all we need is a computed property there as well.

Your final model-file should now look something like this:

app/user/model.js

As you can see, we don't have any raw numbers anymore, everything is computed on the fly on the client side.

This has some great advantages. Let's look at what would happen if we were to post a new chirp for example. We'll simulate it by updating the fixture data for our chirp model:

app/mirage/fixtures/chirps.js

Also make sure that you reference that record (with ID 3) in your user fixture:

app/mirage/fixtures/users.js

You now see all three chirps in the user's profile and, lo and behold, the number of chirps has automatically been updated to 3!

It is important to note that, even though the whole page was refreshed to show the changes in this particular example (because we're using fixture data), this kind of change will be instant and reflected in your UI as soon as the model data changes.

It's really a great feeling not to have to worry about updating all your data in the UI manually as soon as some data changes!

Great, we now have two pages that look pretty good. In the next chapter we're going to take a closer look at how to import different kinds of third-party libraries and use them in our Ember app.

At this point, I got the error "Error while processing route: user.index Assertion Failed: You looked up the 'chirps' relationship on a 'user' with id 1 but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (DS.hasMany({ async: true }) )", which was fixed by amending app/user/model.js as follows: chirps: DS.hasMany('chirp',{async:true})

@corinnebrady: Thanks! Without your comment finding a solution would have been much more painful. I think the author should update the tutorial to reflect this.

I also ran into this issue and the author really should include this into the tutorial or else reading the comments becomes *required* to proceed.

@stuartquinn: I got this error as well, just add {async:true} to hasMany in user/model.js as discussed in earlier comments

@Chris @ckmorris Thanks for the input guys. I've updated that part of the lesson now. The reason I left out the { async: true }-part is because it's the new default in the stable version of Ember 2.0. However, since there's still a gap between the releases of Ember CLI and the releases of Ember.js (the Ember team should really work on that), it might still be necessary to include it since it's not the default in 1.13. Thanks again for the feedback!

Image appears to be on the home page, according to the link ... but it is definitely the profile page. Also, on the web site the dates are rendering in the long machine format. You may know the latter already!

@danieljeffery: You're right. This image was taken directly from Sketch, where the header stays the same whatever the design is. So I just copied that because I'm lazy. :) But yeah, it should be fixed to avoid confusion.

For some strange reason, ember doesn't 'recognize' the new profile.scss, even though i have added it to app.scss. I have restarted the app, delete and create again profile.scss, but nothing, the profile page is rendered without any css styling.

I was struggling to understand why you needed an inverse relationship, and the fact it comes from the same model and refers to the other field makes it even more confusing. After reading this: http://stackoverflow.com/a/25700619/256272

it became clearer if you imagine two user objects, where one has the followers and the other has the followees field and you're drawing that edge between the two users.

Curious as to why it isn't making a single request to /chirps rather than multiple requests to /chirps/:id?

Actually I think I can figure it out ... it's going to see an array [1,2] etc, and so it is going to make individual requests.

If you had an array with fifty different entries, would it still make fifty requests for the information?

I'm on version 2.5 of Ember and when I got to this step I did not get the error messages shown here (/api/chirps/1). I feel I have followed all the step correctly because I have recheck all of them. Do you know if there are changes to Ember Data in 2.5 that has affected this example?

If I put this in as you have it here, I get the following error... "Uncaught TypeError: items.slice is not a function". But, if I try to enter it the same as the home template, I get the else block and my chirps don't appear?

File name app/users ... should be app/user!

Is there a benefit to these methods vs. just calling {{model.user.followees.length}} in the user/template.hbs ?

Something I saw a few days back ...

this.get('followees').get('length');

can be ...

this.get('followees.length');

Which I think is a little neater, simpler!

@timherpderp: In this case, you're right, it would work the same. :)

One could imagine that in a more optimized version of the app, you would want to send out numberOfFollowing and numberOfFollowers as values from the backend instead of dynamically generating them on the frontend (for performance reasons). In that case, it might be good to have them as separate properties.

But in this example, it does not actually matter as you pointed out!

Actually I don't think you do?

I didn't

Turns our it's actually not necessary since it's stored as a global variable. Imo, it's still a good practice to be explicit though. :)

And here??

Although it looks like you put a curly brace instead of a square bracket! :P

Just a reminder, this is in the wrong place eh?

Should this be in the app/mirage/fixtures/users.js file, and without the 'user' key?

(yeah, ping on this)

@kirkisrael: @danieljeffery: Yup, thanks guys. I had forgotten to click the "Publish"-button on this chapter. :P

:)

I got an unexpected token error and had to remove "user:" to make it work.

I noted that also, it wasn't there in the previous chapter so I wasn't sure if it was meant to be there

@danieljeffery: @varunsudhakar3: You're absolutely right. Thanks for the heads up. I fixed it!

The chirps line needs to be chirps: DS.hasMany('chirp',{ async: true }) -- or else

"Either make sure they are all loaded together with the parent record, or specify that the relationship is async (DS.hasMany({ async: true }) ) " happens in the next steps

@kirkisrael: @paridin: Actually, if you're using Ember.js 2.0 or higher, you can leave out the { async: true } since it's the default option. However, if you're on 1.13.X you will still need to specify it. :)

After looking for the why it's necessary add {async: true}, I found this async-relationships, and it says, that is because since version of ember 1.18 you will need to be explicit with this param.

Hm. On this step I see &lt;DS.PromiseManyArray:ember558&gt; intstead of number of chirps

What is this reference? "// app/user/index/route.js" I dont see that anywhere. If I try to update the "app/user/route.js" file it breaks.

@williamfleming: That's the default subroute for the user page where the chirps are displayed. It should have been created in the chapter "Routes" through "ember g route user/index".

Oi vey! For some reason I didn't run that. Ouch on the oversight! Thank you.

Also doesn't work. I see blank area where the data's should be

I tried this

return Ember.RSVP.hash({

chirps: this.modelFor('user').user.get('chirps')

});

following the same logic as in home/route.js and modified the 'user/index/template' to use {{chirp-list chirps=chirps}} but it's not working?

When should one use RSVP.hash?

Is this missing "import Ember from 'ember';"?

@tomkay: Yes you're absolutely right. I forgot it even though I specifically mentioned it a few paragraphs higher up, lol.

Is there a reason {async: true} was taken out from followees and followers? Up the page they are included here they are not? :)

@danieljeffery: Nope, seems to be an error on my part. Although it's worth noting that in the next Ember version (stable 2.0), { async: true } will be the default!

Which is due in very soon!

So this says 'update the route-file' here, but doesn't the route file stay the same as it is in the last chapter?

@danieljeffery: This is the "user"-page's route file. The one in the previous chapter was for "home"!

Right you are sir!

What does it mean to be "inverse"? Does that mean the opposite of a follower is a followee?

@edprats: Exactly!

How can we create a more realistic follower/following scenario using fixtures as an example. For instance what if we add one more user to our app/mirage/fixtures/users.js and then show the relationship of following and followers. How can we achieve that?