In Part I we created a simple directive showing weather for any given Zip Code.
Now what if we wanted to make this directive more dynamic and interactive where the user could select the location and the same instance of the directive would show the corresponding weather information?
Luckily with AngularJS creating this is fairly easy.

The scope property of the directive object provides a way to introduce outside variables into the directive.

scope has 3 possible values:

  1. true- then a new scope will be created for this directive.
    If multiple directives on the same element request a new scope, only one new scope is created.
    The new scope rule does not apply for the root of the template since the root of the template always gets a new scope.
  2. {}- A new isolate scope is created. This object will have properties to create binding between the controller variables or functions or expressions to the ones present within the directive
  3. false- Parent scope is used.

We will look at these in detail in our next tutorial. But right now we will just use option #2 with the ‘@’ binding type which creates a one way binding from parent to directive scope.
Note that in this current tutorial we do not need to modify values back in the parent controller so one way binding is good enough.

Taking the code from Part I, we will move the Ajax call to a new service.

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//create a service to wrap our Ajax Call.
    WeatherApp.factory('weatherService', ['$http', function (http) {
        var ws = {
        //this function only returns the http promise object.
        //The caller will actually provide the call back function for success or failure
        getWeatherByLocation: function (loc) {
            return http({
                url: "http://localhost:61560/AngularJS/Directives/YahooWeather.ashx?l=" + loc,
                method: "post"
            });
        }
    }

    return ws;
}]);


angular.module('directives', [])
    .directive('yahooWeather', //note that we injected the 'yahooWeather' service in the directive.
   function (weatherService) {
       var yw = {
           templateUrl: 'partials/weatherTemplate.html',
           replace: true,
           restrict: 'EACM',
           scope: {
               location: '@' //creates a one way binding between the parent controller and the directive scope variables.
           },
           link: function postLink(scope, iElement, iAttrs) {
               scope.$watch('location', function (newVal, oldVal) {
                   if (newVal && newVal.length === 5) {
                       getWeather(newVal);
                   }
               });

               function getWeather(loc) {
                   weatherService.getWeatherByLocation(loc)
                       .success(function (data, status, headers, config) {
                           if (data) {
                               scope.item = data.query.results.channel.item;
                           }
                       });
               }
           }
       }
       return yw;
   });

We also modified the directive as below:

  • We removed the controller
  • We added the scope property with “@” one way binding for the location property.
    When the location attribute value changes in the parent scope, the locationin directive scope gets that value.
  • We add scope.$watch() function which will be executed when the value in location changes.
  • Within the watch function we call the server to get weather information for the selected Zip code and everything else remains same.

That’s all it takes to make the directive reusable and interactive.

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    <!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js" ng-app="WeatherApp">
<!--<![endif]-->
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title></title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width">
    <script src="../lib/jquery-1.8.3.min.js"></script>
    <script src="../bootstrap/js/bootstrap.min.js"></script>
    <script src="../lib/angularjs.min.js"></script>
    <script src="js/main.js"></script>
    <link href="../bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body ng-controller="main">
    <div class="container">
        <div class="row span12">
            <header>
                <h1>AngularJS Directives Demo</h1>
                <h3>Weather For Places I can't afford.</h3>
            </header>
            <section>
                <div class="span3 pull-left" >
                    <select ng-model="selectedLocation">
                        <option value="">Select A Location</option>
                        <option value="10038">New York, NY</option>
                        <option value="90210">Beverly Hills, CA</option>
                        <option value="96706">Honolulu, HI</option>
                    </select>
                    <yahoo-weather location="{{selectedLocation}}" ng-show="selectedLocation != ''"></yahoo-weather>
                </div>
            </section>

        </div>
    </div>
</body>
</html>

Click here to download the code.