How to create an Angular directive in the <head> section of a document?
Asked Answered
S

5

11

I am new to angular.js. I am trying to create a directive to add some title and meta tags in the <head> section of html documents, but I am having some trouble.

My index.html document is as following:

<!DOCTYPE html>
<html ng-app="myApp">
<head>
    <meta charset="UTF-8">
    <base href="/">
    <seo-title></seo-title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.min.js"></script>
    <script src="https://code.angularjs.org/1.4.1/angular-route.min.js"></script>
    <script src="/incl/js/myApp.js"></script>
</head>
<body >
    <div ng-view></div>
</body>
</html>

My javascript is:

var app = angular.module ('myApp', ['ngRoute']);

app.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {

    $routeProvider
        .when('/', { templateUrl: 'routes/home.html'})
        .when('/pageA', { templateUrl: 'routes/pageA.html'})
        .when('/pageB', { templateUrl: 'routes/pageB.html'})
        .otherwise({ redirectTo: '/' });

    $locationProvider.html5Mode({
        enabled: true
    });

}]);

app.directive('seoTitle', function() {
    return {
        restrict: 'E',
        template: '<title>{{seo.title}}</title>'
    };
});

When I open the inspector, the directive has been moved to the <body> and has not been replaced with the template:

enter image description here

How can I create directives in the header?

P.S.: A code example would be great!

Sidesaddle answered 7/7, 2015 at 19:28 Comment(4)
Why would u need a directive to add meta tags? Just put them in from the start? And title can be set from a controller easily which can be assigned to the html tag and the head will have access to it.Rebound
Some page will have some meta tags and others won't. Besides, it is not only about title, it could be about setting a robots meta tag to noindex a page, etc...Secessionist
I'm not certain, but I think HTML spec doesn't allow for custom elements, like <seo-title> as a child of <head>. Regardless, the template doesn't replace the directive element - you could use replace: true (but that is being deprecated), or you could just use an attribute directive instead <title seo-title></title>Gravelly
no need for a template or custom element , just use title with restrict:'E' for the directiveCasemaker
Z
5

Your directive does not need to go in the head to set the title. Just have your directive inject $window and set $window.document.title = 'your title'.

UPDATE This is how you can update meta tags.

For updating meta tags I would use a Directive like this:

mmMetaTags.$inject = ['metaTags'];
function mmMetaTags(metaTags) {

    return {
        restrict: 'A',
        link: function(scope, element) {

            metaTags.metaTags.forEach(function(tag) {
                addMetaTag(tag.name, tag.content)
            });

            metaTags.subscribe(addMetaTag);

            function addMetaTag(name, content) {

                var tag = element[0].querySelector('meta[name="' + name + '"]'); 

                if (tag) {

                    tag.setAttribute('content', content);
                } else {

                    element.append('<meta name="' + name + '" content="' + content + '">');
                }
            }
        }
    }

}

directive('mmMetaTags', mmMetaTags);

Along with a service to set the metaTags:

function MetaTags() {

    // private
    this._tags = [];

    // private
    this._subscriber;

    var self = this;
    Object.defineProperty(this, 'metaTags', { get: function() {
        return self._tags;
     }});
}

MetaTags.prototype.addMetaTag = function(name, content) {
    this._tags.push({ name: name, content: content });
    this._updateSubscriber(name, content);
}

MetaTags.prototype.subscribe = function(callback) {
    if (!this.subscriber) {
        this._subscriber = callback;
    } else {
        throw new Error('Subscriber already attached. Only one subscriber may be added as there can only be one instance of <head>');
    }
}

// private
MetaTags.prototype._updateSubscriber = function(name, content) {
    this.subscriber(name, content);    
}

service('metaTags', MetaTags);

So in your head tag you would include the attribute mm-meta-tags. Then in your controller you would inject the metaTags service and call addMetaTag to update the tags.

Zoster answered 7/7, 2015 at 21:52 Comment(2)
I am aware of the simpler solution for titles, but as mentioned in the question comments, it is not only about title..Secessionist
@JVerstry I updated my answer with an example of directive and service to set meta tags.Zoster
N
2

You answer is here: Set Page title using UI-Router, implemented in your code it could be:

<!DOCTYPE html>
<html ng-app="myApp">
<head>
    <meta charset="UTF-8">
    <base href="/">
    <title seo-title>doesn't work</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.min.js"></script>
    <script src="https://code.angularjs.org/1.4.1/angular-route.min.js"></script>
    <script src="/incl/js/myApp.js"></script>
</head>
<body >
    <div ng-view></div>
</body>
</html>

and you js:

app.directive('seoTitle', function() {
return {
    restrict: 'a',
    template: 'works'
};

you just need to add a controller or some logic to set the title you want

Newscast answered 7/7, 2015 at 20:56 Comment(0)
S
2

First things first: I was looking in the inspector and yes, somehow the title tag appears within the body. But it seems not to affect its function.

Now to the solution: At first glance it seems that only replace: true is missing in the declaration of the seoTitle directive. Adding it solves the problem and the seo-title is replaced with title tag as planned, but Angular wraps the content in an additional span element as a new scope is created (even if the scope for seoTag is declared isolated scope: {}).

I came up with following solution:

app.directive('seoTitle', function() {

    function compile(elem, attrs, transclude) {

        return function ($scope) {
            transclude($scope, function (clone) {
                elem.empty();
                elem.append(clone[0].innerText);
            });
        };
    }

    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        scope: {},
        compile: compile,
        template: '<title ng-transclude></title>',
    };

});

Usage:

<seo-title>My Title</seo-title>

As already mentioned, with replace: true you can remove the wrapping seo-title tag.

In order to remove the additionally created span element, I provide the compile function with returns the postLink function.

I can't really explain, why I need to use the transclude function within the postLink function. It seems to be a quite common problem, that Angular creates an additional span element in this case. With a little bit try and error I found that the easiest way to get rid of the span, is to emtpy() the element and append only the innerText.

Sip answered 7/7, 2015 at 21:41 Comment(1)
no idea where this extra span comes from, never seen that before. Solution provided seems far more complex than what is neededCasemaker
A
0

You can try metang library. Beside title it supports other meta tags(description, author, og:, twitter:, etc)

Amalgamate answered 21/6, 2016 at 6:51 Comment(2)
Hey can you please help me with metang by any sampleCheddite
Yes, sure. Here is example gist.github.com/timsly/85aadbfb559d85c03a0995f58b28ba56Amalgamate
E
0
    angular.directive('ngHead', function () {
    let strHtml = '';
    strHtml += '<meta charset="utf-8">';
    strHtml += '<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">';
    strHtml += '<meta http-equiv="x-ua-compatible" content="ie=edge">';
    strHtml += '<meta name="google" content="notranslate" />';
    strHtml += '<title>';
    strHtml += '    title';
    strHtml += '</title>';
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            element.append(strHtml);
        }
    }
});

in your html <head ng-head></head> work for me.

Elmaelmajian answered 29/12, 2020 at 22:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.