1

I have a custom directive with isolated scope, and in it's link function I am setting some additional scope variables like:

templateUrl:"chat.html",
scope:{},
link: function (scope, element, attrs) {
            scope.chat;
            scope.send_txt="";
            scope.use_enter = false;
            scope.other;

in my chat.html I have an input element like

....
...
<textarea ng-model="send_txt" class="form-control" placeholder="Message..." aria-describedby="basic-addon1" style="border:2px solid #0079BE; border-radius:5px;margin-bottom:5px;"></textarea>
...

Normally when I set the scope.send_txt within my link, the value is correctly reflected in the textarea, but as soon as I type something in the textarea the scope.send_txt variable stops updating, as if the textarea now has it's own scope. Any ideas what the issue might be?

EDIT:

Directive:

'use strict';

angular.module('chatApp')
  .directive('chatpane', [
    '$http',
    'socket',
    'message',
    '$mdDialog',
    '$mdMedia',
    function ($http,socket,message,$mdDialog,$mdMedia) {
      return {
        templateUrl: 'app/dashboard/message/chatpane/chatpane.html',
        restrict: 'E',
        scope:{
            thread : "=thread",
            owner : "=owner",
        },
        link: function (scope, element, attrs) {
          /*console.log("chat: ",nv, " : ",ov);*/
            scope.chat;
          scope.send_txt="";
            scope.use_enter = false;
            scope.other;

            scope.$watch("thread",function(nv,ov,scope){
                if(nv){
                    $http.get('api/messages/threads/'+nv._id+'/0').then(function(d){
                                    scope.chat = d.data;
                                    scope.other = (scope.owner!=scope.thread.owner._id)?scope.thread.owner._id:scope.thread.recipient._id;
                                    //mark thread read
                              message.markThreadRead(scope.thread._id);
                                })
                }
            },true);

          scope.$watch("send_txt",function(nv,ov,scope){
            console.log("chat: ",nv, " : ",ov);
          },true);

            scope.$watch('chat', function(newValue, oldValue, scope) {
            if(element.find('.msg_inbox_msgs').length>0){
              element.find('.msg_inbox_msgs').stop(true,true).animate({scrollTop:(element.find('.msg_inbox_msgs')[0].scrollHeight+(element.find('.msg_inbox_msgs>div').length*20))+'px'}, 1000);
            }
            });

            scope.post = function(txt){
                var tmp = txt || scope.send_txt;
                scope.send_txt = "";
                console.log("Posting: ",scope.send_txt);
            if(tmp && _.trim(tmp)!=""){
              $http.post('api/messages',{message:tmp, owner:scope.owner, recipient:scope.other, parent: scope.thread._id}).then(function(d){
                //scope.chat.push(d.data);
                //console.log("Response: ",d);
                //console.log("chat: ",scope.chat);
              });
            }else{
              console.log("Posting: ",scope.send_txt);
            }
            }

          scope.acceptReferralRequest = function() {
            $http.post('api/messages/acceptReferralRequest/'+scope.thread._id).then(function(d){
              scope.thread.status = "accepted";
                });
          }
          scope.ignoreReferralRequest = function() {
            $http.post('api/messages/ignoreReferralRequest/'+scope.thread._id).then(function(d){
              scope.thread.status = "ignored";
                });
          }

            element.bind("keydown keypress", function (event) {
                if(event.which === 13 && scope.use_enter) {
                    scope.post();
                    event.preventDefault();
                }
            });

            scope.refer = function(ev){
              var my_connections = [];
              $http.get('api/users/get-all-connections').then(function(d){
                for(var i in d.data){
                  var ii = _.findIndex(my_connections,{'_id':d.data[i]._id});
                  if(ii!=-1){
                    if(my_connections[ii].relation.indexOf(d.data[i].relation) == -1){
                      my_connections[ii].relation = my_connections[ii].relation +", "+d.data[i].relation;
                    }
                  }else{
                    my_connections.push(d.data[i]);
                  }
                }

                //show popup
                var useFullScreen = ($mdMedia('sm') || $mdMedia('xs'));
                $mdDialog.show({
                  controller: function(scope,$mdDialog){
                    scope.connections = my_connections;
                    scope.heading = "Refer";
                    scope.isAuthorized = true;
                    scope.cancel = function(){
                      $mdDialog.cancel();
                    };
                    scope.hide =  function(user_url){
                      $mdDialog.hide(user_url);
                    }
                  },
                  templateUrl: 'app/dashboard/message/chatpane/refer.html',
                  parent: angular.element(document.body),
                  targetEvent: ev,
                  clickOutsideToClose: true,
                  fullscreen: useFullScreen
                })
                .then(function(user_url) {
                  if(scope.send_txt && scope.send_txt!=""){
                    scope.send_txt += " #"+user_url+" ";
                  }else{
                    scope.send_txt = "I would like to recommend #"+user_url+" ";
                  }
                }, function() {
                  scope.status = 'You cancelled the dialog.';
                })
              });

            };



            var handleSelf = function(d){
                if((d.parent == scope.thread._id) && scope.owner==d.owner._id){
                    scope.chat.push(d);
                }
                //console.log("socket self: ",d);
           };
            socket.socket.removeListener('message:posted',handleSelf);
            socket.socket.on('message:posted',handleSelf);

            var handleRemote = function(d){
                if((d.parent == scope.thread._id) && scope.owner==d.recipient._id){
                    scope.chat.push(d);
                }
                //console.log("socket remote: ",d);
           };
            socket.socket.removeListener('message:unread',handleRemote);
            socket.socket.on('message:unread',handleRemote);
        }
      };
    }]);

Template:

{{send_txt}}<div class="msg_inbox_cont" flex="column" flex layout-align="start stretch" ng-if="thread.group!='ask_referral'">
    <div class="msg_inbox_head" flex ng-if="thread.title">
        <div class="msg_topic_img col-sm-1 col-xs-3">
            <img ng-src="api/images/{{msg.currentThread.owner.profile_pic || 'default.png' }}" alt="" width="30"/>
        </div>
        <div class="msg_topic_header col-sm-10 col-xs-9">
            {{ thread.title}}
        </div>
        <div class="msg_topic_desc col-sm-10 col-xs-9">
            {{ thread.message | limitTo:20 }}
        </div>
    </div>
    <div class="msg_inbox_msgs" flex>
        <div ng-class="ch.owner._id == owner ? 'msg_inbox_msg mine' : 'msg_inbox_msg other'" ng-repeat="(key, ch) in chat">
            {{ch.message}}
        </div>
    </div>
    <div class="msg_inbox_input" flex>
        <div class="input-group">
            <textarea ng-model="send_txt" class="form-control" placeholder="Message..." aria-describedby="basic-addon1" style="border:2px solid #0079BE; border-radius:5px;margin-bottom:5px;"></textarea>
        </div>
        <span><input type="checkbox" ng-model="use_enter" style="vertical-align: text-top;" /> Submit on enter</span>
        <input type="button" ng-if='send_txt' class="pull-right btn btn-default btn-primary" ng-click="post()" value="Send"/>
        <input type="button" ng-if='!send_txt' class="pull-right btn btn-default btn-disabled" disabled value="Send"/>
    </div>
</div>


<!-- Ask Referral -->
<div class="msg_inbox_cont" flex="column" flex layout-align="start stretch" ng-if="thread.group=='ask_referral'">
    <div class="msg_inbox_head" flex ng-if="thread.owner._id!=owner">
        <div class="msg_topic_img col-sm-1 col-xs-3">
            <img ng-src="api/images/{{thread.owner.profile_pic || 'default.png' }}" alt="" width="30"/>
        </div>
        <div class="msg_topic_header col-sm-10 col-xs-9">
            {{ thread.owner.name }}
        </div>
        <div class="msg_topic_desc col-sm-10 col-xs-9">
            has asked for a referral
        </div>
    </div>
    <div class="msg_inbox_head" flex ng-if="thread.owner._id==owner">
        <div class="msg_topic_img col-sm-1 col-xs-3">
            <img ng-src="api/images/{{thread.owner.profile_pic || 'default.png' }}" alt="" width="30"/>
        </div>
        <div class="msg_topic_header col-sm-10 col-xs-9">
            You
        </div>
        <div class="msg_topic_desc col-sm-10 col-xs-9">
            have asked for a referral
        </div>
    </div>
    <!-- accepted -->
    <div class="msg_inbox_msgs" flex ng-if="thread.status=='accepted'">
        <div ng-class="ch.owner._id == owner ? 'msg_inbox_msg mine' : 'msg_inbox_msg other'" ng-repeat="(key, ch) in chat">
            {{ch.message}}
        </div>
    </div>
    <!-- accepted -->
    <!-- available / pending acceptance -->
    <div class="msg_inbox_msgs" flex ng-if="thread.status=='available'">
        <div ng-class="ch.owner._id == owner ? 'msg_inbox_msg mine' : 'msg_inbox_msg other'" ng-repeat="(key, ch) in chat">
            {{ch.message}}
        </div>
        <div style="clear:both;" ng-if="thread.owner._id!=owner">
          <input style="margin:10px;" type="button" class="pull-left btn btn-primary" value="Accept" ng-click="acceptReferralRequest()"/>
          <input style="margin:10px;" type="button" class="pull-left btn btn-warning" value="Ignore" ng-click="ignoreReferralRequest()"/>
        </div>
    </div>
    <!-- available / pending acceptance -->
    <!-- available / pending acceptance -->
    <div class="msg_inbox_msgs" flex ng-if="thread.status=='ignored'">
        <div ng-class="ch.owner._id == owner ? 'msg_inbox_msg mine' : 'msg_inbox_msg other'" ng-repeat="(key, ch) in chat">
            {{ch.message}}
        </div>
        <div style="clear:both;text-align:center;">
          <span style="color:#5e5e5e;font-weight:bold;"><small><em>- Request has been ignored -</em></small></span>
        </div>
    </div>
    <!-- available / pending acceptance -->
    <div class="msg_inbox_input" flex ng-if="thread.status=='accepted'">
        <div class="input-group">
            <textarea ng-model="send_txt" class="form-control" placeholder="Message..." aria-describedby="basic-addon1" style="border:2px solid #0079BE; border-radius:5px;margin-bottom:5px;"></textarea>
        </div>
        <span><input type="checkbox" ng-model="use_enter" style="vertical-align: text-top;" /> Submit on enter</span>
        <input type="button" ng-if='send_txt' class="pull-right btn btn-default btn-primary" ng-click="post(send_txt)" value="Send"/>
        <input type="button" ng-if='!send_txt' class="pull-right btn btn-default btn-disabled" disabled value="Send"/>
        <input type="button" ng-if='thread.owner._id!=owner' class="pull-right btn btn-default btn-primary" ng-click="refer($event)" value="Refer"/>{{send_txt}}
    </div>
</div>
<!-- Ask Referral -->
btdev
  • 61
  • 3
  • Can you post all the code for `chat.html`? This sounds like a typical *"child scope problem"*. You can read more about it [here](http://stackoverflow.com/questions/18342917/angularjs-ng-model-doesnt-work-inside-ng-if). – Cosmin Ababei Mar 18 '16 at 14:51
  • How do you know, how did you check that scope.send_txt is not updating? – Bata Mar 18 '16 at 14:51
  • Provide all directive code, such as the name, etc...and other important so we can find the problem easier and help you. – que1326 Mar 18 '16 at 14:52
  • @Bata I have setup `scope.$watch` for the variable within the directive which responds the first time when setting value from within `link`. @CosminAbabei I'll update the full code for template though it won't be clean – btdev Mar 18 '16 at 14:54

1 Answers1

0

You should always have a . with ng-model or strange things can happen as new scopes may be introduced...

https://github.com/angular/angular.js/wiki/Understanding-Scopes

As specified in the spec:

First ngModel will try to bind to the property given by evaluating the expression on the current scope. If the property doesn't already exist on this scope, it will be created implicitly and added to the scope.

Second: By default, ngModel watches the model by reference, not value. This is important to know when binding inputs to models that are objects (e.g. Date) or collections (e.g. arrays). If only properties of the object or collection change, ngModel will not be notified and so the input will not be re-rendered.

So if you refer directly, your reference might get "lost", which doesn't happen with a ....

Try:

templateUrl:"chat.html",
scope:{},
link: function (scope, element, attrs) {
  scope.info = {
    send_txt: ""
  };

And

<textarea ng-model="info.send_txt" class="form-control" placeholder="Message..." 
    aria-describedby="basic-addon1" style="border:2px solid #0079BE; 
    border-radius:5px;margin-bottom:5px;"></textarea>
malix
  • 3,566
  • 1
  • 31
  • 41
  • 1
    Can you please explain why that is? This did work though, so thanks – btdev Mar 18 '16 at 15:00
  • @MathewBerg Yes it should. See the witnessed behavior and the specs and recommended uses... – malix Mar 18 '16 at 15:05
  • @btdev: Cool! I edited with some explanation... I think your `send_txt `'s are overwriting each other... Can you +1 and accept? – malix Mar 18 '16 at 15:06
  • Thanks! Helps: Contrary to others, I try to be constructive and very parsimonious with my -1's :) – malix Mar 18 '16 at 15:11