Inject a controller into a running Angular app-Collection of common programming errors

I’ve written some simple code to allow for bootstrap modals to work with angular in that it loads a link into a modal when clicked. Now the links in question have their own angular controllers which are included in their source. When the modal is being loaded I first use jquery to load all it’s dependant scripts and then have Angular compile the modal so that it is “aware” of it. However it seems that despite the fact that I define the controller on-demand as the modal is loaded, Angular will not be “aware” of it and throws an error (Uncaught Error: Argument ‘ControllerName’ is not a function, got undefined).

Is there a way for me to tell Angular to recognize the new controller I’ve added at run-time?

Here’s the modal code I’m using fwiw (prototype code):

var directivesModule = angular.module('modal.directives', []);
directivesModule.directive("modal", function(ModalService) {
    return function($scope, elem, attrs) {
            elem.on("click", function(e) {
                e.preventDefault();
                var url = $(this).attr('href');

                ModalService.load(url, $scope);
            });
    };
});

var servicesModule = angular.module('modal.service', []);
servicesModule.factory('ModalService', function ($http, $compile, $rootScope)
{
    var ModalService = {};

    ModalService.load = function(url, scope)
    {
        if ($('.modal[id="'+url+'"]').length > 0)
        {
            ModalService.show($('.modal[id="'+url+'"]'), scope);
            return;
        }

        $http.get(url).success(function (data) {
            var _data = $(data);
            if (_data.find(".modal-body").length == 0) {
                var _data =  $(''
                      + 'x'
                      + '

'+_data.find(".title.hidden").text()+'

'
                      + ''+data+''
                      + ''
                      + 'Close');
            }

            var _scripts = [];
            var scripts = _data.find("script");
            if (scripts.length > 0)
            {
                scripts.each(function()
                {
                    var elem = $(this);
                    if (elem.attr("src"))
                    {
                        _scripts.push(elem.attr("src"));
                        elem.remove();
                    }
                });
            }

            ModalService.elem = $('');
            ModalService.elem.append(_data);
            ModalService.elem.appendTo("body");

            if (scripts.length > 0)
            {
                $.getScript(_scripts, ModalService.show.bind(this, ModalService.elem, scope));
            }
            else
            {
                ModalService.show(ModalService.elem, scope);
            }
        });
    };

    ModalService.show = function(elem, scope)
    {
        $rootScope.$broadcast('$routeChangeSuccess');

        $compile(elem)(scope);
        elem.modal();

        elem.find(".btn-close").click(function() {
            elem.modal("hide");
            setTimeout(function() { elem.remove(); }, 500);
        });
    };

    return ModalService;
});
  1. Yes, it is possible, and all needed utilities are already included in angular. I cannot write ready code, but here is something that will give you an idea:

    1. Load your HTML code, probably right into DOM -- something like $('#myDiv').load('dialog.html'). Do not bind controllers with ng-controller! Save a reference to DOM node (let's call it element).

    2. Load your controller — probably with head.js or whatever suits your situation best. Save a reference to controller function (controller is a good name). You don’t need to register it in your angular app, just a plain, global function. Reference any required services and scope as usual:

    function MyCtrl($scope, $resource, $http, $whatever, YourOwnService) {
        $scope.hello = 'world';
    }
    
    function ($compile, $rootScope, $inject) {
        var element = angular.element('#myDiv'),
            controller = MyCtrl;
    
        // We need new scope:
        var $scope = $rootScope.$new(true);
    
        // Compile our loaded DOM snippet and bind it to scope:
        $compile(element)($scope);
    
        $inject.invoke(controller, {
            $scope:$scope // you can add any other locals required by your controller
        });
    }
    

    I cannot guarantee this code works as is, but it describes technique I’ve used couple of weeks ago. But I must warn you that this will not allow you to dynamically add AngularJS dependencies: your app should include all possible modules added before bootstrapping app.

Originally posted 2013-12-02 01:31:00.