2

I'm trying to dynamically generate an ng-model directive using a filter. The main idea is that there is some text in a database. This text has gaps defined by numbers between square brackets ([1], [2], etc.). The purpose is to parse those gaps and turn them into inputs. These inputs should be then binded to a variable using the ng-model directive but I can't get it to work.

Here is my controller:

 app.controller('exerciseTemplateCtrl', ['$http', '$scope', '$sce', '$filter', '$compile',  function($http, $scope, $sce, $filter, $compile){

    // used to test the binding through ng-model
    $scope.answers = [];

    $http.get('encode_exercises.json')
         .then(function(response){
            $scope.firstExercise = response.data;
        });

    $scope.parseHtml = function(input){
        input = $filter('gapToInput')(input);
        return $sce.trustAsHtml(input);
    };

}]);

Here my filter 'gapToInput'

app.filter('gapToInput', function(){
    return function(input){
        return input.replace(/\[[0-9]\]/g, '<input type="text" ng-model="answers">');
    };
});

As you can see I am binding the model using the "answers" variable. Here is my directive:

app.directive('exerciseTemplate', function(){
  return{
    restrict: 'E',
    templateUrl: 'exercise-template.html'
  };
});

The index.html contains the previous directive:

<exercise-template></exercise-template>

And here my template for the previous directive (simplified)

<div ng-controller="exerciseTemplateCtrl as e">
    <div ng-repeat="exercise in firstExercise">
        <div ng-repeat="(questionId, question) in exercise.q">
            <div ng-bind-html="parseHtml(question.d.q)"></div>
        </div>
    </div>
    <p><button>Check</button></p>
</div>

question.d.q contains the text from the database with the gaps ([1], [2], etc.) and it's applying the filter successfully (apologies, I don't have enough reputation to post images):

https://i.stack.imgur.com/W0NoI.png

The problem is that, even if the replacement works, the ng-model directive is not binding each input with the "answers" variable. For what I've been reading, this is because I have to recompile again the template so that Angular parses all of the ng-directives again. Tried doing the following without any luck:

var scope = $scope;
$scope.parseHtml = function(input){
    input = $filter('gapToInput')(input);
    input = $compile(input)(scope);
    return $sce.trustAsHtml(input);
};

I've also followed this thread and tried changing the directive format to the following:

app.directive('exerciseTemplate', ['$compile', '$http', function($compile, $http){
    return {
        restrict: 'E',
        link: function(scope, element, attrs){
            $http.get('exercise-template.html').then(function(result){
                element.replaceWith($compile(result.data)(scope));
            });
        }
    }
}]);

But it's still not binding the model. I'm starting to feel a bit frustrated of how difficult Angular is even with the simplest things so any help would be really appreciated.

Thanks

Community
  • 1
  • 1
mIwE
  • 217
  • 2
  • 3
  • 10

2 Answers2

1

I haven't tested this code, but the point here is that you can split the "gaps" using a filter inline on your ng-repeat. That will return an array of items and you can base your model on that.

<div ng-repeat="exercise in firstExercise">
    <div ng-repeat="(questionId, question) in exercise.q | gapToInput">
        <input ng-repeat="" type="text" ng-model="exercise.q[questionId].answer">
    </div>
</div>

where your filter is like:

app.filter('gapToInput', function(){
    return function(input){
        return input.split(/\[[0-9]\]/g);
    };
});
SoluableNonagon
  • 11,541
  • 11
  • 53
  • 98
  • Thank your for your comment. That could work. Will give it a try and post the result. – mIwE Dec 17 '14 at 19:58
  • Well, I just tested it and it works! I have to fix now some layout problems because some of these inputs can be inside a table cell and adding the input breaks the table format, but maybe I can find a solution for that. Thank you very much! – mIwE Dec 18 '14 at 09:17
0

After some investigation I managed to find a solution to my initial problem. Even if SoluableNonagon answer's work, I am going to post another way of dealing with my problem.

The idea is pretty similar to my second attempt of recompiling the template but I was probably missing something so here is the complete working code:

Directives:

app.directive('exerciseTemplate', function(){
    return{
        restrict: 'E',
        scope: true,
        templateUrl: '/exercise-template.html'
    };
});

app.directive('qText', ['$compile', '$timeout', function($compile, $timeout){
    return {
        restrict: 'E',
        link: function(scope, element, attrs){
            $timeout(function(){
                var output = element.html().replace(/\[[0-9]\]/g, '<input type="text" ng-model="answers">');
                element.html(output);
                $compile(element.contents())(scope);
            });
        }
    }
}]);

exercise-template.html:

<q-text ng-bind-html="parseHtml(question.d.q)"></q-text>

This will get the inner HTML of <q-text></q-text> and pass it to the link function of the directive. Then I use the html() function of jQLite to retrieve the HTML and replace each gap with an input, after this I just have to put back the HTML in the element and recompile the template. After that every single input will be binded with the "answers" variable through the ng-model directive.

I had to use $timeout because otherwise, the html() method was returning null, probably because the DOM was not yet ready. No idea if this is a good practise or not, but it's the only way I could find to make it work.

Any suggestion or recommendation would be very appreciated.

mIwE
  • 217
  • 2
  • 3
  • 10