Angular JS – shaping up (level 2)

1 Filters, Directives, and Cleaner Code

http://campus.codeschool.com/courses/shaping-up-with-angularjs/level/2/section/1/video/1

This is probably where things get super confusing and I cry. I know that’s coming up, I just know it. Let’s go over directives we’ve addressed so far:

  • ng-app – attach Application Module to page
    • ng-app=”store”
  • ng-controller – attach Controller function to page
    • ng-controller=”StoreController as store”
  • ng-show and ng-hide – display something based on expression
    • ng-show=”name”
  • ng-repeat – repeat section for each item in array
    • ng-repeat=”product in store.products”

1.1 Filters

So here’s a pipe: | and it’s funny that we call it a pipe and that it looks like a pipe too because we also use it now in a way that’s a lot like how you’d use a pipe: to send something from one place to another. In this case, we have the expression, and whatever the output is from the expression, we can then “pipe” it to another expression to do other things with the output before actually outputting it to the screen. In the case of the tutorial, the issue is that prices aren’t displaying correctly. We don’t want them to show up like this: $2 but rather, like this $2.00.

Here’s the expression currently:

<h2>${{product.price}}</h2>

…and if we send it 2 it’ll output $2.

And now let’s make a pipe and another expression.

<h2>${{product.price | currency}}</h2>

And now it’ll output: $$2.00 We’re getting closer—why is it outputting that wrong? Because we’d written the dollar sign ourselves.

<h2>{{product.price | currency}}</h2>

And now it works well! What’s that currency thing? It’s a filter. So we take the price, we pipe it to the currency filter, and it changes a 2 into a $2.00 — beautiful! Generally, this is how filters work: {{data | filter:options}}. Filters include date, uppercase, lowercase, limitTo (limits number of characters in a string or number of items in an array).

Let’s look closer at limitTo for a second, because this seems pretty cool:

<li ng-repeat="product in store.products | limitTo: 3">

Cool. We can also sort. Here’s ascending price:

<li ng-repeat="product in store.products | orderBy:'price'">

and in descending order:

<li ng-repeat="product in store.products | orderBy:'-price'">

You see that one little difference there? price vs -price controls the asc vs desc order.

1.2 ng-src

Lastly, let’s say we have an image as one of the properties we’re dealing with:

    {
      name: 'Dodecahedron',
      price: 2,
      description: ' . . . ',
      canPurchase: true,
      image: 'http://www.something.com/something.jpg'
    },

If in the HTML we do:

<img src="{{store.product.image}}">

it won’t work because the expression doesn’t evaluate until after the page has loaded. How do we deal with this? Angular has directives we can use for examples where this is the case. In this case, it has ng-src

<img ng-src="{{store.product.image}}">

And now it’ll work.

2 Practice

http://campus.codeschool.com/courses/shaping-up-with-angularjs/level/2/section/1/using-filters

2.1 filters

In the first problem, prices are showing incorrectly, like this: $110.5

So we switch:

<em class="pull-right">${{product.price}}</em>

to

<em class="pull-right">{{product.price | currency}}</em>

2.2 Image Gallery

Second, we have to show an image for each product. Right now, the images look like this:

      images: [
        "images/gem-01.gif",
        "images/gem-03.gif",
        "images/gem-04.gif"
      ],

And the HTML that works:

<img ng-src="{{product.images[0]}}" />

Works!

2.3 ng-repeat

Now they want me to show all the thumbnails using a ng-repeat. This is the code they give me:

<li class="small-image pull-left thumbnail" >
  <img ng-src="{{product.images[0]}}" />
</li>

Hmm…first I try:

<li ng-repeat="{{image in product.images}}" class="small-image pull-left thumbnail" >
  <img ng-src="{{image}}" />
</li>

But it doesn’t work. So I thinks to myself, do I really need those brackets? NO!

<li ng-repeat="image in product.images" class="small-image pull-left thumbnail" >
  <img ng-src="{{image}}" />
</li>

2.4 ng-show

Sometimes, they say, there’s no images, and we don’t want a gallery to show at all. So the initial code is:

<div class="gallery">
<img class="img img-circle img-thumbnail center-block" />
<ul class="clearfix">
<li class="small-image pull-left thumbnail"> <img /> </li>
</ul>
</div>

