Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angularjs - ngModel.$setViewValue is not a function

Here is my plunker and the code I can't get to work starts on line 32 http://plnkr.co/edit/pmCjQL39BWWowIAgj9hP?p=preview

I am trying to apply an equivalent to markdown filter onto a directive... I created the filter and tested with manually applying the filter and it works that way,, but I should only use the filter conditionally when the type of content on directive is set to markdown.

I am trying to accomplish this by updating ng-model >>> ngModel.$setViewValue(html) but I am getting an error ngModel.$setViewValue is not a function.. which makes me thing that the controller is not recognized although it is required by the directive.

Here is a working controller:

var app = angular.module('testOne', ["ngResource", "ngSanitize"]);

app.controller('testOneCtrl', function ($scope) {

$scope.product = {
    id:12,
    name:'Cotton T-Shirt, 2000',
    description:'### markdown\n - list item 1\n - list item 2',
    price:29.99
};

}); 

app.directive("myText", function ($parse) {
return {
    restrict: "E",
    require: "?ngModel",
    scope:{
        css: "@class", type: "@type"
    },
    controller: function ($scope, $element, $attrs) {},
    templateUrl: "template.html",
    compile: function(elm, attrs, ngModel){

            var expFn = $parse(attrs.contentType + '.' + attrs.value);
            return function(scope,elm,attrs){
                scope.$parent.$watch(expFn, function(val){
                    scope.exp = { val: val };

                if ( attrs.type == 'markdown'){
                    var converter = new Showdown.converter();
                        var html = converter.makeHtml(val);
                        //scope.exp.val = html;

                        ngModel.$setViewValue(html);
                        ngModel.$render();
                }

                })

                scope.$watch('exp.val', function(val){
                    expFn.assign(scope.$parent, val)
                })
            }
    }
}
})

This is a filter for markdown which works when applied.. (I would consider using the filter if I could figure out the way to conditionally apply it to existing directive but I'd rather do it with ng-model)

/*
app.filter('markdown', function ($sce) {
    var converter = new Showdown.converter();
    return function (value) {
        var html = converter.makeHtml(value || '');
        return $sce.trustAsHtml(html);
   };
});
*/

Here is the directive template

<div ng-class="{{css}}"
        ng-click="view = !view" 
        ng-bind-html="exp.val">
</div>


<div>
    <textarea rows="4" cols="30" ng-model="exp.val"></textarea>
</div>

This is the directive in use:

    <mb-text ng-cloak 
        type="markdown"
        content-type="product"
        value="description"
        class="test-one-text-2">
    </mb-text>
like image 598
GRowing Avatar asked Jun 08 '26 12:06

GRowing


1 Answers

Why ngModel is empty?

  • When using require on a directive the controller is passed as the 4th argument to the linking function. In you code you try to reference it as an argument of the compile function. The controller is only instantiated before the linking phase so it could never be passed into the compile function anyway.

  • The bigger issue is that require can only get a controller of the same element ({ require: 'ngModel' }), or parent elements ({ require: '^ngmodel' } ). But you need to reference a controller from a child element (within the template).

How to get ngModel?

Do not use require at all as you cannot get child element's controller with it.

From angular.element docs:

jQuery/jqLite Extras

controller(name) - retrieves the controller of the current element or its parent. By default retrieves controller associated with the ngController directive. If name is provided as camelCase directive name, then the controller for this directive will be retrieved (e.g. 'ngModel').

Inside the linking function you can get the hold of the controller like so:

 var ngModel = elm.find('textarea').controller('ngModel');

I fixed your directive:

here is a plunker: http://plnkr.co/edit/xFpK7yIYZtdgGNU5K2UR?p=preview

template:

<div ng-class="{{css}}" ng-bind-html="exp.preview"> </div>
    
<div>
    <textarea rows="4" cols="30" ng-model="exp.val"></textarea>
</div>

Directive:

app.directive("myText", function($parse) {
  return {
    restrict: "E",
    templateUrl: "template.html",
    scope: {
      css: "@class",
      type: "@type"
    },
    compile: function(elm, attrs) {

      var expFn = $parse(attrs.contentType + '.' + attrs.value);
      
      return function(scope, elm, attrs) {

        scope.exp = {
          val: '',
          preview: null
        };

        if (attrs.type == 'markdown') {
          var converter = new Showdown.converter();

          var updatePreview = function(val) {
            scope.exp.preview = converter.makeHtml(val);
            return val;
          };

          var ngModel = elm.find('textarea').controller('ngModel');
          ngModel.$formatters.push(updatePreview);
          ngModel.$parsers.push(updatePreview);
        }

        scope.$parent.$watch(expFn, function(val) {
          scope.exp.val = val;
        });

        scope.$watch('exp.val', function(val) {
          expFn.assign(scope.$parent, val);
        });
      };
    }
  };
});
like image 133
Ilan Frumer Avatar answered Jun 10 '26 02:06

Ilan Frumer



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!