Angular.js: winklig 1.2.18: ng-Repeat-Problem mit Transclude

Erstellt am 17. Juni 2014  ·  48Kommentare  ·  Quelle: angular/angular.js

Bei der Übergabe an eine Direktive durch ng-transclude funktioniert HTML-Inhalt mit der Referenz {{item}}, die Sie wiederholen möchten (durch ng-repeat="item in collection" implementiert in der Direktive) nicht mit Version 1.2.18

http://embed.plnkr.co/EvzF25sPD3uZLQivDFqy/preview

Hilfreichster Kommentar

Am Ende habe ich nur $parent . Es ist der Schrank für Vanille, ohne zu viele Dinge hinzufügen zu müssen.

Also ich habe sowas wie:

angular.module('test').directive('myDirectiveWithTransclusion', function() {
     return {
          restrict: 'E'
          transclude: {
               transcludeThis: 'transcludeThis'
          }
          template: "<div ng-repeat='item in array'><div ng-transclude='transcludeThis'></div></div>"
     }
})
<my-directive-with-transclusion>
     <transclude-this>
          {{$parent.item}}
     </transclude-this>
</my-directive-with-transclusion>

Alle 48 Kommentare

oh gesetzgeber. @petebacondarwin möchtest du noch eine davon machen? das ist wirklich die Sache "keine Geschwister-Scopes mit ng-transclude erstellen", es ist nur so, dass es in diesem Fall aufgrund von Defekten vorher funktioniert hat

Leider funktioniert die ng-transclude Transklusion nicht so. Was Sie versuchen, ist, eine Container-Direktive zu erstellen, die ihre untergeordneten Elemente als "Vorlage" dafür verwendet, was in Ihrer eigenen Direktive ausgestanzt werden soll.

Ich bin in der Vergangenheit ein paar Mal darauf gestoßen und hatte eine lange Debatte mit Misko darüber. Der transkludierte Inhalt ist per Definition an den Geltungsbereich des Ortes gebunden, an dem die Richtlinie instanziiert wird; nicht in den Anwendungsbereich des Musters der Richtlinie.

Zuvor hat dies möglicherweise funktioniert, da wir die Transklusion in einigen Fällen, bei denen es sich um verschachtelte Transklusionsszenarien handelte, tatsächlich an den falschen Bereich gebunden haben.

Sie müssen hier also nicht wirklich Transklusion verwenden, da Sie wirklich versuchen, einfach den inneren HTML-Code in Ihr eigenes Template einzufügen. Sie können dies in der Kompilierungsfunktion wie folgt tun:

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

app.directive('test', function(){

  return {
    restrict: 'E',
    compile: function compile(tElement, tAttrs, tTransclude) {

      // Extract the children from this instance of the directive
      var children = tElement.children();

      // Wrap the chidren in our template
      var template = angular.element('<div ng-repeat="item in collection"></div>');
      template.append(children);

      // Append this new template to our compile element
      tElement.html('');
      tElement.append(template);

      return {
        pre: function preLink(scope, iElement, iAttrs, crtl, transclude) {
            scope.collection = [1, 2, 3, 4, 5];
        },
        post: function postLink(scope, iElement, iAttrs, controller) {
          console.log(iElement[0]);
        }
      };
    }
  };
});

Ein weiteres Beispiel dafür (glaube ich):

Index.html:

<html ng-app='myApp'>

<head>
    <title>AngularJS Scopes</title>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js"></script>
    <script src='index.js'></script>
</head>

<body ng-controller='myController'>
    <people>Hello {{person.name}}</people>
</body>
</html>

index.js:

var myApp = angular.module( 'myApp', [] );

myApp.controller( 'myController', function( $scope ) {
    $scope.people = [
        { name: 'Rob'  },
        { name: 'Alex' },
        { name: 'John' }
    ];
});

myApp.directive( 'people', function() {
    return {
        restrict: 'E',

        transclude: true,
        template: '<div ng-repeat="person in people" ng-transclude></div>',
    }
});

Hat mit Angular 1.2.1 gearbeitet, aber nicht mit 1.2.18;

Der unschuldige Entwickler konnte nur erwarten, dass der obige Code funktioniert. Dieses Dokument sagt:

