Use Angular Directive attributes in its template

27,282

Solution 1

You can pull the attributes out and place them into the scope of the directive like this:

angular.module('myApp', []).
directive('myTooltip', function ($log) {
    // allowed event listeners
    var allowedListeners = ["click"];
    return {
        restrict: 'A',
        template:   '<div class="tooltip-title">{{tooltipTitle}}</div>' +
                    '<div class="tooltip-content">' +
                    '{{tooltipContent}}</div>',
        scope: {
            tooltipTitle: '@tooltipTitle',
            tooltipContent: '@tooltipContent'
        },
        link: function (scope, elm, attrs) {
            if (allowedListeners.indexOf(attrs.myTooltip) != -1) {
                elm.bind(attrs.myTooltip, function () {
                    $log.info('clicked');
                });
            }

        }
    };
});

Here is fiddle: http://jsfiddle.net/moderndegree/f3JL3/

Solution 2

This question has already been answered, but I'm going to share my Angular code aswell, as this is one area where it's often useful to see a few working examples.

I have a few webpages, each with their own Angular controller, and I wanted a way to have one "Please Wait" popup on each page, which would appear when any of the pages called an HTTP GET or POST web service.

enter image description here

To do this, each of my webpages contains this line:

<please-wait message="{{LoadingMessage}}" ></please-wait>

...which is bound to a $scope in that page's controller...

$scope.LoadingMessage = "Loading the surveys...";

Here's the code for my <please-wait> directive:

myApp.directive('pleaseWait',  
    function ($parse) {
        return {
            restrict: 'E',
            replace: true,
            scope: {
                message: '@message'
            },
            link: function (scope, element, attrs) {
                scope.$on('app-start-loading', function () {
                    element.fadeIn(); 
                });
                scope.$on('app-finish-loading', function(){
                    element.animate({
                        top: "+=15px",
                        opacity: "0"
                    }, 500);
                });
            },
            template: '<div class="cssPleaseWait"><span>{{ message }}</span></div>'
        }
    });

Notice how it picks up the message attribute ({{LoadingMessage}} in this example) and can display its value in the directive's template.

(That's actually the only part of my answer which directly answers this question, but read on, for a few more tips'n'tricks...)

Now, the cool part is that each of my controllers calls an Angular data service whenever it wants to load or save any data from/to a web service.

   $scope.LoadAllSurveys = function () {
        DataService.dsLoadAllSurveys($scope).then(function (response) {
            //  Success
            $scope.listOfSurveys = response.GetAllSurveysResult;
        });
   }

The dsLoadAllSurveys function looks like this...

myApp.webServicesURL = "http://localhost:15021/Service1.svc";

myApp.factory('DataService', ['$http', 'httpPostFactory', 'httpGetFactory',
    function ($http, httpPostFactory, httpGetFactory) {

        var dsLoadAllSurveys = function (scope)
        {
            //  Load all survey records, from our web server
            var URL = myApp.webServicesURL + "/getAllSurveys";
            return httpGetFactory(scope, URL);
        }

        return {
            dsLoadAllSurveys: dsLoadAllSurveys
        }
    }]);

And, crucially, all "GET" web service calls go via the following function, which displays the Please Wait control for us... then makes it go away when the service has completed.

myApp.factory('httpGetFactory', function ($http, $q) {
    return function (scope, URL) {
        //  This Factory method calls a GET web service, and displays a modal error message if something goes wrong.
        scope.$broadcast('app-start-loading');          //  Show the "Please wait" popup

        return $http({
            url: URL,
            method: "GET",
            headers: { 'Content-Type': undefined }
        }).then(function (response) {
            scope.$broadcast('app-finish-loading');     //  Hide the "Please wait" popup
            if (typeof response.data === 'object') {
                return response.data;
            } else {
                // invalid response
                return $q.reject(response.data);
            }
        }, function (errorResponse) {
            scope.$broadcast('app-finish-loading');     //  Hide the "Please wait" popup

            //  The WCF Web Service returned an error.  
            //  Let's display the HTTP Status Code, and any statusText which it returned.
            var HTTPErrorNumber = (errorResponse.status == 500) ? "" : "HTTP status code: " + errorResponse.status + "\r\n";
            var HTTPErrorStatusText = errorResponse.statusText;

            var message = HTTPErrorNumber + HTTPErrorStatusText;

            BootstrapDialog.show({
                title: 'Error',
                message: message,
                buttons: [{
                    label: 'OK',
                    action: function (dialog) {
                        dialog.close();
                    },
                    draggable: true
                }]
            });

            return $q.reject(errorResponse.data);
        });
    };
});

What I love about this code is that this one function looks after displaying/hiding the "Please wait" popup, and if an error occurs, it also looks after displaying the error message (using the excellent BootstrapDialog library), before returning the error result back to the caller.

Without this factory function, each time one of my Angular controllers would call a web service, it would need to show, then hide, the "Please wait" control, and check for errors.

Now, I can just call my web service, and leave it to inform the user if something goes wrong, otherwise I can assume it's all worked, and process the results.

This allows me to have much simpler code. Remember how I called that web service:

   DataService.dsLoadAllSurveys($scope).then(function (response) {
        //  Success
        $scope.listOfSurveys = response.GetAllSurveysResult;
    });

That code looks as though it's not doing any error-handling, whereas actually, it's all looked after behind the scenes in one place.

I'm still getting the hang of factories and data services with Angular, but I think this is a stonking example of how they can help.

Hope this made sense, and helps.

Share:
27,282
Maarten
Author by

Maarten

20+ years experience in web development, system administration and application management.

Updated on August 02, 2022

Comments

  • Maarten
    Maarten almost 2 years

    How can I use the value of an attribute in a directive? My element looks like this:

    <div class="tooltip-icon" 
      data-my-tooltip="click" 
      data-tooltip-title="foo" 
      data-tooltip-content="test content"></div>
    

    I would like to use that in the template of my directive, which looks like this:

    mainApp.directive('myTooltip',
        function() {
    
            // allowed event listeners
            var allowedListeners = ["click"];
    
            return {
                restrict: 'A',
                template:   '<div class="tooltip-title">...</div>' +
                            '<div class="tooltip-content">' +
                            '...</div>',
                link: function(scope, elm, attrs) {
                    if(allowedListeners.indexOf(attrs.myTooltip) != -1){
                        elm.bind(attrs.myTooltip, function(){
                            ...
                        });
                    }
    
                }
            };
        }
    );
    

    Where the triple dots are there should be code, but I cannot figure out how to get the contents of the attrs object (attrs.tooltipTitle, etc) into that template.