injecting directives on the fly

so I have a directive:

angular.module('mymodule').directive('myNote', function() {
    var fnLink = function(scope, element, attrs){
        console.log('in directive', arguments);
    };

    return {
        restrict: 'E',
        template: [
            '<div class="note" data-id="{{note._id}}">',
                '<span>{{note.content}}</span> ',
            '</div>'
        ].join('\n'), 
        link: fnLink
    };
});

and, in another directive, that manages the container where these "notes" will appear:

Note.find({
    user: $scope.global.user._id,
    module: $scope.module._id
}).then(function(response) {
    $scope.notes = response;
    angular.forEach($scope.notes, function(note){
            $scope.note = note;
        element.append($('<my-note>'));
    });
    $compile(element)($scope);
});

Right now the HTML is being rendered as it should; I have two problems:

  1. the fnLink function is not being run,
  2. I don't know how to connect each note from the forEach to its own instance of the directive

Update the way the code is set up, every note rendered will be bound to the last note in the $scope.notes array. How can I make them individual?

am I approaching this completely wrong?
Thank you

Upvotes: 2

Views: 173

Answers (2)

After many hours investigating, I came to the solution to this problem;
this talk helped me a lot.
I need to use Angular Directive's Isolated Scope. Here's how it works:

//READER Directive
var initNotes = function($scope, Note, element, $compile){
    Note.find({
        user: $scope.global.user._id,
        module: $scope.module._id
    }).then(function(response) {
        $scope.notes = response;
        angular.forEach($scope.notes, function(note){
            var highlighted = element.find('span.highlighted');
            var html = '<my-note _id="'+note._id+'" content="'+note.content+'"></my-note>';
            highlighted.after($compile(html)($scope));
        });
    });
};

//NOTES Directive
angular.module('oowli').directive('myNote', function() {

    var fnLink = function($scope, element, attrs){
        element.on('keydown', '.note span', noteKeyDown($scope));
        element.on('click', '.edit', editClick($scope));
        element.on('click', '.remove', removeClick($scope));
    };

    return {
        restrict: 'E',
        template: [
            '<div class="note" data-id="{{id}}">',
                '<div class="note-buttons">',
                    '<a href="#" class="glyphicon glyphicon-pencil edit"></a>',
                    '<a href="#" class="glyphicon glyphicon-remove remove"></a>',
                '</div>',
                '<span>{{content}}</span> ',
            '</div>'
        ].join('\n'), 
        scope: {
            id: '=_id',
            content: '@content'
        },
        link: fnLink
    };
});

What I pass as attributes of the directive are mapped to the scope in the myNote directive.

Upvotes: 0

Ben Lesh
Ben Lesh

Reputation: 108501

You need to $compile the element to wire up the directives. And pass a $scope to it:

$compile(element)($scope);

I wrote a blog entry a while back on how $compile works, but basically it does this:

  1. Start at the element passed to it and traverse all of its children.
  2. At each step look to see if any directives are matched and set up their linking functions to be run with one function call, your "compiled view".
  3. When the "compiled view" is called, pass the proper scope to each directive (e.g. the same scope, a child scope, or an isolated scope).
  4. Now it's wired up.

Edit: follow up on your question:

am I approaching this completely wrong?

Well. Honestly? Maybe? It's hard to say without seeing more code.

From what I've read, it seems like you have nested directives that "manage notes". Generally speaking this is probably the wrong approach.

Quick run down of Controllers, Services and Directives and what they're used for (VERY general):

  • Directives:
    • Setting up bindings between DOM and Scope.
    • Creating reusable views (partials) with functionality.
  • Services:
    • Used for encapsulating shared logic.
    • Used for managing shared data.
  • Controllers:
    • Used for setting up the "business logic" for a view.
    • Preparing the model for a view.

Directives are more of the "inner workings" of Angular. You can do a lot of stuff in directives and it's very easy to muddle up your concerns in a directive. It's best to try to keep them simple and to the point. A reusable partial that's just a template and a controller, for example. Or a binding directive that is just a linking function.

You should probably be managing the "notes" between your directives on a service. You can even inject that service into your directives, but it will allow you to separate that concern from your directives to make everything more testable.

This here is VERY tightly coupled:

Let me annotate your own code with what concerns are represented in it:

//In a directive, so DOM and model

// Data Access
Note.find({
    // Model
    user: $scope.global.user._id,
    module: $scope.module._id
}).then(function(response) {

    // Model
    $scope.notes = response;

    // DOM Manipulation
    angular.forEach($scope.notes, function(note){
        // Model
        $scope.note = note;

        // Manual DOM manipulation
        element.append($('<oowli-note>'));
    });

    // Manual call to $compile
    $compile(element)($scope);
});

.. All in a directive, which as I mentioned above should limit it's concerns. So basically, if you go to test this directive, you are going to be dealing with

You might be better off with a repeater?

<oowli-note ng-repeat="note in notes"/>

Then get notes in a controller from a service call?

app.controller('NoteCtrl', function($scope, Note) {
    Note.find($scope.x, $scope.y).then(function(notes) {
       $scope.notes = notes;
    });
});

But, I have no idea what you're actually trying to accomplish... so I'm just giving this advice in hopes that it helps.

Upvotes: 2

Related Questions