Chase Florell
Chase Florell

Reputation: 47417

How to get ng-model value inside custom directive

I've searched here on SO and tried the answers I found, but I can't seem to get the model value out of the ngModel of my custom directive.

enter image description here

Here's the directive

/*
 *usage: <markdown ng:model="someModel.content"></markdown>
 */
breathingRoom.directive('markdown', function () {
    var nextId = 0;
    return {
        require: 'ngModel',
        replace: true,
        restrict: 'E',
        template: '<div class="pagedown-bootstrap-editor"></div>',
        link:function (scope, element, attrs, ngModel) {

            var editorUniqueId = nextId++;
            element.html($('<div>' +
                '<div class="wmd-panel">' +
                '<div id="wmd-button-bar-' + editorUniqueId + '"></div>' +
                '<textarea class="wmd-input" id="wmd-input-' + editorUniqueId + '">{{modelValue()}}' +
                '</textarea>' +
                '</div>' +
                '<div id="wmd-preview-' + editorUniqueId + '" class="wmd-panel wmd-preview"></div>' +
                '</div>'));

            var converter = new Markdown.Converter();

            var help = function () {
                // 2DO: add nice modal dialog
                alert("Do you need help?");
            };

            var editor = new Markdown.Editor(converter, "-" + editorUniqueId, {
                handler: help
            });

            editor.run();


            // local -> parent scope change (model)
            jQuery("#wmd-input-" + editorUniqueId).on('change', function () {
                var rawContent = $(this).val();
                ngModel.$setViewValue(rawContent);
                scope.$apply();
            });

            // parent scope -> local change
            scope.modelValue = function () {
                console.log('modelvalue - ', ngModel.$viewValue);
                return ngModel.$viewValue;
            };
        }
    };
});

And here's the HTML

<markdown ng-class="{error: (moduleForm.Description.$dirty && moduleForm.Description.$invalid) || (moduleForm.Description.$invalid && submitted)}"
          id="Description" 
          name="Description" 
          placeholder="Description" 
          ng-model="module.description" 
          required></markdown>   

The problem here is that the output is simply

{{modelValue()}}

I also tried creating a private method

function getModelValue() {
    console.log(ngModel.$viewValue);
    return ngModel.$viewValue;
}

and then change the one template line to

'<textarea class="wmd-input" id="wmd-input-' + editorUniqueId + '">' + getModelValue() +

but then the output is

NaN

Where am I going wrong?


if it matters, here's the order of my scripts (not including vendor scripts)

<script src="app.js"></script>
<script src="directives/backButtonDirective.js"></script>
<script src="directives/bootstrapSwitchDirective.js"></script>
<script src="directives/markdownDirective.js"></script>
<script src="directives/trackActiveDirective.js"></script>
<script src="services/alertService.js"></script>
<script src="services/roleService.js"></script>
<script src="services/moduleService.js"></script>
<script src="services/changePasswordService.js"></script>
<script src="services/userService.js"></script>
<script src="controllers/usersController.js"></script>
<script src="controllers/userController.js"></script>
<script src="controllers/moduleController.js"></script>
<script src="controllers/modulesController.js"></script>

Upvotes: 18

Views: 49383

Answers (4)

Craig Squire
Craig Squire

Reputation: 2141

The HTML your inserting isn't getting compiled. It's easiest just to move it into your template, or investigate using ng-transclude. Here's an example of moving it into your template.

plunker

breathingRoom.directive('markdown', function () {
    var nextId = 0;
    return {
        require: 'ngModel',
        replace: true,
        restrict: 'E',
        template: '<div class="pagedown-bootstrap-editor"><div class="wmd-panel">' +
                '<div id="wmd-button-bar-{{editorUniqueId}}"></div>' +
                '<textarea class="wmd-input" id="wmd-input-{{editorUniqueId}}">{{modelValue()}}' +
                '</textarea>' +
                '</div>' +
                '<div id="wmd-preview-{{editorUniqueId}}" class="wmd-panel wmd-preview"></div>' +
                '</div></div>',
        link:function (scope, element, attrs, ngModel) {

            scope.editorUniqueId = nextId++;

            // parent scope -> local change
            scope.modelValue = function () {
                console.log('modelvalue - ' + ngModel.$viewValue);
                return ngModel.$viewValue;
            };
        }
    };
});

Upvotes: 14

Kniganapolke
Kniganapolke

Reputation: 5413

Look at angular's parse service. It enables you get and set the value of property referenced in ng-model.

link: function(scope, elem, attrs) {
    var getter = $parse(attrs.ngModel);
    var setter = getter.assign;
    var value = getter(scope);
    setter(scope, 'newValue');
}

Upvotes: 3

Nahn
Nahn

Reputation: 3256

The simplest way is:

If ngModel variable is on the top level of the scope

link: function(scope, elem, attrs) {
    if (attrs.ngModel) {
        var myModelReference = scope[attrs.ngModel];
    }
}

If ngModel refers to a property deeply nested on the scope (best way)

(so, for scope.prop1.prop2 , attrs.ngModel will be "prop1.prop2"), and since you can't just look up scope['prop1.prop2'], you need to dig in by converting the string key to actual nested keys.

For this I recommend Lodash _.get() function

link: function(scope, elem, attrs) {
    if (attrs.ngModel) {
        var myModelReference = _.get(scope, attrs.ngModel);
    }
}

Upvotes: 2

Ben Foster
Ben Foster

Reputation: 34830

You weren't actually resolving your model as the {{modelValue()}} expression was just part of the HTML string your were building in the link function.

You should move the editor markup to the template so that you can bind to ng-model.

Assuming the goal is to create the necessary HTML markup for the Markdown Editor and then show the preview of the converted markdown I would split this into two roles:

  1. A directive for the custom markup
  2. A filter for actually converting the value to markdown:

Markup:

<div ng-app="app" ng-controller="DemoCtrl">
    <h3>Markdown editor</h3>
    <markdown-editor ng-model="markdown"></markdown-editor>
</div>

JavaScript:

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

app.filter('markdown', function () {
    return function (input) {
        return input.toUpperCase(); // this is where you'd convert your markdown
    };
});

app.directive('markdownEditor', function () {
    return {
        restrict: 'E',
        scope: {
            ngModel: "="
        },
        template:
            '<div>' +
            '<textarea ng-model="ngModel"></textarea>' +
            '<div class="preview">{{ ngModel | markdown }}</div>' +
            '</div>'
    };
});

app.controller('DemoCtrl', function ($scope) {
    $scope.markdown = "**hello world**";
});

The = scope sets up a two way binding on the property passed to ng-model and {{ ngModel | markdown }} pipes the value of ngModel to the markdown filter.

http://jsfiddle.net/benfosterdev/jY3ZK/

Upvotes: 10

Related Questions