How to dynamically load components in Angular version 1.6

In my AngularJS 1.6 application, there is a widget section that allows the end user to customize 3 widgets. The user can select from a list of available widgets and decide where to place them.

Each widget is a component initialized like this:

(
  function(angular) {

    function Module1Controller() {
      var vm = this;

      // other stuff here
    }

    angular.module("app")
      .component(
        "module1", {
          controller: Module1Controller,
          templateUrl: 'module1.html'
        }  
      );
  }
)(angular)

The data for rendering the widgets comes from a webservice, and I need to be able to activate one component and deactivate the rest based on the data.

My initial approach was to create a controller like this:

(
  function(angular) {

    function ModulesController() {
      var vm = this;

      vm.firstModule = 1;
      vm.secondModule = 1;
      vm.thirdModule = 1;

      vm.availableModules = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    }

    angular.module("app")
      .controller(
        "ModulesController", 
        [
          ModulesController
        ]
      );
  }
)(angular)

The HTML structure for rendering the widgets is as follows:

<div class="container-fluid" ng-controller="ModulesController as c">
    <div class="row">
        <div class="col-xs-4"> <!-- third widget area -->
            <module-1 ng-if="c.firstModule == 1"></module-1>
            <module-2 ng-if="c.firstModule == 2"></module-2>
            ...
            <module-10 ng-if="c.firstModule == 10"></module-10>
        </div>
        <div class="col-xs-4"> <!-- third widget area -->
            <module-1 ng-if="c.secondModule == 1"></module-1>
            ...
            <module-10 ng-if="c.secondModule == 10"></module-10>
        </div>
        <div class="col-xs-4"> <!-- third widget area -->
            <module-1 ng-if="c.thirdModule == 1"></module-1>
            ...
            <module-10 ng-if="c.thirdModule == 10"></module-10>
        </div>
    </div>
</div>

While this approach works, it is not scalable. If I have a large number of widgets, I would have to duplicate code for each one. I considered using ng-include but it doesn't load the accompanying controller.

A provided Plunker demonstrates the desired functionality: https://plnkr.co/edit/uO8SUcmNMOGHcPjc1lWs?p=preview

By changing the combo value, the corresponding module is replaced. The question remains: is there a more efficient way to activate a component based on a controller field in AngularJS 1.6 without relying on ng-if for each component?

Answer №1

Instead of manually declaring all modules and controlling their visibility with ng-if, a more efficient approach is using a module-slot directive to dynamically load and compile the desired module.

This method utilizes ocLazyLoad, but you have the flexibility to use other loaders if preferred.

For a practical demonstration, check out this modified plunkr incorporating ocLazyLoading.

With this directive, you only need to use one directive for all modules:

<div class="row">
    <div class="col-xs-4">
      <module-slot module="c.firstModule"></module-slot>
    </div>
    <div class="col-xs-4">
      <module-slot module="c.secondModule"></module-slot>
    </div>
    <div class="col-xs-4">
      <module-slot module="c.thirdModule"></module-slot>
    </div>
  </div>

Here is the directive implementation:

angular.module("app")
    .directive('moduleSlot', function($ocLazyLoad, $compile) {
      return {
        restrict: 'E',
        scope: {
          module: '='
        },
        link: function(scope, element, attrs) {
          // watch the module change
          scope.$watch('module', function() {
            // load the module
            $ocLazyLoad.load("module" + scope.module + ".js").then(function() {
              // compile the new component inside the slot
              element.html("<module-" + scope.module + "></module-" + scope.module + ">");
              $compile(element.contents())(scope);

            }, function(e) {
              console.log('error');
              console.error(e);
            })
          });
        }
      }
    });

References:

OcLazyLoad Documentation

Simple Example from ocLazyLoad Repository

$compile Service Documentation

Answer №2

Check out this plunker link for a helpful solution. To successfully link the template and controller dynamically, it is important to create a directive.

app.directive("module", ['$compile', '$http', '$controller', '$templateCache', function ($compile, $http, $controller, $templateCache) {
    return {
        restrict: 'E',
        replace: true,
        scope: {
            model: '='
        },
        link: function (scope, element, attrs) {
            scope.$watch("model", function (newValue, oldValue, scope) {
                if (newValue) {
                    var template = scope.model.id + ".html";
                    $http.get(template, { cache: $templateCache }).then(function (result) {
                        element.html(result.data);
                        element.children().data('$ngControllerController', $controller(scope.model.id + "Ctrl", { $scope: scope }));
                        $compile(element.contents())(scope);
                    });
                }
            });
        }
    };
}]);

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

Enable the submission of the form with a combo of Shift key and Enter

When using a simple form that posts to a basic PHP script, I encountered an issue where if someone is typing quickly and accidentally holds down the Shift key while pressing "Enter," a barrage of PHP error messages appears. Is there a way to allow Shift + ...

SignOut operation in Express.js Firebase instantly responds with a status of 200 to the client-side Fetch POST request

I'm currently facing an issue with my POST request setup using the Fetch API in my client-side JavaScript. The request is being sent to my server-side JavaScript code which utilizes Express Js and Firebase Auth. The problem arises when the auth().sign ...

vaadin-grid selection issue not functioning

I'm encountering an issue with the row selection feature. The selectedItems array only updates when I select all items at once. I'm not sure if I'm missing something or if this is a bug. selectedItems: An array that contains the selected ...

What is the best way to make a vertical line using the CSS code provided in the link?

Check out this cool horizontal line design I found on stackoverflow: Click here for CSS code to create the line I really like the look of the line. Here's how I implemented it on my website: hr.fancy-line { border: 0; height: 5px; } hr ...

Is there a regular expression that can identify whether a string is included in a numbered list?

Struggling with creating a regular expression to determine if a string is part of a numbered list like those in word processors. Need it to return true only if the string starts with a number, followed by a full stop and a space. Easy for single or doubl ...

A distinctive noise is heard when hovering over multiple instances of a div

I'm trying to implement a feature where a unique sound plays when hovering over a specific div element with a particular class (.trigger). However, I am encountering an issue where multiple instances of this div class result in the same sound being pl ...

Is mocking all dependencies in an AngularJS controller necessary for unit testing?

Is it necessary to mock all the dependencies of my controller in order to test the scope? Here is a snippet of my code... .controller('SignupCtrl', ['$scope', 'vcRecaptchaService', '$http', '$location', & ...

Extracting data from string in object form

Values are stored as JSON objects in my database. After retrieving these values, the result is: '["{ zone :1, cat_id : 1, subcat : 2}","{ zone :1, cat_id : 2, subcat : 2}","{ zone :1, cat_id : 2, subcat : 3}"]' I then convert it to an array us ...

Tips for dynamically populating a mat-table dataSource

While working with backend data streaming, I encountered an issue where trying to push an event to dataSource resulted in an error stating that dataSource is not defined. Can anyone provide guidance on how to dynamically add data to a materialize table? s ...

What is the best method for transmitting server errors to the client?

When using passport.js, I have all the required code in place. However, I am facing an issue with displaying errors on the client-side. Despite having the successRedirect set up correctly, nothing happens when a wrong password is entered, and no error mess ...

What causes Three.js OBJ conversion to render as mesh successfully but log as undefined?

I'm just getting started with Three.js and I'm experimenting a lot. Although I'm new to Javascript as well, the issue I'm facing seems to be more about variable scoping and callback function protocols than it is about Three.js itself... ...

In my experience, I have encountered issues with certain routes not functioning properly within Express

I am currently working on developing a tic-tac-toe game and looking to store user data in a database. However, I am facing an issue with the router I intended to use for this purpose as it is returning an 'Internal server error message (500)'. B ...

Getting the value of a button using JavaScript

Is there a way to retrieve the value of a button when multiple buttons are generated dynamically? I have a JavaScript function that creates buttons in a list based on my search history, with each button labeled as a city name. However, after clicking on o ...

Having trouble with webpack displaying CSS background images?

I've set up the html boilerplate using webpack. I'm currently encountering an issue where calling an image in my scss file like "background: url('/img/img.png');" isn't working. I've included the webpack file and folder struct ...

Issue encountered while attempting to load several google charts simultaneously

What do I need? I am in need of two Google charts, one on each tab of the website as shown in the screenshot below: Tabs What seems to be the issue? The second chart is not loading properly and showing mixed information. This can be observed in the scre ...

What causes Gun.js to generate duplicate messages within a ReactJs environment?

I need assistance with my React application where gun.js is implemented. The issue I am facing is that messages are being duplicated on every render and update. Can someone please review my code and help me figure out what's wrong? Here is the code s ...

Ways to effortlessly activate an angular directive once the page has been fully loaded

I am facing an issue with a print directive that is triggered by the print="id" attribute within an <a></a> element. The button is contained in a modal that remains hidden from the user. I want the directive to execute as soon as the modal is l ...

Eliminating single and multiple relationships - Mongoose

My Assignment schema includes references to both Groups and Projects. Assignment == Group [One-One Relationship] Assignment == Projects [One-Many Relationship] Here is my Assignment Schema: var AssignmentSchema = new Schema({ name: String, group ...

Develop a custom JavaScript code block in Selenium WebDriver using Java

Recently, I came across a JavaScript code snippet that I executed in the Chrome console to calculate the sum of values in a specific column of a web table: var iRow = document.getElementById("DataTable").rows.length var sum = 0 var column = 5 for (i=1; i& ...

The issue arises when attempting to utilize ExpressJS middleware in conjunction with NextJS Link feature

Incorporating Next with Express routes, I have set up a scenario where /a should only be accessible to authorized individuals, while /b is open to the public. ... other imports... const app = next({ isDev }) const handle = app.getRequestHandler() async f ...