Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ng-repeat render order reversed by ng-if

Tags:

angularjs

I've just come across this and am wondering if it's a bug or expected behaviour? This is just a small example to show the issue. The code below is used in both examples:

<body ng-app="app" ng-controller="AppCtrl">
    <item-directive ng-repeat="item in ::items" item="item"></item-directive>
</body>


angular
    .module('app', [])
    .controller('AppCtrl', AppCtrl)
    .directive('itemDirective', itemDirective)
    .factory('model', model);


function AppCtrl($scope, model) {

    $scope.items = model.getItems();
} 

function itemDirective() {

    return {
        restrict: 'E',
        replace: true,
        scope: {
            item: '='
        },
        templateUrl: 'item-directive.html'
    };
}

function model() {

    return {
        getItems: getItems
    }

    function getItems() {

        return [
            {
                type: 'test',
                title: 'test 1'
            },
            {
                type: 'test',
                title: 'test 2'
            },
            {
                type: 'test',
                title: 'test 3'
            }
        ];
    }
}

The first example has this item-directive.html which gets rendered in the correct order as expected

<div>

    <span>{{::item.title}}</span>

</div>

Plunkr without ng-if

But the second example - which has the below item-directive.html - incorporates an ng-if, which is causing the list to get rendered in reverse order?

<div ng-if="item.type == 'test'">

    <span>{{::item.title}}</span>

</div>

Plunkr with ng-if


-------- UPDATE ----------

I've just noticed (which relates to the issue noted in @squiroid's answer) that the isolate scope isn't actually working in this example. It appears to be, but item is being made available to the item-directive scope (or rather the scope it ends up with) by the ng-repeat, not the isolate scope. If you try to set any other values on the isolate scope, even though they show up on the scope passed to the directive's link and controller functions (as can be seen in the console output for the plnkr), they're not available to the template. Unless you remove replace.

Plunkr showing broken isolate scope

Plunkr showing fixed isolate scope when replace:false


--- UPDATE 2 ---

I've updated both of the examples to show the issue persisting once the the isolate scope is removed

Plunkr without ng-if and no isolate scope

Plunkr with ng-if and no isolate scope

And also a new version showing the change from templateUrl to template - as suggested by @Manube - that shows the behaviour working as expected

Plunkr with ng-if and no isolate scope using template instead of templateUrl

like image 290
james Avatar asked Feb 21 '15 17:02

james


2 Answers

Using ng-if on a root element of a directive with replace: true creates a broken scope

ISSUE

This is happening with the combination of replace:'true' and ng-if on the root element.

Make sure the contents of your html in the templateUrl has exactly one root element.

If you place ng-if on span

<div >

    <span ng-if="item.type == 'test'">{{::item.title}}</span>

</div>

Now why it is happening,it is happening because The ngIf directive removes or recreates a portion of the DOM tree based on an {expression}. If the expression assigned to ngIf evaluates to a false value then the element is removed from the DOM, otherwise a clone of the element is reinserted into the DOM.

which may lead to no root element on the templateUrl while rendering and thus leads to unwanted behaviour.

like image 59
squiroid Avatar answered Oct 27 '22 18:10

squiroid


It is to do with templateUrl, which is asynchronous;

If you replace templateUrl by

 template:'<div ng-if="item.type === \'test\'"><span>{{::item.title}}</span></div>'

it will work as expected: see plunker with template instead of templateUrl


the test <div ng-if="item.type === 'test'"> will execute when scope is ready and the templateUrl has been fetched.

As the way the template is fetched is asynchronous, whichever template comes back first executes the test, and displays the item.

Now the question is: why is it always the last template that comes back first?

like image 1
Manube Avatar answered Oct 27 '22 16:10

Manube