I did this:

<div class="gallery">

but it’s not right, according to the tutorial…although it seems to be working in practice. Oh okay, so it said I should be using the length property so the correct answer is:

<div class="gallery">

3 Tabs Inside Out

So here’s how you make tabs in Bootstrap:

<section>
  <ul class="nav nav-tabs">
    <li class="nav-item">
      <a class="nav-link" href="#">Description</a></li>
    <li class="nav-item">
      <a class="nav-link" href="#">Specifications</a></li>
    <li class="nav-item">
      <a class="nav-link" href="#">Reviews</a></li>
  </ul>
</section>

3.1 Two-Way Data Binding

Now, using Angular this is how we can set a variable depending on which tab is clicked, which is essentially setting a value to a tab:

<section>
  <ul class="nav nav-tabs">
    <li class="nav-item">
      <a class="nav-link" href="#">Description</a></li>
    <li class="nav-item">
      <a class="nav-link" href="#">Specifications</a></li>
    <li class="nav-item">
      <a class="nav-link" href="#">Reviews</a></li>
  </ul>
</section>
{{tab}}

Here’s the thing—this DOESN’T WORK ON ITS OWN. If you fire up a new CodePen with Bootstrap and Angular and this bit of code, it won’t work. But once ng-app is tossed into the mix, it works. Like this:

<section>
  <ul>
    <li>
      <a href="#">Thing 1</a></li>
    <li>
      <a href="#">Thing 2</a></li>
  </ul>
  <p>{{tab}}</p>
</section>



See, the code we’re talking about is OUTSIDE the ng-app, and also, ng-app doesn’t refer to anything. I don’t know why it should work like this. I nearly asked a StackOverflow question, but upon reading other answers, I see that there’s so much more that I don’t know yet that I figure I should probably wait a little while so I don’t sound like a total idiot. Okay, I did it:
http://stackoverflow.com/questions/43049548/does-ng-app-on-html-element-act-like-an-on-off-switch

The point is that when you click on a tab, the tab value gets updated.

Expressions define a 2-way Data Binding… this means that Expressions are re-evaluated when a property changes.

So, now we add this:

<section>
  <ul class="nav nav-tabs">
    <li class="nav-item">
      <a class="nav-link" href="#">Description</a></li>
    <li class="nav-item">
      <a class="nav-link" href="#">Specifications</a></li>
    <li class="nav-item">
      <a class="nav-link" href="#">Reviews</a></li>
  </ul>
  <p>{{tab}}</p>
</section>
<section>
  <div class="panel">
    <h4>Description</h4>
    <p>{{product.description}}</p>
  </div>
  <div class="panel">
    <h4>Specifications</h4>
    <blockquote>None yet</blockquote>  
  </div>
  <div class="panel">
    <h4>Reviews</h4>
    <blockquote>None yet</blockquote>
  </div>
</section>

3.2 ng-click

And we want to show different panels based on the tab clicked. How? ng-show:

<section>
  <div class="panel">
    <h4>Description</h4>
    <p>{{product.description}}</p>
  </div>
  <div class="panel">
    <h4>Specifications</h4>
    <blockquote>None yet</blockquote>  
  </div>
  <div class="panel">
    <h4>Reviews</h4>
    <blockquote>None yet</blockquote>
  </div>
</section>

So cool!

3.3 ng-init

But when we refresh, there’s no panel showing. We’d like to have some panel be open initially. So we can use ng-init to evaluate some expression on page load. Like this:

<section ng-init="tab = 1">

3.4 ng-class

What would be super cool is if we could add the active class to the li that’s selected so that Bootstrap would style/highlight it correctly. There’s a directive for that: ng-class

ng-class="{ active:tab === 1 }"

The way this works is…
active is the name of the class we want to set.
tab === 1 is the expression that should evaluate to true for the class to be set, otherwise, nothing happens.

<section>
  <ul class="nav nav-tabs">
    <li class="nav-item">
      <a ng-click="tab = 1" ng-class="{active:tab === 1}" class="nav-link" href="#">Description</a></li>
    <li class="nav-item">
      <a ng-click="tab = 2" class="nav-link" ng-class="{active:tab === 2}" href="#">Specifications</a></li>
    <li class="nav-item">
      <a ng-click="tab = 3" ng-class="{active:tab === 3}" class="nav-link" href="#">Reviews</a></li>
  </ul>
  <p>{{tab}}</p>
</section>

3.5 Cleaning up this mess

So to work with these tabs we are now using:
ng-show
ng-init
ng-click
ng-class

That’s messy. So what we’re going to do is create a new controller to make the HTML less messy.

First, we add:

<section ng-init="tab = 1" ng-controller="PanelController as panel">

where panel is the alias we’ll use to refer to PanelController from within this section. And then, within app.js we add:

  app.controller('PanelController', function(){

  });

3.6 Removing ng-init

The first thing we’ll remove is ng-init="tab = 1", which we’ll replace with this code, which goes in the PanelController:

this.tab = 1;

Which isn’t working. I’m going to try switching out all tab instances with panel.tab — panel being the alias. Okay, after doing so, as well as combining those two <section> elements, it looks like this and it works:

<section ng-controller="PanelController as panel">
  <ul class="nav nav-tabs">
    <li class="nav-item">
      <a ng-click="tab = 1" ng-class="{active:panel.tab === 1}"  class="nav-link" href="#">Description</a></li>
    <li class="nav-item">
      <a ng-click="panel.tab = 2" class="nav-link" ng-class="{active:panel.tab === 2}" href="#">Specifications</a></li>
    <li class="nav-item">
      <a ng-click="panel.tab = 3" ng-class="{active:panel.tab === 3}"  class="nav-link" href="#">Reviews</a></li>
  </ul>

  <div class="panel">
    <h4>Description</h4>
    <p>{{product.description}}</p>
  </div>
  <div class="panel">
    <h4>Specifications</h4>
    <blockquote>None yet</blockquote>  
  </div>
  <div class="panel">
    <h4>Reviews</h4>
    <blockquote>None yet</blockquote>
  </div>
</section>

3.7 DRY ng-click

Next, rather than repeating the code to select tab by clicking on it, we’ll create a function that the ng-click can call.

    this.selectTab = function(setTab){
      this.tab = setTab;
    };

Where the HTML now reads ng-click="panel.selectTab(1)", etc.

3.8 DRY ng-class & ng-show

Similarly, both ng-class and ng-show are concerned with checking whether this.tab is equal to whatever tab number we’re clicking on. So we’ll make a function for that too.

    this.isSelected = function(checkTab){
      return this.tab === checkTab;
    };

And in the html:

ng-class="{active: panel.isSelected(2)}"

as well as

ng-show="panel.isSelected(1)"

4 Challenges

4.1 create a controller

  app.controller('TabController', function(){

  });

4.2 Initialize the tab to 1

  app.controller('TabController', function(){
    this.tab = 1;
  });

4.3 Set the Tab

Set it, that is, to the value of the tab clicked.

this.setTab = function(value){
      this.tab = value;
};

4.4 check to see if tab is same as this.tab

    this.isSet = function(value){
      return this.tab === value;
    };

4.5 Attach TabController to section with div class. Also Alias.

ng-controller="TabController as panel"

4.6 trigger setTab on click, pass in tab number

ng-click="panel.setTab(3)"

4.7 use isSet to show correct tab

        <div>

4.8 Add the active class to active tabs

This is not working:

ng-class="active:panel.isSet(1)"

But this works:

ng-class="{active:panel.isSet(1)}"

Also, in the tutorial it needs to go in the li while in my example, it needed to go in the a–which is just on account of how Bootstrap works.

4.9 Output item description

This doesn’t work:

<blockquote>Shine: {{store.product.description}}</blockquote>

I’m not sure what to do…because this works:

this.description = function(tab){
      return gems[tab].description;
    };

alongside

<div class="panel">
    <h4>Description</h4>
    <p>{{panel.description(panel.tab)}}</p>
  </div>

but the problem is that in the challenge I can’t edit the JS. So there must be another way. My next thought is that maybe I’m supposed to have the <section> elements within the StoreController–it didn’t really occur to me that I could have controllers inside other controllers. So…in that case, it’s just product.description I’m going for.

Oh! Okay, so that worked

4.10 Create a GalleryController

  app.controller('GalleryController', function(){

  });

4.11 make a current property of 0

this.current = 0;

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s