3

I'm writing an angularjs app (with a node/express backend). I have in my scope an object that gets populated (and repopulated) asynchronously via a service.

The contents of the object is a series of "question" objects, which for simplicity have a "text" and a "type" attribute.

What I'm trying to achieve is to be able to have an angular directive per type, and for these to render properly. So for example if the server returns [{type:'booleanquestion',text:'Is the sky blue?'}] I would create an element , and then the booleanquestion directive will kick in and render it appropriately.

So far I have defined a "questionset" directive which works well, watching the questions collection and compiling the required stuff so the directives display correclt.y

Html

<div questionset="questions"/>

app.js

...
scope.questions = questions; // e.g. [{text: 'some text',type: 'boolean'}]

directives.js

angular.module('myApp').directive('questionset',function($compile){
    return {
        transclude: 'true',
        compile: function(element,attr,linker){
            return function($scope,$element,$attr){
                var elements = [],
                    parent = $element.parent();

                $scope.$watchCollection('questions',function(questions){
                    var block, i, childScope;

                    if(elements.length > 0)
                    {
                        for( i = 0; i < elements.length ; i++)
                        {
                            elements[i].el.remove();
                            elements[i].scope.$destroy();
                        }
                        elements = [];
                    }
                    if(!questions) return;

                    for(i = 0; i < questions.length; i++)
                    {
                        childScope = $scope.$new();
                        childScope['question'] = questions[i];
                        var html = '<div class="'+questions[i].type+'"/>';
                        var e = angular.element(html);
                        block = {};
                        block.el = e;
                        block.scope = childScope;
                        $compile(e)(childScope);
                        element.append(e);
                        elements.push(block);
                    }
                });
            }
        }
    }
});

// For example, one type might be "boolean"
angular.module('myApp').directive('boolean',function($compile){
    return {
        restrict: 'AC',
        template: '<div class="question">{{question.text}}</div>',
        replace: true,
        link: function(scope,elem,attrs,ctrl){
            …….
       // for some reason, $compile will not be defined here?
        }
    };
});

Whilst this is working OK, I have 2 questions

1). Is this the "right" angular way to do this? This is my first angular project and it seems I've jumped in rather at the deep end (or that's how it feels anyway)

2). My next goal is for the question.text to be able to contain HTML and for that to be "compiled". For example the text might be

"Is the <strong>sky</strong> blue?"

I'm not sure how to make this work - as my comment in the code suggest, for some reason $compile is not being injected into my boolean directive. Perhaps this is because I've manually created that child scope for it? Is it right I'm trying to $compile again the contents of the element? I feel like this last bit is probably very simple, but I'm not seeing it.

1 Answer 1

2

1) I don't know. In my opinion, the overall approach seems very nice; just need to polish it, as we'll see below.

2) Maybe $compile is not available at the nested function because it is not used in the parent level. Try referencing $compile in the parent function to see if this is really the reason:

angular.module('achiive-vision').directive('boolean',function($compile){
    var justTesting = $compile;
    return {
        restrict: 'AC',
        template: '<div class="question">{{question.text}}</div>',
        replace: true,
        link: function(scope,elem,attrs,ctrl){
            …….
       // check if $compile is defined here now
        }
    };
});

I would simplify things by changing the questionset elements html to:

var html = '<div class="question ' + questions[i].type + '">'
           + questions[i].text + '</div>';

This way, you don't need to compile again, and you will already have HTML support.

Also, it is a bit strange to have

var html = '<div class="'+questions[i].type+'"/>';

above, and then you will REPLACE it by a similar markup:

template: '<div class="question">{{question.text}}</div>',

...and then you want to COMPILE this again...

In my opinion, you don't need replace: true and you don't need the template at the question type directives. Implement the behaviour and anything else on them, but let the parent questionset include the question text and compile it - everything is ready for this.

Also, a detail: I wouldn't user var html and var e inside a loop. I would add them to the list above:

var block, i, childScope, html, e;

(Also, I would avoid naming variables as "i" and "e"...)

These are my comments regarding your code. As I said, I think the approach is very nice, not to say "advanced" for someone who is beginning with AngularJS. I myself am using it for less than two months, only.

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

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.