• No results found

Controller option

In document SPA AngularJS Draft (Page 89-95)

In a directive, when we set the controller option we are creating a controller for the directive.

This controller is instantiated before the pre-linking phase.

The pre-linking and post-linking phases are executed by the compiler. The pre-link function is executed before the child elements are linked, while the post-link function is executed after. It is only safe to do DOM transformations after the post-link function.

We are defining a controller function in our directive, so we don‘t need to define either of these functions, but it is important to note that we cannot do DOM manipulations in our controller function.

What does a controller function look like? Just like any other controller. We‘ll inject the $http service in our controller (using the bracket injection notation):

app.directive('ngSparkline', function() { return {

restrict: 'A', require: '^ngCity', scope: {

ngCity: '@' },

template: '<div class="sparkline"><h4>Weather for {{ngCity}}</h4></div>',

controller: ['$scope', '$http', function($scope, $http) { $scope.getTemp = function(city) {}

}],

link: function(scope, iElement, iAttrs, ctrl) { scope.getTemp(iAttrs.ngCity);

} }

});

Note that in our link function, we have access to all of the attributes that were declared in the DOM element. This will become important in a minute when we go to customize the

directive.

Now we can create the function getTemp to fetch from the openweathermap.

var url =

"http://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=

imperial&cnt=7&callback=JSON_CALLBACK&q="

$scope.getTemp = function(city) { $http({

method: 'JSONP', url: url + city

}).success(function(data) { var weather = [];

angular.forEach(data.list, function(value){

weather.push(value);

});

$scope.weather = weather;

});

}

Now, inside of our link function, we‘ll have a promise that will be fulfilled by the controller method. A promise, if you‘re not familiar, is basically an object that will return the result of an action that is run asynchronously.

<div ng-sparkline ng-city="San Francisco"></div>

See it

This will print out a bunch of JSON

Weather for San Francisco

Weather:

[{"dt":1376078400,"temp":{"day":57.81,"min":57.81,"max":57.81,"night":57.81,"eve":57.81,"morn":5 7.81},"pressure":1021.65,"humidity":99,"weather":[{"id":804,"main":"Clouds","description":"overc ast

clouds","icon":"04n"}],"speed":6.05,"deg":228,"clouds":92},{"dt":1376164800,"temp":{"day":64.09,

"min":56.35,"max":64.09,"night":56.35,"eve":59.36,"morn":57.25},"pressure":1022.84,"humidity":87 ,"weather":[{"id":500,"main":"Rain","description":"light

rain","icon":"10d"}],"speed":5.77,"deg":241,"clouds":0,"rain":0.5},{"dt":1376251200,"temp":{"day

":64.45,"min":55.22,"max":64.56,"night":55.22,"eve":60.71,"morn":56.25},"pressure":1021.66,"humi dity":87,"weather":[{"id":800,"main":"Clear","description":"sky is

clear","icon":"01d"}],"speed":5.31,"deg":231,"clouds":0}]

As you can see, the directive is rendered on screen initially without data, but as soon as the link method is run (linking the specific element to the DOM), the getTemp method will run and will eventually populate the weather property on the scope.

Now, let‘s take this data and create a sparkline with it. To start, we‘ll want to pick a property on the weather to work on.

Let‘s start by creating a chart on the high temperatures. To start with, we‘ll need to create a $watch on the weather object. Because we are fetching the weather data asynchronously, we cannot simply write the method expecting the data to be populated for us when the linking function runs. No matter, AngularJS makes this incredibly easy with the built-in

function$watch.

The $watch function will register a callback to be executed whenever the result of the expression changes. Inside the $digestloop, every time AngularJS detects a change, it will call this function. This has the side effect that we cannot depend on state inside this function.

To counter this, we‘ll check for the value before we depend on it being in place.

Here is our new $watch function:

scope.getTemp(iAttrs.ngCity);

scope.$watch('weather', function(newVal) { if (newVal) {

} });

Every time the weather property on our scope changes, our function will fire. We will use d3 to chart our sparkline.

Here‘s what we have so far:

app.directive('ngSparkline', function() { var url =

"http://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=

imperial&cnt=14&callback=JSON_CALLBACK&q=";

return {

restrict: 'A', require: '^ngCity', scope: {

ngCity: '@' },

template: '<div class="sparkline"><h4>Weather for {{ngCity}}</h4><div class="graph"></div></div>',

controller: ['$scope', '$http', function($scope, $http) { $scope.getTemp = function(city) {

$http({

method: 'JSONP', url: url + city

}).success(function(data) { var weather = [];

angular.forEach(data.list, function(value){

weather.push(value);

});

$scope.weather = weather;

});

} }],

link: function(scope, iElement, iAttrs, ctrl) { scope.getTemp(iAttrs.ngCity);

scope.$watch('weather', function(newVal) {

// the `$watch` function will fire even if the // weather property is undefined, so we'll // check for it

if (newVal) { var highs = [], width = 200, height = 80;

angular.forEach(scope.weather, function(value){

highs.push(value.temp.max);

});

// chart }

});

} } });

See it

Weather for San Francisco

020406080

Show d3 source

Now that we have our chart being drawn, let‘s look at ways that we can customize the directive when we invoke it.

Inside of our watch function, we‘re currently setting a static height and width. We can do better than that by allowing the invocation to determine the width and the height. Because we have access to the iAttrs (the instance of the attributes on the instance of the DOM element), we can simply look there first; otherwise we can set a default.

Change the width and the height to look like this:

var width = iAttrs.width || 200,

height = iAttrs.height || 80;

When we invoke the directive, this time we can add a width to set the width:

<div ng-sparkline width='400'></div>

Now our sparkline changes width to be 400 pixels, instead of the default 200.

See it

Weather for San Francisco

020406080

Inside of our directive as it stands today, we have a label that tells us ―Weather for .‖

Although this is convenient for demo purposes, we might not always want that label to be static inside the directive. We can set it to include any html that we put inside of the DOM element that contains our directive.

In document SPA AngularJS Draft (Page 89-95)