Skip to content
This repository has been archived by the owner on Apr 13, 2022. It is now read-only.

ngIf breaks ngModel #7216

Closed
Francisc opened this issue Apr 23, 2014 · 8 comments
Closed

ngIf breaks ngModel #7216

Francisc opened this issue Apr 23, 2014 · 8 comments

Comments

@Francisc
Copy link

Hello,

If, for example, you place a checkbox component inside a container with ngIf, inside the ngChange method, the state of the checkbox is always false.

I've made a simple Plunkr to demonstrate this.
http://plnkr.co/edit/QEIXZR1kJVvcw30R1O0t?p=preview

@pkozlowski-opensource
Copy link
Member

ngIf is creating a child scope, so your ngModel wasn't binding to the scope you were assuming it was. Here is quick remedy: http://plnkr.co/edit/evjc4n4sFKcYunqLAha9?p=preview

You can read up more on the scope-related topics here:
https://github.com/angular/angular.js/wiki/Understanding-Scopes

@Francisc
Copy link
Author

Hm, OK, but this doesn't look right.

Basically ng-if="data" vs ng-if="namespaced.data" should be the same thing as far as ngIf is concerned.

The fact that it creates a new isolated child scope is a side-effect. You use ngIf to add / remove elements from DOM, not to isolate scope.

Maybe this should be given more thought.
What do you think?

Also, thank you for your answer.

@pkozlowski-opensource
Copy link
Member

@Francisc this is not only about ngIf but you are going to see the same behaviour with any directive that is creating a child scope (btw, those are not isolated scopes): ngRepeat, ngView etc. Those scopes serve a purpose as then we can clean up model associated with a given scope. If you remove part of the DOM you don't want to keep model specific to this part of the view, right?

@Francisc
Copy link
Author

That's true, I don't want to leave suspended scope data, but I still think it should be able to compile in the same context.
There is no obvious difference between data and namespaced.data and there shouldn't be.

Another issue for me is the fact that, for example, in the Plunkr above, the visible var starts off as true and never changes which should mean the element is never removed from DOM.

In other news, why doesn't GitHub notify me about comments to this issue?...

@trusktr
Copy link
Contributor

trusktr commented Apr 28, 2014

The fact that ngIf(and similar directives like ngInclude) create a child $scope is counter-intuitive in certain cases.

For example, when I use the ngController directive on an element, then somewhere inside that element I use declare an ngIf element, then inside there I declare another ngController, intuition would lead one to expect that the second ngController scope is a direct child of the first ngController.

ngIf is used for presentation logic, but having those between ngController declarations messes with one's ability to use $scope.$parent in a clean way. The more ngIf directives that are declared between a parent and child, the more $scope.$parent.$parent.$parent the user will have to do.

This is a huge problem in some cases. For example, due to the use of ngInclude to include templates that contain "presentation logic", I am not able to know how many ngIf directives the designer of the template has used, but what I do know is that somewhere down the line the designer must declare a new ngController and it will inherit from the parent ngController, the parent being outside of the ngInclude. As the developer of the controllers, accessing the parent controller from the child controller with $scope.$parent (which is intuitively expected) simply does not work.

Code example:

<!-- index.html: I made this. -->
<div ng-controller="ParentController">
    <div ng-include="templateMadeByDesigner.html"></div> <!-- New scope -->
</div>


<!-- templateMadeByDesigner.html: Some other designer made this. -->
<div ng-if="isSomething"> <!-- New scope -->
    <span>blah blah</span>
    <!-- number of ngIf directives used here is unknown only to the designer, not me. It's supposed to be just "presentation logic" right? -->
    <div ng-if="someBoolean"> <!-- New scope -->
        <span>lorem ipsum</span>
        <div ng-controller="ChildController"> <!-- finally, the template designer has declared the required child ngController. -->
            ...
        </div>
    </div>
</div>

In this example, common intuition (for those who don't already know about ngIf creating a child scope) would lead one to believe that calling $scope.$parent in the ChildController would refer to the $scope of the ParentController. You can see how the scopes created by the ngInclude and ngIf directives (largely just presetation logic directives) get in the way of the business logic between the parent and child controllers.

The mere fact that ngIf creates child scopes has led me to spend hours wondering what's going on, and has also left me with no clean way to simply access the scope of the ParentController.

However, creating a child $scope for ngIf is a good thing, and is desirable. It is nice that the data models associated with the elements under the ngIf directive are destroyed when the element is not needed. That is good behavior.

So how can we come to a middle ground?

Perhaps a scope's $parent can be made to refer to the scope of the nearest parent ngController, and a new property with a name like $implicitParent or $directParent can be used to explicitly access scopes of other directives that are not ngController directives.

It is highly likely that a developer will want to simply access the scope of the nearest parent ngController much more often than the scope of some random ngIf directive that is used merely for presentation logic, not business logic.

@trusktr
Copy link
Contributor

trusktr commented Apr 28, 2014

I edited my previous comment. If you read replies by email, please read it on GItHub instead. Cheers.

@trusktr
Copy link
Contributor

trusktr commented Apr 28, 2014

If the solution to this problem won't be fixed in AngularJS itself, a simple workaround would be to create a circular assignment of a controller's scope to make the scope easily accessible in a child scope regardless of how many ngIf (or other directives) scopes get in the way.

Considering the HTML code in my previous comment, this would be the parent controller

app.controller("ParentController", function($scope) {
                    $scope.myParentScope = $scope; // circular reference so children scopes can access this easily.
});

In the child controller you can access the parent scope:

app.controller("ChildController", function($scope) {
          console.log($scope.myParentScope); // logs the parent scope.

          // no longer need to do $scope.$parent.$parent.$parent.$parent
});

That's a simple solution, but it will only work if you as the developer have access to both the ParentController and ChildController. There might be situations where you are only allowed to develop the child controller, and thus an Angular-specific fix would be nice.

@trusktr
Copy link
Contributor

trusktr commented Apr 28, 2014

@pkozlowski-opensource ^

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants