Search code examples
angularjsangularjs-service

Can I use $compile in an Angular service directly on a templateUrl instead of on raw HTML or a raw angular.element?


Given the following service that is meant to create a "dialog" element (i.e. a modal):

app.service('dialog', ['$document', '$compile', '$rootScope',
    function($document, $compile, $rootScope) {

        var body = $document.find('body');
        var scope = $rootScope.$new();

        this.createDialog = function() {
            var dialogElem = angular.element('<div ng-include="\'/dialog.html\'"></div>');
            $compile(dialogElem)(scope);
            body.append(dialogElem);
        };

    }
]);

which can be utilized in a controller like so:

$scope.someFunction = function() {
    dialog.createDialog();
};

Is there a way that I can use $compile or anything else to not have HTML in my service? I'd really prefer to just invoke a directive, so that running createDialog() immediately injects a directive into my DOM and thus the directive is responsible for linking a new controller and template together. If I'm going about this the wrong way I'm totally open to constructive ideas.


Solution

  • Of course you can!, here you go:

    app.factory('modalService', function ($document, $compile, $rootScope, $templateCache, $http) {
    
        var body   = $document.find('body'),
            modals = [];
    
        var service = {
            show: function (template, data, modal) {
    
                // The template's url
                var url = 'template/modal/' + template + '.html';
    
                // A new scope for the modal using the passed data
                var scope = $rootScope.$new();
                angular.extend(scope, data);
    
                // Wrapping the template with some extra markup
                modal = modal || angular.element('<div class="modal"/>');
    
                // The modal api
                var api = {
                    close: function () {
    
                        modal.remove();
                        scope.$destroy();
                        modals.splice(modals.indexOf(api), 1);
    
                    },
                    replace: function (template, data) {
    
                        return angular.extend(api, service.show(template, data, modal));
                    }
                };
    
                // Adding the modal to the body
                body.append(modal);
    
                // A close method
                scope.close = api.close;
    
                // Caching the template for future calls
                $http.get(url, {cache: $templateCache})
                    .then(function (response) {
    
                        // Wrapping the template with some extra markup
                        modal.html('<div class="win">' + response.data + '</div>');
    
                        // The important part
                        $compile(modal)(scope);
                    });
    
                modals.push(modal);
    
                return api;
            },
            showOrReplaceLast: function (template, data) {
    
                return service.show(template, data, modals.length > 0 ? modals[modals.length - 1] : null);
            }
        };
    
        return service;
    });
    

    Some notes:

    • You need to insert the modal somewhere in the DOM, that's why the $document is injected.
    • Yes, you can take the modal markup out of here.
    • Remember to create new scopes for the dialog and to destroy them ($rootScope.$new).
    • This is a WIP, I hope it's clear enough.