4

UPDATE1: developed the plunker sample that will reproduce the problem. See below.

I have a strange problem in my project, where it appears in one place only. Finally, I was able to reproduce the problem using plunker sample:

http://plnkr.co/edit/JJbq54?p=preview

In the above sample, see the section "With ng-if" and "Without ng-if", enter something in the input text, and see how the double curly braces not working under ng-if, but ng-bind works fine. Also, if you remove check-if-required from the template sites-and-improvements.html also the problem is solved.


More details below:

I have the the following HTML5 code block:

    <div ng-if="isFullCUSPAP" id="sites_and_imrpovements_comments">
        <div class="form-row">
            <div class="inputs-group">
                <label>WIND TURBINE:</label>
                <div class="input-binary">
                    <label>
                       <input type="radio" id="wind_turbine" 
                              name="wind_turbine"
                              ng-model="$parent.wind_turbine" 
                              value="Yes" force-model-update />
                       Yes
                    </label>
                </div>
                <div class="input-binary">
                    <label>
                      <input type="radio" id="wind_turbine"
                             name="wind_turbine" 
                             ng-model="$parent.wind_turbine" 
                             value="No" force-model-update />
                      No
                    </label>
                </div>
                <span ng-bind="wind_turbine"></span>
                <span>wind_turbine = {{wind_turbine}}</span>
            </div>
        </div>
    </div>

I know that ng-if will create a new child scope. See above code, scope variable wind_trubine. Only in this HTML5 file, the curly braces {{}} is not working. However, if I use ng-bind it works fine. In other HTML5 files, I have no problem what so ever. This HTML5 is implemented using directive as follows:

app.directive('sitesAndImprovements', function() {
    return {
        restrict: 'E',
        replace:true,
        templateUrl: '<path-to-file>/site-and-improvments.html',
        link: function (scope, elem, attrs) {
            //Business Logic for Sites and Improvements
        }
    } 
})

And, simply, I put it in the parent as follows:

<sites-and-improvements></sites-and-improvements>

The only difference I could see, is that this implementation has two levels of nested ng-if, which would look like the following:

<div ng-if="some_expression">
    ...
    ...
    <sites-and-improvements></sites-and-improvements>
    ...
    ...
</div>

Based on comments, I used controller As notation and defined MainController accordingly. See snapshots below. It seems there is a problem if ng-if is nested with two levels. The scope variable is completely confused. I don't get the same results using ng-bind and double curly braces.

enter image description here

enter image description here enter image description here

enter image description here

If you examine the above snapshots, even though I used controller As notation, you will see that ng-bind gives different results when compared with interpolation using {{}}.

I even changed the default value of wind_turbine to be set as follows in the link function:

scope.MainController.wind_turbine = 'Yes';

I noticed that on page load, everything looks fine, but when I change the value of the input element wind_trubine using the mouse, all related reference are updated correctly except the one that uses {{}}.

Maybe this is because there are two nested levels of ng-if?

Appreciate your feedback.

Tarek

