Alerts Removal

We can easily implement the alert removal by handling the close button click and removing the associated markup from the DOM. But in AngularJS the view is guided by the underlying model and any changes in model is reflected in the view and vice versa. So we will modify the application to proper model based behavior.

app.controller("AlertsController", function($scope){ var message = "<strong>Well done!</strong> You successfully read this important alert message."; var infomessage = "<strong>Heads up!</strong> This alert needs your attention, but it's not super important."; var warningmessage = "<strong>Warning!</strong> Best check yo self, you're not looking too good." var dangermessage = "<strong>Oh snap!</strong> Change a few things up and try submitting again."; $scope.alerts = [{ message: message, type: "success" },{ message: infomessage, type: "info" },{ message: warningmessage, type: "warning" },{ message: dangermessage, type: "danger" }]; });

Previously exposed scope variables are made local and these are used in creating an array of alert models named alerts and is then exposed to view through the scope.

<div ng-controller="AlertsController"> <alert ng-repeat="alert in alerts" message="{{alert.message}}" type="{{alert.type}}"></alert> <!-- <alert message="{{message}}" type="info"></alert> <alert message="{{message}}" type="warning"></alert> <alert message="{{message}}" type="danger"></alert> --> </div>

HTML code is now reduced to a simple line. We use ngRepeat and psuedo javascript code to iterate through the alerts keeping the currently iterated one as local alert variable. Since the alert is now an object we use object dot notation to access message and type attributes inside the AngularJS expression.

Now our directive template is ready to handle the removal of alerts.

<div class="alert" ng-class='type && "alert-" + type'> <button type="button" class="close" data-dismiss="alert" aria-hidden="true" ng-click="close()">×</button> <div ng-bind-html-unsafe="message"></div> </div>

ngClick attribute is used to run the close function when the close button is clicked. We have to define the close function on the scope of the directive if this has to work. We have changed our alert to a model and the removal of this could not be generalized inside the directive and so declaring this close functionality on the directive is not desirable (besides the directive does not have access to the alerts model collection).

Another option is to define the close function on the controller which have access to the alerts collection and knows how to remove one alert from it. Unfortunately, our directive is in isolated scope and can’t access the controller scope directly. We can’t revert to @ symbol help as we have done previously as it can only link properties. AngularJS directives have & symbol for executing expressions on parent scope so let’s try it.

app.directive("alert", function(){ return{ restrict: 'EA', templateUrl: "alert.html", replace: true, scope:{ message: "@", type: "@", close: "&" }, link: function(){ } }; });

& symbol enables the directive to point close to a given expression (close function to be implemented on the controller scope in our case). It is also used in scenarios where we have to pass values from directives to controller or parent scope. Now we can place the expression on the close attribute of the directive.

<div ng-controller="AlertsController"> <alert ng-repeat="alert in alerts" message="{{alert.message}}" type="{{alert.type}}" close="close($index)"></alert> </div>

ngRepeat exposes a special variable named $index on the local scope and it could be used for identifying the alert that need to be removed. Once we have this index, the only thing left is just to remove the alert from the array.

$scope.close = function(index){ console.log(index, $scope.alerts); $scope.alerts.splice(index, 1); }

We logs the alerts array and the index to the console to make sure the index passed to the function and removing the alert from the array are working. Clicking on the close buttons a few times reveals that alerts array elements are in fact removed but our view is not updating these changes. The reason for this fantastic fail, i believe, is that our directive is in isolated scope and it is not aware of the changes happening inside the controller scope. One way to remedy our situation is to bring transclusion feature to our directive. Let’s make a slight change in the directive and we will get the expected behavior.

app.directive("alert", function(){ return{ restrict: 'EA', templateUrl: "alert.html", replace: true, transclude: true, scope:{ message: "@", type: "@", close: "&" }, link: function(){ } }; });

Transclusion in plain english means including a document in another. In our directive, if we make translude true any of the content of our alert directive tag (empty for now since our markup in view does not have any content for alert tag) will be compiled against the parent scope and the content will be made available inside the directive as we will seen shortly.