Diana Vermilya
Diana Vermilya

Reputation: 33

How do I call a function using $scope from HTML added by a directive in angularjs

I moved the following HTML from a controller to a directive, as part of HTML I wanted subbed in when a button was clicked. The problem is that now the save button(below) does nothing. I tried changing the ng-click to a different function I included in the directive code, and that worked, but I know that a function that is saving data belongs in the controller. How can I access the saveStudent function? (When the HTML was right in the view, I used to use $scope.saveStudent in the controller.)

I'm sorry if this question has been asked before. I tried my best to look through the related questions, but I'm new to angular so it's likely I missed a similar one. Thanks!

<button class="btn-bl btn-bl-red pull-right" ng-click="saveStudent(addStudentForm);" ng-enable="addStudentForm.$valid">
    SAVE
</button>

This is the function (within the link function in the directive) that loads the new template when a button is clicked.

        function studentEditForm(element) {
            var templateUrl = baseUrl + 'settings-edit.html';
            templateLoader = $http.get(templateUrl, {
                cache : $templateCache
            }).success(function(html) {
                element.html(html);
            }).then(function() {
                var compiled = $compile(element.html())(scope);
                element.html(compiled);
            });

        };

        templateLoader.then(function() {
            var compiled = $compile(element.html())(scope);
            element.html(compiled);
        });

I'm wondering if maybe the fact that the HTML is being compiled by the directive is the problem?

Upvotes: 3

Views: 20978

Answers (2)

LetterEh
LetterEh

Reputation: 26696

m59 isn't wrong.

But sometimes, the real answers aren't that easy (for a thousand different reasons).

There are a few options to help.

Assuming that your directive IS a child of the HTML where your controller sits:

<div ng-controller="myController">
    <my-save-directive></my-save-directive>
</div>

You have a few options.

I'm going to assume that you've got a bunch of long, drawn out business logic in your controller, and a bunch of interaction-handling in your directive... ...I'm also going to assume that you want to hope this directive can be reusable (in an ideal world), or at least, should not be leaking things in and out of it, aside from what you give it express permission to touch.

So if your controller looks like this:

["$scope", "$http", function ($scope, $http) {

     $scope.doStuff = function () { };
     $scope.students = [];
     $scope.saveStudent = function (particularStudent) {
         $http.post(particularStudent);
     };

}]

Then your options might look like this:

//mySaveDirective.js
angular.module("...").directive("mySaveDirective", function () {

    var directive = {
        template : "<button ng-click='save(subject)'>SAVE</button>",
        scope : { // <----here's the key bit
            save    : "&",
            subject : "="
        },
        link : function ($scope, el, attrs, ctrl) {
            // .......
        }
    };

    return directive;

});

That scope property will make all the difference:
Defining it in the directive object (JS) like so...

scope : {
   camelName : "&",
   otherName : "="
}

...and using it in the directive's HTML declaration (NOT(!!!) the actual template)

<my-save-directive  camel-name="saveStudent(subject)" other-name="student"></my-save-directive>

What does that get us?

The "&" means that the parent controller is giving you a function (or any expression, which will be wrapped in an implicit function), which can be called inside the directive, but will fire in the parent's scope.

<div controller="myController">
    <my-directive directive-method-name="controllerFunc(directiveArgName)"></...>

So inside of your directive's actual template, you can now write:

<button ng-click="directiveMethodName({ directiveArgName : scopeObj })">

The reason it holds an object with a property with the same name, is because Angular parses the definition of the method the same way it parses function definitions for DI in other places.

scope : {
    propName : "="
}

Means that you can set up a 2-way binding on that property, between the parent and the directive.
It could be an object or a value, or it could be a method from one end or the other, or a messaging system/event system to communicate.
If one end changes the value, it changes for the other end, too.

So if I'm assuming

// StudentController
$scope.student = {};
$scope.saveStudent = function (student) { };

Then the directive JS can look like this:

// SaveDirective
var directive = {
    scope : {
        subject : "=",
        save    : "&"
    }
};

And the HTML where the directive is declared (not compiled):

<div ng-controller="studentController">
    <div save-directive  subject="student" save="saveStudent(subject)">

And finally, the button:

<button ng-click="save({ subject : subject })">SAVE</button>

I hope that makes sense.

Also, there are very, very few times you care about $compile.
Almost none, unless you're writing something that needs to do heavy modification to the template, before using it (at which point, why not write a different template?).

Typically, in a directive, you care more about the link method than the compiler.

The difference is that the compiler runs one time on the template, period.
If you use the same directive in 8 different places, the compiler will still only have run once (unless you force it, for some reason).
The link function is run for each instance of the template (the final cloned node that you're given), and you also have access to the directive's scope, and proper linking to everything that sits on top.

...It's 2am, I hope that helped...

Upvotes: 3

m59
m59

Reputation: 43755

With limited code available in your post, this is the best I can do. I hope it helps!

The original attributes will be copied over to the new element, so the references should all work, even if you gave the button an isolated scope.

Live demo (click).

  <button    
    my-directive=""
    ng-click="saveStudent()"
    ng-enable="addStudentForm.$valid"
  ></button>

JavaScript:

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

app.controller('myCtrl', function($scope) {
  $scope.saveStudent = function() {
    console.log('saving student!');
  };
});

app.directive('myDirective', function() {
  return {
    replace: true,
    template: '<button class="btn-bl btn-bl-red pull-right">SAVE</button>'
  };
});

Upvotes: 0

Related Questions