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.sstatic.net/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

2 Answers 2

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);
    };
});
Sign up to request clarification or add additional context in comments.

2 Comments

Thank your for your comment. That could work. Will give it a try and post the result.
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!
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.

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.