...manchmal ist es wünschenswert, ein ganzes Template anstelle eines Strings oder eines Objekts übergeben zu können. Nehmen wir an, wir möchten eine Komponente "Dialogfeld" erstellen. Das Dialogfeld sollte jeden beliebigen Inhalt umschließen können.

Während die ngTransclude-Dokumentation sagt:

Direktive, die den Einfügepunkt für das transcludierte DOM der nächsten übergeordneten Direktive markiert, die Transclusion verwendet. Jeglicher vorhandener Inhalt des Elements, auf dem diese Anweisung platziert wird, wird entfernt, bevor der transkludierte Inhalt eingefügt wird.

Wie unterscheidet sich dies von der @petebacondarwin- Definition:

...eine Container-Direktive erstellen, die ihre untergeordneten Elemente als "Vorlage" dafür verwendet, was in Ihrer eigenen Direktive ausgestanzt werden soll

Ich verstehe wirklich nicht, warum Transklusion hier nicht die richtige Lösung ist. Wenn überhaupt, würde ich erwarten, dass die vernünftige Lösung darin besteht, den Vorlagenbereich in die Transklusionsfunktion einzufügen.

Der Unterschied besteht darin, dass transkludierter Inhalt an die "Außenseite" gebunden ist, dh an den Bereich des Ortes, an dem das <people> Element gefunden wird.
Was Sie dagegen wünschen, ist, dass die Inline-Vorlage an das "Innere", dh den Geltungsbereich der Richtlinie, gebunden ist.
Wenn Ihre Direktive keinen eigenen Geltungsbereich schafft, ist dies ungefähr dasselbe. Wenn Ihre Richtlinie beispielsweise einen isolierten Geltungsbereich schafft, ist dies definitiv nicht dasselbe. Der innere Bereich hat keinen Zugriff auf den äußeren Bereich.

Ich denke, Sie könnten Ihre eigene Anweisung erstellen, die tatsächlich den Vorlagenbereich in die Transklusionsfunktion einfügt ...

Was Sie sagen, macht absolut Sinn - aber nur, wenn Sie die Tiefen von Angular verstehen. Mein Punkt ist, dass das obige Beispiel irgendwie ohne zu viel zusätzliche Arbeit funktionieren sollte.

Scheint mir sehr vernünftig und sehr praktisch, dass die Transklusionsfunktion irgendwie auf den Bereich der Vorlage (oder des 'inneren') zugreifen kann. Ich kann mir viele Fälle vorstellen, warum dies erforderlich sein wird.

Das gleiche Problem wird in diesem Blog erklärt . Ich gehe davon aus, dass sich immer mehr Leute über den aktuellen Stand der Dinge beschweren werden (es gibt bereits eine Vielzahl verwandter Probleme auf Github aufgrund dieses Verhaltens).

Und vielen Dank für den Code. Ich reproduziere es hier zum Nutzen anderer:

var myApp = angular.module( 'myApp', [] );

myApp.controller( 'myController', function( $scope ) {
    $scope.people = [
        { name: 'Rob'  },
        { name: 'Alex' },
        { name: 'John' }
    ];
});

myApp.directive( 'people', function() {
    return {
        restrict: 'E',
        transclude: true,
        template: '<div ng-repeat="person in people" inject></div>'
    }
});

myApp.directive('inject', function(){
  return {
    link: function($scope, $element, $attrs, controller, $transclude) {
      if (!$transclude) {
        throw minErr('ngTransclude')('orphan',
         'Illegal use of ngTransclude directive in the template! ' +
         'No parent directive that requires a transclusion found. ' +
         'Element: {0}',
         startingTag($element));
      }
      var innerScope = $scope.$new();
      $transclude(innerScope, function(clone) {
        $element.empty();
        $element.append(clone);
        $element.on('$destroy', function() {
          innerScope.$destroy();
        });
      });
    }
  };
});

:-) Ich stimme zu, dass Transklusion kein einfaches Thema ist, das ohne viel Kopfkratzen und Tastaturhämmern zu verstehen ist.
Vielleicht müssen wir die Dokumentation noch weiter klären, dass ng-transclude den transcludierten Inhalt an den "äußeren" Bereich bindet?

