Add a fresh AngularJS controller to the HTML dynamically once the page has finished loading

I am currently developing an AngularJS website that fetches data from a web service/API. One API returns HTML and AngularJS code in order to dynamically add controllers or other new Angular components as needed. This specific string will be included in the API response.

<div id="homecontainer" class="flex-center p-page" loader style="overflow:hidden;">
    <div class="column-1">
        <div class="grid m-0 col-xs-12">
            <div ng-repeat="Widget in V3Widgets track by $index" class="grid-item">
                <div class="grid-sizer"></div>
             {{Widget}}
            </div>


        </div>
        <div ng-controller="WelcomeController">
            {{greeting}}
        </div>
        <script>
            var app = angular.module('demo', [])
                //RestService is Another Module Which is Already Functioning Properly 
            .controller('WelcomeController', function ($scope, RestService) {
                $scope.greeting = 'Welcome!';
            });
            angular.bootstrap(document, ['demo']);
        </script>
    </div>
</div>

I now have a directive that binds this string to the page.

<renderdynamicwidgethtml ng-if="Widget.Id==null && Widget.Html!=null" html="Widget.Html"/>

The JavaScript for the directive:

.directive('renderdynamicwidgethtml', ['$compile', function ($compile) {
    return function (scope, element, attrs) {
        scope.$watch(
          function (scope) {
              return scope.$eval(attrs.html);
          },
          function (value) {
              element.html(value);
              $compile(element.contents())(scope);
          }
       );
    };
}])

scope.$eval should convert the string into Angular components, but unfortunately it failed with the following error message.

[ng:btstrpd] http://errors.angularjs.org/1.3.17/ng/btstrpd?p0=document

Answer №1

With the introduction of Angular 1.5, a new feature called angular.component was added. In this response, the term Component is used to encompass all the available tools provided by Angular, including directives, filters, and angular.component.

