web-dev-qa-db-fra.com

Angular passage de la portée à ng-include

J'ai écrit un contrôleur que j'utilise à plusieurs endroits de mon application avec ng-include Et ng-repeat, Comme ceci:

<div
  ng-repeat="item in items"
  ng-include="'item.html'"
  ng-controller="ItemController"
></div>

Dans le contrôleur/modèle, la valeur item doit exister, et l’ensemble est construit autour de cette idée. Maintenant, cependant, je dois utiliser le contrôleur d’une manière légèrement différente, sans le ng-repeat, Mais je dois tout de même pouvoir passer un item. J'ai vu ng-init Et j'ai pensé qu'il pourrait faire ce dont j'avais besoin, comme ceci:

<div
  ng-init="item = leftItem"
  ng-include="'item.html'"
  ng-controller="ItemController"
></div>
<div
  ng-init="item = rightItem"
  ng-include="'item.html'"
  ng-controller="ItemController"
></div>

Mais cela ne semble pas fonctionner. Quelqu'un a une idée sur la façon dont je peux transmettre une variable pour scope dans un cas singulier comme celui-ci?

Edit: Le contrôleur au-dessus de cela charge dans les valeurs leftItem et rightItem, quelque chose comme ceci:

.controller('MainController', function($scope, ItemModel) {
    ItemModel.loadItems()
        .then(function(items) {
            $scope.$apply(function() {
                $scope.leftItem = items.left;
                $scope.rightItem = items.right;
            });
        });
});
37
kbjr

J'ai fini par le réécrire dans une directive et lier la valeur nécessaire dans le champ d'application avec

scope: {
    item: '='
}
17
kbjr

Vous pouvez utiliser l'attribut onload que ngInclude fournit pour cela:

<div ng-include="'item.html'"
     ng-controller="ItemController"
     onload="item = rightItem">
</div>

Lien vers la documentation .

[~ # ~] éditer [~ # ~]

Essayez de faire quelque chose comme ceci dans la portée parent:

$scope.dataHolder = {};

Ensuite, lorsque les données asynchrones sont reçues, stockez les données sur dataHolder:

$scope.dataHolder.leftItem = items.leftItem;
$scope.dataHolder.rightItem = items.rightItem;

Maintenant, quand ng-include charge le modèle, il créera une étendue enfant qui héritera des propriétés du parent. Alors $scope.dataHolder sera défini dans cette étendue enfant (initialement sous forme d'objet vide). Mais lorsque vos données asynchrones sont reçues, la référence à l'objet vide doit alors contenir les données nouvellement reçues.

33
Sunil D.

En retard pour le parti, mais il y a un peu angular 'bidouillage' pour y parvenir sans mettre en œuvre une directive idiote.

L'ajout d'une directive intégrée qui étendra la portée de votre contrôleur (comme ng-if) partout où vous utilisez ng-include vous permettra d'isoler le nom de la variable pour toutes les portées incluses.

Alors:

<div ng-include="'item.html'"
  ng-if="true"
  onload="item = rightItem">
</div>
<div ng-include="'item.html'"
  ng-if="true"
  onload="item = leftItem">
</div>

Vous pouvez ensuite lier votre modèle item.html à la variable d'élément à plusieurs reprises avec différents éléments.

Voici un plunker pour réaliser ce que vous voulez

Le problème était que l'élément continuait à changer dans la portée du contrôleur et qu'il ne contenait qu'une référence à une variable d'élément qui était effacée à chaque instruction onload.

L'introduction d'une directive qui étend la portée actuelle vous permet d'avoir une portée isolée pour tous les ng-include. En conséquence, la référence à l'article est préservée et unique dans toute la portée étendue.

32
Piou

LOVE @ La réponse de Tanin. résout donc beaucoup de problèmes à la fois et de manière très élégante. Pour ceux d'entre vous comme moi qui ne connaissent pas Coffeescript, voici le javascript ...

REMARQUE: Pour des raisons qui me semblent trop nouvelles pour être comprises, ce code nécessite que vous citiez le nom de votre modèle ne fois, plutôt que l'obligation de ng-include de citer deux fois vos noms de modèles, c'est-à-dire. <div ng-include-template="template-name.html" ... > au lieu de <div ng-include-template="'template-name.html'" ... >

.directive('ngIncludeTemplate', function() {  
  return {  
    templateUrl: function(elem, attrs) { return attrs.ngIncludeTemplate; },  
    restrict: 'A',  
    scope: {  
      'ngIncludeVariables': '&'  
    },  
    link: function(scope, elem, attrs) {  
      var vars = scope.ngIncludeVariables();  
      Object.keys(vars).forEach(function(key) {  
        scope[key] = vars[key];  
      });  
    }  
  }  
})
12
Mike

L'utilisation de la charge n'est pas une solution propre, car elle a une portée globale. Si vous avez quelque chose de plus complexe, ça va commencer à échouer.

ng-include n'est pas réutilisable car il a accès à la portée globale. C'est un peu bizarre.

Ce qui précède n'est pas vrai. Ng-si avec onload n'éclaire pas la portée globale

Nous ne voulons pas non plus écrire une directive spécifique pour chaque situation.

Faire une directive générique au lieu de ng-include est une solution plus propre.

L'utilisation idéale ressemble à:

<div ng-include-template="'item.html'" ng-include-variables="{ item: 'whatever' }"></div>
<div ng-include-template="'item.html'" ng-include-variables="{ item: variableWorksToo }"></div>

La directive est:

.directive(
  'ngIncludeTemplate'
  () ->
    {
      templateUrl: (elem, attrs) -> attrs.ngIncludeTemplate
      restrict: 'A'
      scope: {
        'ngIncludeVariables': '&'
      }
      link: (scope, elem, attrs) ->
        vars = scope.ngIncludeVariables()
        for key, value of vars
          scope[key] = value
    }
)

Vous pouvez voir que la directive n'utilise pas la portée globale. Au lieu de cela, il lit l'objet à partir de ng-include-variables et ajoute ces membres à sa propre portée locale.

J'espère que c'est ce que vous voudriez; c'est propre et générique.

11
Tanin

ng-init est préférable pour cela, je pense.

<div ng-include='myFile.html' ng-init="myObject = myCtrl.myObject; myOtherObject=myCtrl.myOtherObject"/>
4
Will Sadler

Ci-dessus ne fonctionnera pas pour les attributs de second niveau, comme <div ng-include-template=... ng-include-variables="{ id: var.id }">. Remarquez le var.id.

Directive mise à jour (moche, mais fonctionne):

.directive('ngIncludeTemplate', function() {
  return {
    templateUrl: function(elem, attrs) { return attrs.ngIncludeTemplate; },
    restrict: 'A',
    scope: {
      'ngIncludeVariables': '&'
    },
    link: function(scope, elem, attrs) {
      var cache = scope.ngIncludeVariables();
      Object.keys(cache).forEach(function(key) {
        scope[key] = cache[key];
      });

      scope.$watch(
        function() {
          var val = scope.ngIncludeVariables();
          if (angular.equals(val, cache)) {
            return cache;
          }
          cache = val;
          return val;
        },
        function(newValue, oldValue) {
          if (!angular.equals(newValue, oldValue)) {
            Object.keys(newValue).forEach(function(key) {
              scope[key] = newValue[key];
            });
          }
        }
      );

    }
  };
});
3
Viktor Aseev

Peut-être que la mise à jour évidente pour Mike et Tanin répond - si vous utilisez des modèles intégrés comme:

<script type="text/ng-template" id="partial">{{variable}}</script>

<div ng-include-template="'partial'" ng-include-variables="{variable: variable}"></div>

Ensuite, dans la directive ngIncludeTemplate, remplacez

templateUrl: function(elem, attrs) { return attrs.ngIncludeTemplate; },  

Avec

template: function(elem, attrs) { return document.getElementById(attrs.ngIncludeTemplate.split("'")[1]).innerHTML },
1
Zmey Petrov