Persönlich finde ich die aktuelle Dokumentation, zumindest auf dieser zentralen Seite , ziemlich eindeutig und klar:

Was genau bewirkt diese Transklusionsoption? transclude macht den Inhalt einer Richtlinie mit dieser Option Zugriff auf den Geltungsbereich außerhalb der Richtlinie anstatt innerhalb der Richtlinie.

(und alles, was folgt, veranschaulicht dies weiter).

Ich würde in Erwägung ziehen, vielleicht die von Ihnen bereitgestellte Richtlinie in den Rahmen aufzunehmen und sie möglicherweise als „ng-transclude-intern“ zu bezeichnen? Ich kenne mindestens eine weitere Person, die sich mit der Direktive namens "transscope" daran versucht hat.

Ich habe eine Lösung versucht, aber ich stand vor einem anderen Problem, warum der Geltungsbereich des ng-repeat-Elternteils nicht der Geltungsbereich der Richtlinie ist

http://plnkr.co/edit/7j92IC?p=preview

@luboid Der Grund, der in Ihrem Plunker nicht funktioniert, ist, dass Ihre Direktive einen isolierten Geltungsbereich hat und das modifizierte kompilierte DOM (tun Sie dies übrigens nicht, dies ist eine dumme Möglichkeit, dieses Problem zu lösen) verwendet a gleichgeordnetes Element des übergeordneten Elements des Isolate-Bereichs.

Ich werde ein Beispiel für einen geeigneten Weg hinzufügen, um dies so zu gestalten, wie Sie es erwarten. (Aber das ist im Allgemeinen immer noch ein ziemlich schreckliches Design, es gibt keinen guten Grund, dies zu tun)

Tatsächlich können Sie dies mit ng-repeat oder anderen Element-Transclusion-Anweisungen nicht wirklich beheben. Also ja, das wird seit Version 1.2.0 nicht mehr funktionieren

@petebacondarwin Eine wirklich var innerScope anstatt nur:

myApp.directive( 'inject', function() {
    return {
        link: function( $scope, $element, $attrs, controller, $transclude ) {
            if ( !$transclude ) {
                throw minErr( 'ngTransclude' )( 'orphan',
                    'Illegal use of ngTransclude directive in the template! ' +
                    'No parent directive that requires a transclusion found. ' +
                    'Element: {0}',
                    startingTag( $element ));
            }

            $transclude( $scope, function( clone ) {
                $element.empty();
                $element.append( clone );
            });
        }
    };
}); 

Es ist im Allgemeinen am besten, einen neuen Geltungsbereich zu erstellen, wenn Sie einige neue Elemente kompilieren möchten, da Sie nicht sicher sind, ob die ursprüngliche Direktive mit einer komplexen Direktive verbunden ist, die auch einen Geltungsbereich erstellen möchte usw. Aber Sie können möglicherweise Folgendes erhalten weg damit hier...

Danke für die Hilfe,
Ich werde externe Vorlagen ($templateCache) verwenden, dann geht die Sache etwas einfach, die Basisvorlage wird mit Direktivenbereich kompiliert

@Izhaki @petebacondarwin Vielen Dank für diese Include-Direktive. Gerade endlich von 1.2.16 auf 1.2.20 aktualisiert und es hat ein bisschen gedauert, bis ich sah, warum meine App so stark kaputt ging.

Ich dachte, Transklusion sei ein ziemlich einfaches Konzept, bevor ich diesen Thread gefunden habe. Nö.

Danke an alle, die mitgeholfen haben, dies aufzuklären. Das Update auf 1.2.21 hat aufgrund der hier beschriebenen Probleme einen Großteil unserer Benutzeroberfläche beschädigt, und jetzt sind wir auf dem richtigen Weg.