tarekahf
  • 738
  • 1
  • 16
  • 42
  • you're relying on `wind_turbine` being in your scope. `ng-if` creates a new inner scope, so what you should do is have a `controllerAs="vm"` or something like that in the `ng-controller` line (probably this syntax, but I might be wrong), and then use `vm.wind_turbine` instead of directly using `wind_turbine` – Giora Guttsait Jul 21 '17 at 21:16
  • 1
    this looks like a case where the golden rule for angularjs applies: **always use a dot in angular bindings**. (in other words, bind to object properties, not primitives). – Claies Jul 21 '17 at 22:13
  • It is not wise to depend on `$parent` in `ng-model` getting to the correct scope. If there are two levels of scope created by two levels of `ng-if` directives, the value will be placed on a scope that is unavailable to the binding. – georgeawg Jul 21 '17 at 22:30
  • @GioraGuttsait: I followed you recommendation, and still same problem. See updated description above. – tarekahf Jul 21 '17 at 22:31
  • Possible duplicate of [What are the nuances of scope prototypal / prototypical inheritance in AngularJS?](https://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs). – georgeawg Jul 22 '17 at 14:33
  • @georgeawg - I updated the description with more details. This is most likely a bug or some other issue. See the plunker sample I provided. – tarekahf Jul 31 '17 at 19:29

2 Answers2

2

Remove the replace: true from the sites-and-improvements directive:

app.directive('sitesAndImprovements', function() {
    return {
        restrict: 'E',
        ̶r̶e̶p̶l̶a̶c̶e̶:̶t̶r̶u̶e̶,̶
        templateUrl: 'site-and-improvments.html',
        link: function (scope, elem, attrs) {
          //debugger;
        }
    } 
})

It is fighting the check-if-required directive:

app.directive('checkIfRequired', ['$compile', '$timeout', function ($compile, $timeout) {
  return {
    priority: 2000,
    terminal: true,
    link: function (scope, el, attrs) {
      el.removeAttr('check-if-required');

      $timeout(function(){
        //debugger;
        $(':input', el).each(function(key, child) {
          if (child && child.id === 'test_me') {
                angular.element(child).attr('ng-required', 'true');
                }
          if (child && child.id === 'testInput1') {
            //debugger;
                //angular.element(child).attr('ng-required', 'true');
                }
        });
            $compile(el, null, 2000)(scope);
      })
    }
  };
}])

The DEMO on PLNKR.


replace:true is Deprecated

From the Docs:

replace ([DEPRECATED!], will be removed in next major release - i.e. v2.0)

specify what the template should replace. Defaults to false.

  • true - the template will replace the directive's element.
  • false - the template will replace the contents of the directive's element.

-- AngularJS Comprehensive Directive API - replace deprecated

From GitHub:

Caitp-- It's deprecated because there are known, very silly problems with replace: true, a number of which can't really be fixed in a reasonable fashion. If you're careful and avoid these problems, then more power to you, but for the benefit of new users, it's easier to just tell them "this will give you a headache, don't do it".

-- AngularJS Issue #7636

For more information, see Explain replace=true in Angular Directives (Deprecated)

Community
  • 1
  • 1
georgeawg
  • 48,608
  • 13
  • 72
  • 95
0

Another solution posted by AngularJS team here:

https://github.com/angular/angular.js/issues/16140#issuecomment-319332063

Basically, they recommend to convert the link() function to use compile() function instead. Here is the update code:

app.directive('checkIfRequired', ['$compile', '$timeout', function ($compile, $timeout) {
  return {
    priority: 2000,
    terminal: true,
    compile: function (el, attrs) {
      el.removeAttr('check-if-required');
      var children = $(':input', el);
      children.each(function(key, child) {
        if (child && child.id === 'test_me') {
            angular.element(child).attr('ng-required', 'true');
                }
      });
            var compiled = $compile(el, null, 2000);
            return function( scope ) {
                compiled( scope );
            };
    }
  };
}]).directive('sitesAndImprovements', function() {
    return {
        restrict: 'E',
        replace:true,
        templateUrl: 'site-and-improvments.html'
    } 
});

The main problem I have with this solution is that I am using the scope parameter which is passed to the link() function. For example, in the .each() loop above, I need to get the value of the element ID which is based on interpolation using {{<angular expre>}}.

So I tried to use pre-link and post-link within the compile function where the scope is available. I noticed that the section with ng-if is removed when execution is in pre-link and then it is added shortly after that. So I had to use $watch to monitor changes to the children to run the needed process when required. I developed this plunker sample:

http://plnkr.co/edit/lsJvhr?p=preview

Even after all such effort, the issue is not resolved. So the bottom line for similar cases, is that if you need to use the scope then you have to remove replace: true.

Any feedback would be appreciated.

Tarek

tarekahf
  • 738
  • 1
  • 16
  • 42