Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Angular Directive attributes in its template

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.

like image 789
Maarten Avatar asked Jun 27 '13 14:06

Maarten


People also ask

Can a directive have a template in Angular?

Components are directives with templates. The only difference between Components and the other two types of directives is the Template. Attribute and Structural Directives don't have Templates. So, we can say that the Component is a cleaner version of the Directive with a template, which is easier to use.

What is the use of attribute directive in Angular?

The attribute directive changes the appearance or behavior of a DOM element. These directives look like regular HTML attributes in templates. The ngModel directive which is used for two-way is an example of an attribute directive.

What are the directives with a template in Angular called?

Structural directives are directives which change the DOM layout by adding and removing DOM elements. Angular provides a set of built-in structural directives (such as NgIf , NgForOf , NgSwitch and others) which are commonly used in all Angular projects.


2 Answers

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/

like image 197
Brian Lewis Avatar answered Oct 15 '22 16:10

Brian Lewis


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.

like image 45
Mike Gledhill Avatar answered Oct 15 '22 18:10

Mike Gledhill