@caitp @petebacondarwin : Es wäre sehr hilfreich, wenn wir zwei zusätzliche Informationen erhalten könnten:

  1. Welche Breaking Change, die irgendwo "um Version 1.2.0" (wie Caitlin sagte) passierte, führte dazu, dass dieser Ansatz plötzlich explodierte? Ich verstehe, was nicht mehr funktioniert, aber ich sehe im Changelog in der Nähe dieser Version keine spezifischen Dinge, die mit diesem speziellen Problem zu korrelieren scheinen.
  2. Was ist der alternative Ansatz, den Endbenutzer anstelle von ng-transclude verwenden sollten, wenn wir isolierte, wiederverwendbare Container wünschen, von denen beliebige innere Inhalte erben können? Beispiel: eine große, mandantenfähige Anwendung, bei der sogar die Schnittstelle variabel und vollständig datengesteuert ist. Wir haben also Dinge wie Listensteuerelemente, die Daten abrufen müssen, um sich selbst zu füllen, und dann konsistente Interaktion/Verhalten haben. Wir würden gerne in der Lage sein, eine Wrapper-Direktive zu standardisieren, die diese Dinge bereitstellt. Aber die Vorlagen, die wir für die Listenelemente verwenden (die innere Direktiven enthalten können) variieren je nach Situation. Und die Listen sind oft rekursiv verschachtelt, basierend auf der Baumstruktur der Daten, die sie generieren. Daher benötigen wir eine Isolierung für diese verschachtelten Listen. Wir kontrollieren den gesamten Code, also verwenden wir keine Transklusion, um das Innere vom Äußeren zu isolieren. Vielmehr geht es uns nur um einen netten, deklarativen Ansatz für die Container und ihren willkürlichen Inhalt, wie er im Direktiven-Leitfaden ausdrücklich empfohlen wird. Ist ng-include dafür jetzt die bessere Option (den Pfad zum inneren Template als Attribut in der Container-Deklaration übergeben), obwohl es uns von Inline-Templates wegführt und sich daher etwas weniger idiomatisch anfühlt?

Ich verstehe, was nicht mehr funktioniert, aber ich sehe im Changelog in der Nähe dieser Version keine spezifischen Dinge, die mit diesem speziellen Problem zu korrelieren scheinen.

https://github.com/angular/angular.js/blob/master/CHANGELOG.md#120 -timely-delivery-2013-11-08

