guyja
guyja

Reputation: 917

How can I pass a model into custom directive

My goal is to pass the projectName model from my MainController to my custom contenteditable directive.

I have the following controller:

app.controller("MainController", function($scope){
    $scope.projectName = "Hot Air Balloon";
});

Here is how I'm calling the directive:

<div class="column" ng-controller="MainController">
    <h2 contenteditable name="myWidget" ng-model="projectName" strip-br="true"></h2>
    <p>{{projectName}}</p>
</div>

I've gotten the contenteditable directive working by following this tutorial: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController

If I understand the docs correctly then Angular is not going to use the model I want it to. Instead its going to create a new model w/ scope local to the contenteditable directive. I know that I can add an isolate scope to the directive but I don't know how to use the model passed to the isolate scope within the link function.

I have tried something like the following which didn't work ...

<h2 contenteditable item="projectName"></h2>

--- directive code ---

scope: {
    item: '=item'
}

link: function(){
    ...
    item.$setViewValue(...)
    ...
}

--- my original directive call --

<div class="column" ng-controller="MainController">
    <h2 contenteditable name="myWidget" ng-model="projectName" strip-br="true"></h2>
    <p>{{projectName}}</p>
</div>

--- my original controller and directive ---

app.controller("MainController", function($scope){
    $scope.projectName = "LifeSeeds";
});

app.directive('contenteditable', function(){
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function(scope, element, attrs, ngModel){
            console.log(ngModel);

            if(!ngModel) return;

            console.log(ngModel.$viewValue);

            ngModel.$render = function(){
                element.html(ngModel.$viewValue || '');
            };

            element.on('blur keyup change', function(){
                scope.$apply(read);
            });
            read();

            function read(){
                var html = element.html();
                if(attrs.stripBr && html == '<br>'){
                    html = '';
                }
                ngModel.$setViewValue(html);
            }

        }
    };
});

Upvotes: 0

Views: 2105

Answers (2)

guyja
guyja

Reputation: 917

Working solution: http://plnkr.co/edit/Lu1ZG9Lpx2sl8CYe8FCx?p=preview

I mentioned that I tried using an isolate scope in my original post.

<h2 contenteditable item="projectName"></h2>

This was actually the correct approach so ignore my full original example using the model argument of the directive's link function.

link: function(scope, element, attrs, ngModel)

The reason the isolate scope approach did not work was because $scope.projectName stored a primitive instead of an object. I did not understand some javascript basics. Mainly, I did not know that primitive types were passed to functions by value.

Primitives are passed by value in javascript. Consequently, changes made to primitive values within a function do NOT change the value of the variable passed to the function.

function changeX(x){
    x = 5;
}
x = 4;
changeX(x);
console.log(x) // will log 4 ...  Not 5

However, objects passed to functions in javascript are passed by reference so modifications to them within the function WILL be made to the variable passed into the function.

My problem was in how I declared the scope within the MainController.

I had:

$scope.projectName = "LifeSeeds";

That's a primitive. When I passed projectName to the directive I was passing a primitive.

<h2 contenteditable item="projectName"></h2>

Thus, changes to the editable element were being made to the value within the directive but not to the value stored in the MainController's scope. The solution is to store the value within an object in the MainController's scope.

// correct
$scope.project = {
    html: "Editable Content"
};

// wrong
$scope.projectName = "Editable Content"

Upvotes: 1

Benoit Tremblay
Benoit Tremblay

Reputation: 728

You can use ng-model with your own directive. To make sure it is included, you can use the attribute require like this:

app.directive("myDirective", function(){ return { require:"ngModel", link: function(scope, element, attr, ngModel){ console.log(ngModel); } } });

Then, you can code whatever behavior your want of ng-model within your directive.

Upvotes: 2

Related Questions