Angular (1.5.8) Dynamic Components
Asked Answered
A

2

17

I'm trying to build a sort of dynamic dashboard using Angular 1.5.8. I've made decent progress up until the final hurdle. Which is actually rendering the dynamic component.

I've tried 2 options, either adding a ui-view and programatically passing in the name of the widget, or, and this is the route I'm guessing is more correct, I need to figure out how to render a dynamic widget.

For Example: As I append and item to the dashItems collection, it should render a new widget (based on the name I've provided)

I have seen that I can swap out templates using ngInclude, but I'm still unclear as to how to get a template and controller to be injected dynamically. (EG all my templates wont be sharing a common controller).

JavaScript:

angular
    .module('myDashboard', [])
    .config(routesConfig)
    .component('dashboard', {
        templateUrl: 'dashboard/dashboard.tpl.html',
        controller: dashboardController
    })
    .component('widgetPie', {
        template: '<h3>Pie Graph</h3>',
        controller: function($log) {
            $log.info('widgetPie: loaded');
        }
    })
    .component('widgetLine', {
        template: '<h3>Line Graph</h3>',
        controller: function($log) {
            $log.info('WidgetLine: loaded');
        }
    });

function routesConfig($stateProvider) {
    // this is only needed if I go the ui-view route.. I assume
    $stateProvider
        .state('widgetPie', { component: 'widgetPie'})
        .state('widgetLine', { component: 'widgetLine'});
}

function dashboardController($log) {
    $log.info('in dashboard');
    this.dashItems = [
        { widget:'widgetPie' },
        { widget:'widgetLine' }
    ];
}

Markup (dashboard/dashboard.tpl.html):

<div>
    <ul>
        <li ng-repeat="item in dashItems">
            <!--somehow render dynamic-->
            <!--<widget-pie></widget-pie>-->
            <!--<div ui-view="item.widget"></div>-->
        </li>
    </ul>
</div>

Question(s):

1. I've looked into ngInclude, but to be perfectly honest, I'm not sure how to go about using it in this instance, and IF it is the right tool for this, or am I approaching this incorrectly?

2. Should I even be adding items to the state provider for this, EG i / could a widget be seen as a child state (thus I'm not sure what would be seen as best practice)

Alexei answered 16/11, 2016 at 13:41 Comment(7)
Have you read up on directives?Admirable
@LarryTurtis yes, I am familiar with directives and how they work for the most part, but not with regards to having them (or components) render dynamically such as in my question, i'm still somewhat in the darkHy
I might be misunderstanding the intention, but would you be able to dynamically compile directives and add them to the page, as described in this post?Admirable
Possible duplicate of Is there a way to dynamically render different templates for an angular 1.5 componentEzekielezell
Yes, ng-include is the most simple way. Components don't differ from directives in this regard (because they are directives), you can also check a considerable collection of questions on 'dynamic directive' topic.Ezekielezell
@estus please correct me if Im wrong (and I might be / probably are), but I'm not wanting to just dynamically swap out templates... thus they wont have the same controller in the back. I want to swap out a dynamic template and controller? Isn't this a different scenario?Hy
A template should contain a desired component.Ezekielezell
A
22

I ended up changing the dashboard.tpl.html file to:

<div>
    <ul>
        <li ng-repeat="item in dashItems">
            <div ng-include="item.widget"></div>
        </li>
    </ul>
</div>

But I also needed to add a build task to run through my widgets folder and generate the following (or you can manually add, whatever floats your boat I guess).

angular
 .module('myDashboard')
 .run(function ($templateCache) {
    $templateCache.put('widgetPie', '<widget-pie></widget-pie>');
    $templateCache.put('widgetLine', '<widget-line></widget-line>');
 });

The above allows me to either use templateUrl, or inline templates.

.component('widgetPie', {
   templateUrl: 'dashboard/widgetPie.tpl.html',
   controller: function($log) {
      $log.info('widgetPie: loaded');
   }
})
.component('widgetLine', {
    template: '<h1>Some Content</h1>',
    controller: function($log) {
      $log.info('widgetLine: loaded');
    }
})
Alexei answered 16/11, 2016 at 19:8 Comment(3)
in this case if I have to pass data to widget from ng-include, how is that possible?Solecism
@AnkurArora I haven't worked in angular in a few years :P But I'd imagine a injecting a shared service or store that will allow you to pass the data will allow you to access data in the different components. e.g. controller: function($log, $yourService, $yourStore) { }Hy
Thanks for the response. A service would be great if data is shared but in this case being a component I wanted to have it passed. I sorted it out other way, thanks again.Solecism
C
6

You can do it. Firstly, you need to use wrapper component which helps you compile your dynamic component:

app.component('dynamicWrapper',
    {
        controller: function widgetClientCtrl($scope, $compile, $element) {
                    var self = this;
                    self.$onInit = function () {
                          renderWidget(self.name, self.payload);
                    };
                    function renderWidget(name, payload) {
                          var template = '<' + name;

                          if (payload) {
                             $scope.payload = payload;
                             template += ' payload="payload"';
                          }

                          template += '></' + name + '>';
                          $element.append($compile(template)($scope));
                    }
        },
        bindings: {
            name: '@',
            payload: '=?'
        }
    });

your dynamic component:

app.component('someDynamicComponent', {
    templateUrl: 'yourPath',
    controller: dynamicComponentCtrl,
    controllerAs: 'vm',
    bindings: {
        payload: '<'
    }
});

and then:

<li ng-repeat="(name, payload) in vm.dynamicComponents">
      <dynamic-wrapper name="{{name}}" payload="payload"></dynamic-wrapper>
</li>
Childbearing answered 20/11, 2018 at 17:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.