Let's delve into how AngularJS initializes everything:

  1. Upon loading the JavaScript files, you'll notice that all angular code begins with angular.module, angular.controller, angular.directive, etc. These components are registered by passing a function as a parameter (except for angular.module, which takes a name and dependencies). However, these components are not created at this stage but merely registered.

  2. Once Angular has successfully registered all modules and components, it is ready to be bootstrapped. This can be done either using the ng-app directive or manually with angular.bootstrap. Both methods require a string parameter, representing the root module. Angular then examines the dependencies deeply (forming a dependency tree) and commences loading components starting from the leaf nodes (modules without dependencies) up to the root module's components.

  3. Angular follows a specific order when building each module, beginning with constants, followed by providers, configuration blocks in their registration order, values, factories/services, directives, and ultimately executing run blocks in their specified sequence (although it's advised to verify this order).

  4. After setting up everything, Angular 'seals' the application, disallowing any further registrations.

The mentioned error indicates that your application is being bootstrapped twice. This issue typically arises from using both the ng-app directive and calling angular.bootstrap, or making multiple calls to angular.bootstrap.

As stated in the manual initialization section of the documentation:

You should invoke angular.bootstrap() after loading or defining your modules. It's not possible to add controllers, services, directives, etc. post-application bootstrap.

To dynamically load new controllers, ensure they are registered prior to the bootstrap process. This can be achieved by fetching necessary data from an API using tools like jQuery (as Angular isn't operational yet), executing the code within the promise returned by the request, and subsequently manually bootstrapping AngularJS with angular.bootstrap.

If you need to repeat this process, ensure the code executed through eval doesn't bootstrap Angular before registering all required components.

Answer №2

What is the reason behind opting for scope.$eval over the $compile service, specifically designed to compile your template string?

   link: function (scope, ele, attrs) {
      scope.$watch(attrs.html, function(html) {
        $compile(ele.contents())(scope);
      });
    }

Answer №3

Give this a try:


app.directive('dynamicContent', [ '$compile',
function ($compile) {
    return {
        restrict: 'A',
        replace: true,
        link: function (scope, elem, attrs) {
            scope.$watch(attrs.dynamicContent, function (htmlCode) {
                elem.html(htmlCode);
                $compile(elem.contents())(scope);
            });
        }
    };
}]);

To implement this directive, assign the whole string to $scope.myText in your JavaScript file, and in your HTML use

<div dynamic-content="myText"></div>
. This way, it will compile and display all contents as expected.

Answer №4

I have discovered a potential solution that eliminates the need to know about the controller before bootstrapping:

// Create module Foo and save $controllerProvider in a global variable
var controllerProvider = null;
angular.module('Foo', [], function($controllerProvider) {
    controllerProvider = $controllerProvider;
});
// Bootstrap Foo
angular.bootstrap($('body'), ['Foo']);

// .. time goes by ..

// Load javascript file with Ctrl controller
angular.module('Foo').controller('Ctrl', function($scope, $rootScope) {
    $scope.msg = "It works! rootScope is " + $rootScope.$id +
        ", should be " + $('body').scope().$id;
});
// Load html file with content that uses Ctrl controller
$('<div id="ctrl" ng-controller="Ctrl" ng-bind="msg">').appendTo('body');

// Manually register Ctrl controller
// If you can reference the controller function directly, just run:
// $controllerProvider.register(controllerName, controllerFunction);
// Note: I haven't found a way to get $controllerProvider at this stage
//    so I keep a reference from when I ran my module config
function registerController(moduleName, controllerName) {
    // Here I cannot get the controller function directly so I
    // need to loop through the module's _invokeQueue to get it
    var queue = angular.module(moduleName)._invokeQueue;
    for(var i=0;i<queue.length;i++) {
        var call = queue[i];
        if(call[0] == "$controllerProvider" &&
           call[1] == "register" &&
           call[2][0] == controllerName) {
            controllerProvider.register(controllerName, call[2][1]);
        }
    }
}
registerController("Foo", "Ctrl");
// compile the new element
$('body').injector().invoke(function($compile, $rootScope) {
    $compile($('#ctrl'))($rootScope);
    $rootScope.$apply();
});

Fiddle. The only drawback is having to retain the $controllerProvider and utilize it in an unconventional location (after the bootstrap). Additionally, there appears to be no straightforward method to access a function used to define a controller until it is registered, necessitating looping through the module's _invokeQueue, which is not officially documented.

UPDATE: To register directives and services, rather than using $controllerProvider.register, simply use $compileProvider.directive and $provide.factory respectively. Once again, references to these must be saved in your initial module configuration.

UDPATE 2: Check out this fiddle which automates the registration of all controllers/directives/services loaded without the need for individual specifications.

Answer №5

When working with JavaScript, you can utilize the eval method to evaluate JS code and the $compile method to handle HTML code. I have created a plunker containing a complete test code. You can access it through the following link.

https://plnkr.co/edit/9sJw8ua1n5ItnHONwGmS

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Why does React-Perfect-Scrollbar not work properly when the height of an element is not specified in pixels?

Currently, I am developing a chat application with React 18 for its user interface. The app includes a sidebar that displays user profiles. To ensure the app fits within the browser window height, I've made the list of user profiles scrollable when n ...

The data input in the AngularJS form has not been identified

When utilizing AngularJS, my form is set up to pass data to a controller upon submission and then send it to an API. Form: <form ng-submit="newCompany()"> <div class="form-group" ng-controller="CompaniesController"> ...

How can I dynamically apply an active class when clicking on a group of buttons?

Iterating through the buttons array, I retrieve three buttons. My goal is to determine which button has been clicked and apply an active class to it. import React from "react"; import { useState } from "react"; import style from "./years.module.scss"; co ...

Discover the method for selecting an element within a table that includes a style attribute with padding using Jquery

Looking for a way to identify a td element with the padding style in a table I've attempted to locate a td element with the padding style attribute in a table $(Table).find('td[style="padding:"]') or $(Table).find('td[style] ...

What is the best way to change a date-containing string into a Json object?

I need to convert a string into a JSON Object using JavaScript. However, when I do so, the date in the original string gets completely changed. Here is the string I am working with: var JsonData=[[2013-02-27,787],[2013-02-26,131],[2013-02-02,0],[2013-01- ...

Unlocking your phone with a smooth Jquery scroll animation

https://i.sstatic.net/TpwoPPnJ.gif Is there a way to create an onscroll unlock screen animation where the phone remains static/sticky for a period before moving on scroll? I also want to include a parallax effect with text alongside it. I've impleme ...

What is the best way to designate external dependencies in WebPack that are not imported using '*'?

I need assistance with specifying office-ui-fabric-react as an external dependency in my TypeScript project using Webpack. Currently, I am importing only the modules I require in my project: import { Dialog, DialogType, DialogFooter } from 'office-u ...

What is the best way to determine the total of values from user-input fields that are created dynamically

Scenario- A scenario where a parent component is able to create and delete input fields (child components) within an app by clicking buttons. The value of each input field is captured using v-model. Issue- The problem arises when a new input field is crea ...

Renewing individual parental resolution cache

Is there a way to update specific resolves in the parent resolves without refreshing the entire page? Can resolve caching be disabled for certain items in ui-router? In my current setup, the company object is passed to 2 child states by an abstract parent ...

Utilizing Pug for Passing Variables to JavaScript

I am working with a Pug view that displays a set of links to users. The user is already authenticated and their username and department are stored in session variables. I am able to pass these variables to the view using this code: res.render('landin ...

Encountering the "EHOSTUNREACH" error message while attempting to establish a connection to an API through the combination of Axios and Express

Exploring the capabilities of the Philips Hue Bridge API, I delved into sending requests using Postman. To my delight, I successfully authenticated myself, created a user, and managed to toggle lights on and off. Verdict: Accessing the API is achievable. ...

Scraping a URL that functions perfectly on Firefox without the need for cookies or javascript results in a

Despite blocking all cookies and turning off JavaScript on Firefox, I encounter an issue when attempting to scrape a URL using Python's urllib module. The error message HTTP Error 403: Forbidden is returned. I have ensured that I am using the same use ...

What is the mechanism behind JQuery Ajax?

Currently, I am attempting to utilize JQuery Ajax to send data to a Generic Handler for calculation and result retrieval. Within my JQuery script, the Ajax request is contained within a for loop. The structure of the code resembles the following: function ...

Avoid navigating through hidden tab indexes

Below is the HTML code that I am working with: <span tabindex="19"> </span> <span tabindex="20"> </span> <span tabindex="21"> </span> <span id="hidden" tabindex="22"> </span> <span tabindex="23"&g ...

Displaying Edit and Delete options upon hovering over the data row

I'm working on a table that uses ng-repeat on the <tr> element. In the last column of each row, I have edit/delete links that should only be visible when the user hovers over the <tr> element. <tr ng-repeat="form in allData | filter:se ...

Is it possible to line up Ajax request in Javascript?

I am seeking a way to schedule an Ajax request to occur every second. The code I currently have in place functions flawlessly. window.onload = function () { setTimeout(doStuff, 1000); // Wait before continuing } function doStuff() { ...

Integrating HTML, JavaScript, PHP, and MySQL to enhance website functionality

Exploring the intricacies of HTML, JavaScript, PHP, and MySQL, I have been working on an order form to understand their interactions. View Order Form The aim of this table is to allow users to input a quantity for a product and have JavaScript automatica ...

Efficient process using angularJs alongside Laravel 4

As a newcomer to angularJs, I have found it incredibly useful in the realm of web development. However, I encountered some questions while trying to integrate it with server-side languages (specifically PHP with Laravel 4.1). AngularJs comes equipped with ...

Error TS2403: All variable declarations following the initial declaration must be of the same type in a React project

While developing my application using Reactjs, I encountered an error upon running it. The error message states: Subsequent variable declarations must have the same type. Variable 'WebGL2RenderingContext' must be of type '{ new (): WebGL2 ...

What is the best way to use jQuery to disable a button?

Specifically, what is the issue with this code that causes fields to only disable when the Department button is double-clicked before the Faculty button? You can view the full context on codepen $("#fac-button").on('click', function (e ...