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

2

7

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.

Hellman answered 29/9, 2014 at 22:4 Comment(6)
I just did it here #25043936Beaded
Thanks for the link. I'm not sure I follow what's different from that answer versus what I've posted above. At its core, I'm also trying to attempt to not have loose HTML in services like this.Hellman
Which html? the one inlined?, just grab it as a template from an url, is that?Beaded
The inlined HTML. I suppose this is the minimal amount I can get away with? var el = angular.element('<div data-dialog></div>') Would you agree? I'm just trying to steer clear as much as possible from HTML and DOM manipulation in services and keep that in directives.Hellman
You can get that inlined html out of the service, that's better for "decoupling" view from service, but if you are pretty sure that your dialog html will be always the same (I mean the window, not the content that is already coming from an url) then is great to have it there, trust me.Beaded
Cool. If you throw that in an answer I'll gladly accept it.Hellman
B
13

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.
Beaded answered 29/9, 2014 at 22:33 Comment(0)
S
1

You could try this as below, just to render your ng-include before opening the dialog

app.service('dialog', ['$http', '$compile', '$q', '$templateCache'
    function($http, $compile, $q, $templateCache) {

        this.compileInclude = function(scope, url) {
            var deferred = $q.defer();
            $http.get(url, {cache : $templateCache}).then(function(response){
                deferred.resolve($compile(response.data)(scope));
            });
            return deferred.promise;
        };
    }
]);

From the controller write as below

dialog.compileInclude($scope, 'dialog.html').then(function(){
    // open dialog here
});
Stative answered 27/6, 2016 at 11:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.