8

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

030
  • 10,842
  • 12
  • 78
  • 123
james
  • 4,150
  • 2
  • 30
  • 36
  • 1
    First of all... LOL! Really interesting. Anyway, you should use a filter for that, not `ng-if` afaik. – Vinícius Gobbo A. de Oliveira Feb 21 '15 at 17:30
  • yeah, i realised as i was writing it that there are probably better ways - i've ended up with an ng-include instead. But thought i'd see if anyone knew why this is happening as it seems pretty weird. I haven't used filters yet, i keep reading that they can be quite expensive? Can you supply a quick plnkr showing how you'd switch it to a filter please? – james Feb 21 '15 at 17:35
  • I don't know why it happens, but when you remove 'replace: true' in itemDirective, the order is no longer reversed!? – Manube Feb 21 '15 at 17:42
  • @Manube Well spotted – james Feb 21 '15 at 17:49
  • could it have to do with the way link functions work in reverse order, like detailed in this post: http://www.jvandemo.com/the-nitty-gritty-of-compile-and-link-functions-inside-angularjs-directives/ ? But there is no link function in itemDirective, just a templateUrl, so why? – Manube Feb 21 '15 at 17:52
  • Does it related to `$formatter.unshift` or `$parser.unshift` – Pankaj Parkar Feb 21 '15 at 18:28
  • Can anybody explain where I am wrong in my answer below ? – squiroid Feb 21 '15 at 18:32
  • @james have you got any solution yet ? – squiroid Feb 21 '15 at 18:48
  • This is a good question and is due to a bug in angular. I did some digging but couldn't reach the root cause. I'll continue tomorrow, and possibly open an issue on the GH repo. – Mosho Feb 21 '15 at 21:25
  • possible duplicate of [Directive with root element with ngRepeat and replace: true](http://stackoverflow.com/questions/27609719/directive-with-root-element-with-ngrepeat-and-replace-true) – New Dev Feb 22 '15 at 02:41
  • @NewDev please see my Update 2. I'd agree that the behaviour demonstrated in the Update 1 plnkrs, demonstrates the issue you've linked to. But the Update 2 plnkrs show that this has nothing to do with isolate scope directives. – james Feb 22 '15 at 08:53
  • @Mosho i did exactly that last night. Only to wake up to find it closed as i'd added it to the wrong (2.0) repo... It's now an issue on 1.x https://github.com/angular/angular.js/issues/11131 – james Feb 22 '15 at 08:56
  • Please check the `ngif` tag. It is not about the Angular directive: `NGif is an open-source tool that creates the ability for .Net to make/read Animated Gifs `. – 030 Dec 19 '16 at 15:04

3 Answers3

1

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?

Manube
  • 5,110
  • 3
  • 35
  • 59
  • I know my answer is not perfect, but comments or, better, a more thorough explanation of the way templateUrl interacts with display, would certainly be more helpful than a plain downvote :) – Manube Feb 21 '15 at 18:19
  • 1
    The explanation is incorrect and it ends with a question. – Mosho Feb 21 '15 at 18:25
  • There is only one request made for the template. – Mosho Feb 21 '15 at 19:38
1

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.

squiroid
  • 13,809
  • 6
  • 47
  • 67
  • I guess this is the exact situation happening here can anybody let me know where i am wrong,So i can improve myself :-) – squiroid Feb 21 '15 at 18:21
  • 1
    @squiroid Good explaination +1 answer..I would have accepted your answer.. :) – Pankaj Parkar Feb 21 '15 at 18:38
  • 1
    Thanks @pankajparkar :-) I dont know why i got -1 from someone :-) – squiroid Feb 21 '15 at 18:39
  • @anonymous i am still waiting for your comment so that i can improve myself if i were wrong in this post. – squiroid Feb 21 '15 at 18:44
  • It's incorrect. How does your answer explain why doing the exact same thing with `template` instead of `templateUrl` produces a different result? – Mosho Feb 21 '15 at 18:51
  • template compile with directive while temlate url is async in nature so directive will delay until template url file is loaded. Doc:- "Because template loading is asynchronous the compiler will suspend compilation of directives on that element for later when the template has been resolved. In the meantime it will continue to compile and link sibling and parent elements as though this element had not contained any directives." – squiroid Feb 21 '15 at 18:56
  • And how does that explain it? – Mosho Feb 21 '15 at 19:05
  • No down voting on my part, i've just been having my dinner! I've read the link you posted and quite a few other related issues on the angular github, to get a better understanding of that issue. But i'm not sure why this would cause the list to be rendered backwards? As @Mosho pointed out, can you explain why it's not replicated when using `template` instead of `templateUrl`? – james Feb 21 '15 at 19:13
0

second one showing in reverse order due to custom directive defined with item-directive name but its not rendering into DOM due to replace=true is used.

for more ref you can refer to this link

Community
  • 1
  • 1
TechnoCrat
  • 710
  • 10
  • 20