0

I have an AngularJS 1.6 application which has a widget section.

The widget section is a row with 3 widget that the end user can configure himself. He's given a list of all available widget, and he can choose which one to place and where.

Every widget is a component initialized like so:

(
  function(angular) {

    function Module1Controller() {
      var vm = this;

      // other stuff here
    }

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

Now, since the user can select which widget to render in a specific position, this data comes from a webservice.

Based on the data received from the WS, I need to be able to "activate" a component and "deactivate" all other components.

My first thought was to do something like so:

Controller:

(
  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)

View:

<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-3 ng-if="c.firstModule == 3"></module-3>
            <module-4 ng-if="c.firstModule == 4"></module-4>
            <module-5 ng-if="c.firstModule == 5"></module-5>
            <module-6 ng-if="c.firstModule == 6"></module-6>
            <module-7 ng-if="c.firstModule == 7"></module-7>
            <module-8 ng-if="c.firstModule == 8"></module-8>
            <module-9 ng-if="c.firstModule == 9"></module-9>
            <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-2 ng-if="c.secondModule == 2"></module-2>
            <module-3 ng-if="c.secondModule == 3"></module-3>
            <module-4 ng-if="c.secondModule == 4"></module-4>
            <module-5 ng-if="c.secondModule == 5"></module-5>
            <module-6 ng-if="c.secondModule == 6"></module-6>
            <module-7 ng-if="c.secondModule == 7"></module-7>
            <module-8 ng-if="c.secondModule == 8"></module-8>
            <module-9 ng-if="c.secondModule == 9"></module-9>
            <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-2 ng-if="c.thirdModule == 2"></module-2>
            <module-3 ng-if="c.thirdModule == 3"></module-3>
            <module-4 ng-if="c.thirdModule == 4"></module-4>
            <module-5 ng-if="c.thirdModule == 5"></module-5>
            <module-6 ng-if="c.thirdModule == 6"></module-6>
            <module-7 ng-if="c.thirdModule == 7"></module-7>
            <module-8 ng-if="c.thirdModule == 8"></module-8>
            <module-9 ng-if="c.thirdModule == 9"></module-9>
            <module-10 ng-if="c.thirdModule == 10"></module-10>
        </div>
    </div>
</div>

This works, but I feel it's the wrong way to do it. What if I have 200 widgets? I would have to write <module-N ng-if="c.firstModule == N"></module-N> 200 times (x3 widget areas), which is clearly not a good way to solve the issue.

I also thought to use ng-include, but as faras I know it only works for including templates, I need the corresponding controller to get loaded too.

Here is a plunker showing a working example of what I'm trying to achieve: https://plnkr.co/edit/uO8SUcmNMOGHcPjc1lWs?p=preview

As you can see, when you change the value of a combo, the corresponding module gets replaced.

The question is: is there a way to activate a specific component based on some controller field in AngularJS 1.6 without having to declare all of them and use ng-if?

6
  • (<module-1></module-1> is the same as <div module-1></div>) You can try to generate and compile your components in a loop with "<div " + module[i] + "></div>". Here is a small Demo with directives Commented Apr 4, 2018 at 11:06
  • @AlekseySolovey Unfortunately I have to do it with components (company requirement). Also, I cannot arbitrary compile code, since $compile comes from $scope and I don't have scope in controllers (again, company chose to avoid using scope in favour of "this" in controllers) Commented Apr 4, 2018 at 11:18
  • @BackSlash what about a module slot directive that receives the module as input, load and compile it afterwards (e.g., <module-slot module="module-1"></module-slot>)? Commented Apr 4, 2018 at 11:50
  • @LenilsondeCastro interesting! Could you please post an example of how you would load the module by knowing its name? Commented Apr 4, 2018 at 12:17
  • Well, you have to implement it the way it suits best your architecture it was just an example, you can pass whatever you want to the slot and resolve it internally on the directive, I can tell you are going to use a loader I recommend ocLazyLoad you can find a simple example here to give you a clue github.com/ocombe/ocLazyLoad/tree/master/examples/simpleExample Commented Apr 4, 2018 at 12:47

2 Answers 2

1

As I suggested in the comments section, you can use a module-slot directive and input the module as a sort of model, and internally resolve either the loading and compilation of the desired module.

This solution uses ocLazyLoad but you can use other loader if you want.

I've made a fork of your plunkr with a solution using ocLazyLoading

So, instead of declaring all modules and control its visibility with ng-if you use only one directive like so:

<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>

Directive:

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() {
              // compiles the new component inside the slot
              element.html("<module-" + scope.module + "></module-" + scope.module + ">");
              $compile(element.contents())(scope);

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

Refs

OcLazyLoad Docs

Simple Example from ocLazyLoad repository

$compile service docs

Sign up to request clarification or add additional context in comments.

Comments

0

Here a plunker showing a solution. The key is to create a directive to associate template and controller dynamicly

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);
                    });
                }
            });
        }
    };
}]);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.