2

Have a bit of an interesting issue (at least I think so:) )

Changed my app to use a factory instead of having everything in the controller, all considered good practice (or so I have heard), the factory takes care of a bunch of data, all kinds really, and different functions handles different calls.

The one I am having a problem with is one that generates a bunch of buttons based on data received from a database. A full version of it would not make much sense, but below is a proof of concept just adding a hello button.

learnByPlay.factory('padArea', function($window, $http, $q, $compile){
    factory.loadButtons = function() { 
        var newButton = '<button class="padbtn" ';
        newButton = newButton + 'data-ng-click="sayHello(\"Angular\")" ';
        newButton = newButton + '<br/>'+Hello +'</button>';
        console.log(newButton);
        var padElement = $compile(newButton); //This should be the compiled button
        console.log(padElement);
        $('#newButton').html(padElement);
    } 

Here is my problem, the compiler requires the $scope, which is not accessible in the factory, but I would rather want to avoid generating the code then compiling it in the controller, as the controller does not know how many buttons or where they are going, that data is all in the factory.

========= UPDATE =========

Please note: I do not believe that ng-repeat is an option in this case as the data generated requires compiling (ng-click can not be included in a ng-repeat directly as ng-repeat will not compile new DOM object for ng-click events, that is my understanding at least).

========= UPDATE 2 =======

This is not a question about how you could use ng-repeat, I know that it is an awesome tool, and that it can do similar thing, but in this case it will not work. As far as I can tell it has to do with that ng-repeat does not compile dom objects, or potentially can not do so dynamically (I do not know enough about the compiler to tell why that is, my theory is that ng-repeat just side steps it, if anyone knows I would be happy to know more, or if I am wrong, please tell me)

For more reading:

ng-click not working from dynamically generated HTML
AngularJS + JQuery : How to get dynamic content working in angularjs

So please, and I say this with the greatest level of respect, do not provide me with yet a solution on how to run ng-repeat.

===============================

Any Ideas?

Community
  • 1
  • 1
vrghost
  • 1,084
  • 2
  • 19
  • 42
  • What @vktr saying is right, you should not use factory for DOM generation. factory should only provide data and then controller will act on DOM depending on data provided by factory – jad-panda May 11 '15 at 10:59
  • see this fiddle here http://jsfiddle.net/jigardafda/dg97fyjb/, here i have used directive to do the some work – jad-panda May 11 '15 at 14:46

3 Answers3

2

Here's the similar example to what you need.

http://jsfiddle.net/jigardafda/dg97fyjb/1/

HTML

<div ng-app="myApp">
    <div ng-controller = "myCtrl">
        <my-button ng-repeat="btn in btns" conf="btn"></my-button>
    </div>
</div>

JS

var myApp = angular.module('myApp', []);

myApp
    .factory('btnsfactory', function($compile){
        var btnList = [
            {
                id: "1",
                name: "button1",
                command: "alert(\"You pressed 1\")"
            },
            {
                id: "2",
                name: "button2",
                command: "alert(\"You pressed 2\")"
            }
        ];

        var obj = {};

        obj.getButtonsList = function(ele, scope){
            return btnList;
        };

        return obj;
    })
    .directive('myButton', function(){
        return {
            restrict: 'E',
            scope: {
                conf: '='
            },
            template: "<button id='{{conf.id}}' ng-click='clickfn(conf)'> {{conf.name}} </button",
            link: function(scope, ele, attr){
                // we can also use eval but eval is evil
                var fn = new Function(scope.conf.command);
                scope.clickfn = function(conf){
                    console.log(conf)
                    fn();
                };
            }
        };
    })
    .controller('myCtrl', function($scope, btnsfactory){
        $scope.btns = btnsfactory.getButtonsList();
    });
jad-panda
  • 2,509
  • 16
  • 22
  • http://jsfiddle.net/7xebmn29/3/ see this and let me know, this is what you want or something else ? – jad-panda May 11 '15 at 13:47
  • Thank you very much jad-panda, rewriting yours (not that good at js-fiddle) to use ng-click depending on data stored in a variable. – vrghost May 11 '15 at 13:58
  • Thank you very much jad-panda, I took your example (thank you very much) and changed it to show what the problem is. http://jsfiddle.net/e979hu3k/ _Note that the command is now a string provided in the JSON annotated data._ – vrghost May 11 '15 at 14:02
  • jad-panda, that is very nice of you, and I am grateful, I guess the solution will be to compile it, which works well inside the controller, but if you move the functionality to a factory you end up in trouble, because compile required scope, and scope is not available in a factory. – vrghost May 11 '15 at 14:14
  • best options is to warp this kind of things inside directive and do all this things inside link function – jad-panda May 11 '15 at 14:18
1

Your factory should offer the data, and the controller takes care about view logic, creating a set of buttons can be done easly with ng-repeat. This is an example, considering you have an array called buttons in your controller, which can be retrieved from the factory.

<button ng-repeat="button in buttons" ng-click="btnFunc(button.id)">{{button.name}}</button>
vktr
  • 149
  • 9
  • I can not use ng-repeat as part of the logic (ng click function etc) is provided by the database, so the code need to be compiled. Also, why is it that I should not compile in the factory. As the factory is the only part of the application that currently knows what needs to be drawn (based on external data), so to remedy this I would have to get the factory to "call" the controller in order to get the controller to compile the data specified by the database to the factory. – vrghost May 11 '15 at 11:40
1

three things You might be doing wrong:

  1. using the $compile service must be done with a scope:

    var padElement = $compile('<button/>')(scope);
    $('#newButton').html(padElement);
    
  2. interacting with the DOM should be done in most cases from a directive. not a service(factory in your case), and not in a controller.

    this way, in the directive link function, you get a scope you can use compiling new html.

  3. You could use the ng-repeat directive to repeat the buttons, or write your own directive.

MoLow
  • 3,056
  • 2
  • 21
  • 41
  • Cheers, will see if I can get it working via a directive, just not certain how I go about getting a directive to use dynamic data to generate it. Worst case scenario I might have to pass it all into the controller again and compile it there. – vrghost May 11 '15 at 14:08
  • You could pass the directive a parameter, which is set in the controller. You have 3 arguments in a directive link function: element,scope,attrs. – MoLow May 11 '15 at 14:14