Ich habe mich speziell auf diese bezogen

  • Isolate nur an Kinder weitergeben, die zur Isolate-Direktive gehören (d0efd5ee)
  • Isolate-Scope wirklich isolieren (909cabd3, #1924, #2500)

Aber ich kann mich nicht mehr wirklich erinnern, wovon ich überhaupt gesprochen habe, wer weiß

Im Changelog gibt es nur zwei Arten von Meldungen: Änderungen und Breaking Changes. Dies wurde nicht als Breaking Change angesehen, sondern eher als Bugfix. Es war schwer vorherzusagen, dass viele Menschen dieses Verhalten nutzten. Aber es sollte wahrscheinlich in die Migrationsdokumentation gehen (die etwas Liebe brauchen)

Sho' 'nuff. Das sind die. Ich habe nach Änderungen im Zusammenhang mit der Transklusion gesucht, aber dies ist eindeutig eine Änderung des Isolat-Bereichs, die mit der Transklusion verbunden ist. Vielen Dank!

Diese Änderung hat auch unseren Code gebrochen. Es wäre schön, eine einfache deklarative Möglichkeit zu haben, auf die person Variablen dieser Direktive zuzugreifen und sie im übergeordneten Geltungsbereich zu verwenden. Scheint ein häufiger Anwendungsfall zu sein, nachdem man gesehen hat, wie viele Leute es benutzt haben, bevor dieses bug in 1.2.18 behoben wurde.

Hier ist die Demo, die in 1.2.17 funktioniert und seit 1.2.18 kaputt ist

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

@evgenyneu : Angenommen, Sie kontrollieren sowohl den inneren Inhalt als auch die Container-Direktive. Wir haben festgestellt, dass die Verwendung von ng-include anstelle von transclude klar und einfach ist und uns das gewünschte Vererbungsmuster liefert. Wir übergeben einfach einen Vorlagennamen als Attribut an die Deklaration der äußeren Direktive und platzieren eine ng-include-Direktive an der gleichen Stelle wie früher die Transclude-Direktive. Problem gelöst.

Wenn Sie möchten, dass die innere Inhaltsvorlage in die Ansichtsvorlage integriert wird, verwenden Sie einfach ng-template, um die Vorlage an derselben Position zu umschließen, die Sie zuvor verwendet haben.

@xmlilley ,

Hier ist die Demo: http://plnkr.co/edit/4MwEL3?p=preview

Leute, ihr wisst, ihr könnt das stattdessen einfach richtig machen http://plnkr.co/edit/fw7thti1u4F9ArxsuYkQ?p=preview --- es ist nicht perfekt, aber es bringt euch dorthin

@caitp , nett, danke. Wir haben viele Lösungen!

Für diejenigen, die sich dieses Problem ansehen, habe ich eine 'verbesserte' ng-transclude-Direktive erstellt, deren Attributwert den gesuchten internen Bereich definiert und einer der folgenden sein kann:

  • silbing - Der Geltungsbereich für gleichrangig zu dem Element, in dem die Transklusion stattfindet. Das ist das aktuelle ng-Transclude-Verhalten.
  • parent - Der Geltungsbereich für transkludierte Inhalte ist der des Elements, in dem die Transklusion stattfindet.
  • child - Der Geltungsbereich für transkludierte Inhalte ist ein untergeordneter Geltungsbereich für den Geltungsbereich des Elements, in dem die Transklusion stattfindet.

Anwendungsbeispiel:

template: 
  '<div ng-transclude="parent">' +
  '</div>'

Vollständiges Beispiel

Sehen Sie sich diesen Plunk an, um ein Beispiel für alle zu sehen.

Die Ausgabe sieht so aus:

image

Quelle

.config(function($provide){
    $provide.decorator('ngTranscludeDirective', ['$delegate', function($delegate) {
        // Remove the original directive
        $delegate.shift();
        return $delegate;
    }]);
})

.directive( 'ngTransclude', function() {
  return {
    restrict: 'EAC',
    link: function( $scope, $element, $attrs, controller, $transclude ) {
      if (!$transclude) {
        throw minErr('ngTransclude')('orphan',
         'Illegal use of ngTransclude directive in the template! ' +
         'No parent directive that requires a transclusion found. ' +
         'Element: {0}',
         startingTag($element));
      }

      var iScopeType = $attrs['ngTransclude'] || 'sibling';

      switch ( iScopeType ) {
        case 'sibling':
          $transclude( function( clone ) {
            $element.empty();
            $element.append( clone );
          });
          break;
        case 'parent':
          $transclude( $scope, function( clone ) {
            $element.empty();
            $element.append( clone );
          });
          break;
        case 'child':
          var iChildScope = $scope.$new();
          $transclude( iChildScope, function( clone ) {
            $element.empty();
            $element.append( clone );
            $element.on( '$destroy', function() {
              iChildScope.$destroy();
            });            
          });
          break;
      }
    }
  }
})

@Izhaki - FWIW, +10. Verletzt nicht die aktuellen Konventionen, bietet aber eine saubere, deklarative Möglichkeit, freiwillig auf einen gemeinsamen Anwendungsfall zuzugreifen. Vielen Dank!

:+1: für @Izhakis Lösung. Ich werde dies so verwenden, wie es ist, aber ich würde mich viel wohler fühlen, wenn es in Angular enthalten wäre.

+1

@Izhaki +1, tolles Beispiel!

Vielen Dank für die benutzerdefinierte Transclude-Anweisung. Am Ende habe ich nur einen separaten benutzerdefinierten Transclude erstellt, anstatt die Standardeinstellung zu überschreiben. Es gibt ein paar Funktionsaufrufe in Ihrem Patch, von denen ich nicht sicher bin, woher sie kommen. minErr() und StartingTag()

@dehru - diese Funktionen sind intern in AngularJS.

@Izhaki vielen Dank! Ich habe Ihr Plunk gegabelt, das den transcludierten Inhalt wiederholt, falls es jemand interessiert.
Es ist erwähnenswert, dass ng-transclude="parent" möglicherweise nicht so funktioniert, wie Sie es erwarten.
http://plnkr.co/edit/S6ngqz?p=preview

plunker_ng-transclude_ng-repeat

@Izhaki Sehr schön.
Sieht aus wie etwas, das in einem Pull-Request zu eckig sein sollte.
Denkst du zumindest in einer separaten Direktive mit einem Github-Repo, dass du es veröffentlichen kannst (damit ich einige Änderungen anbieten kann....)?
Vielen Dank

Izhaki, das ist großartig. Ich _herz_ dich.

@Izhaki Dies ist eine sehr schöne Lösung für das Problem der

Ich bin jedoch verwirrt und habe mich gefragt, ob Sie erklären könnten, wie sich die Vererbung der $transclude-Funktion von der äußeren Direktive auf die ngTransclude-Direktive überträgt? Es ist nirgendwo ausdrücklich angegeben, das ich finden konnte, aber ich nahm an, dass transclude: true in der Direktive verwendet werden musste, um die $transclude-Funktion in der Linkfunktion zu verwenden. Nachdem ich ein wenig mit dem Code gespielt hatte, stellte ich fest, dass die Verwendung von transclude: true den Code tatsächlich bricht. Was ist hier los?

Ich habe tagelang damit gekämpft und auch mit der Tatsache, dass transclude die transcludierten Elemente in den transclude-Platzhalter einfügt, anstatt sie durch sie zu ersetzen.

Ich habe festgestellt, dass ng-transclude-replace beide Probleme löst! http://gogoout.github.io/angular-directives-utils/#/api/ng -directives-utils.transcludeReplace

Ich muss die ng-repeat-Direktive nicht in die Repeat-Direktive selbst oder ähnliches überführen! Alles scheint zu funktionieren, einschließlich aller Validierungen/Bindungen.

Hier ein Ausschnitt meines Codes:

<form-field label="Roles" required>
    <checkbox-group>
        <checkbox ng-repeat="role in roles" label="{{role.description}}">
            <input type="checkbox" name="selectedRoles" ng-model="role.selected" value="{{role.description}}" ng-required="!hasRole" />
        </checkbox>
    </checkbox-group>
</form-field>

@abobwhite Danke für die Erwähnung von ng-transclude-replace , tolle Lösung.

Ich bin vor kurzem auf dieses Problem gestoßen. Während @Izhaki für mich funktioniert hat, bin ich gespannt, ob sich in der Zeit seit dieser Diskussion eine Best Practice herausgebildet hat. Gab es insbesondere ein Interesse daran, ng-transclude="sibling | parent | child" Teil des eckigen Kerns zu machen?

@telekid - Wir haben derzeit nicht vor, diese Funktion in den Kern aufzunehmen.

Ich sehe, dass es Lösungen und Workarounds gibt, aber es wäre sehr praktisch, wenn es eine Möglichkeit gäbe, auf den internen Anwendungsbereich der Richtlinie zuzugreifen.

Ich möchte darauf hinweisen, dass ich den Transclude- Mod, den Angular 1.5 aktualisiert habe, damit er mit Multi-Slot-
Zweig: https://github.com/NickBolles/ngTranscludeMod/tree/Angular1.5-multi-slot
PR: https://github.com/Izhaki/ngTranscludeMod/pull/2
Plunker: http://plnkr.co/edit/5XGBEX0muH9CSijMfWsH?p=preview

Am Ende habe ich nur $parent . Es ist der Schrank für Vanille, ohne zu viele Dinge hinzufügen zu müssen.

Also ich habe sowas wie:

angular.module('test').directive('myDirectiveWithTransclusion', function() {
     return {
          restrict: 'E'
          transclude: {
               transcludeThis: 'transcludeThis'
          }
          template: "<div ng-repeat='item in array'><div ng-transclude='transcludeThis'></div></div>"
     }
})
<my-directive-with-transclusion>
     <transclude-this>
          {{$parent.item}}
     </transclude-this>
</my-directive-with-transclusion>

Hallo @moneytree-doug: Ich verwende diese Lösung, die Sie bereitgestellt haben, aber ich finde den Umfang von transclude - dies ist immer noch der Richtlinienbereich, nicht das Kind des neuen Bereichs, der von ng-repeat generiert wird. Können Sie mir einige Vorschläge machen?

@szetin Könnten Sie eine Jsfiddle aufstellen, die mir zeigt, was Sie erwarten und was passiert?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen