Perte de portée lors de l'utilisation de ng-include

181

J'ai ce module routes:

var mainModule = angular.module('lpConnect', []).
    config(['$routeProvider', function ($routeProvider) {
    $routeProvider.
        when('/home', {template:'views/home.html', controller:HomeCtrl}).
        when('/admin', {template:'views/admin.html', controller:AdminCtrl}).
        otherwise({redirectTo:'/connect'});
}]);

HTML d'accueil:

<div ng-include src="views.partial1"></div>

partial1 HTML:

<form ng-submit="addLine()">
    <input type="text" ng-model="lineText" size="30" placeholder="Type your message here">
</form>

HomeCtrl:

function HomeCtrl($scope, $location, $window, $http, Common) {
    ...
    $scope.views = {
        partial1:"views/partial1.html"
    };

    $scope.addLine = function () {
        $scope.chat.addLine($scope.lineText);
        $scope.lines.push({text:$scope.lineText});
        $scope.lineText = "";
    };
...
}

Dans la addLinefonction $scope.lineTextest undefined, cela peut être résolu en ajoutant ng-controller="HomeCtrl"à partial1.html, mais cela provoque l'appel du contrôleur deux fois. Qu'est-ce que j'oublie ici?

Shlomi Schwartz
la source

Réponses:

83

Cela est dû à ng-includece qui crée une nouvelle portée enfant, donc $scope.lineTextn'est pas modifiée. Je pense que cela thisfait référence à la portée actuelle, donc this.lineTextdevrait être défini.

Renan Tomal Fernandes
la source
260

Comme @Renan l'a mentionné, ng-include crée une nouvelle portée enfant. Cette portée hérite prototypiquement (voir les lignes en pointillés ci-dessous) de la portée HomeCtrl. ng-model="lineText"crée en fait une propriété de portée primitive sur la portée enfant, pas la portée de HomeCtrl. Cette portée enfant n'est pas accessible à la portée parent / HomeCtrl:

ng-include portée

Pour stocker ce que l'utilisateur a tapé dans le tableau $ scope.lines de HomeCtrl, je vous suggère de transmettre la valeur à la fonction addLine:

 <form ng-submit="addLine(lineText)">

De plus, étant donné que lineText appartient au scope / partial ngInclude, je pense qu'il devrait être responsable de l'effacer:

 <form ng-submit="addLine(lineText); lineText=''">

La fonction addLine () deviendrait ainsi:

$scope.addLine = function(lineText) {
    $scope.chat.addLine(lineText);
    $scope.lines.push({
        text: lineText
    });
};

Violon .

Alternatives:

  • définir une propriété d'objet sur la portée de $ de HomeCtrl, et l' utiliser dans la partie: ng-model="someObj.lineText; violon
  • non recommandée, ce qui est plus d'un hack: l' utilisation $ parent dans la partie pour créer / accéder à un lineTextbien sur la portée de HomeCtrl $:   ng-model="$parent.lineText"; violon

Il est un peu compliqué d'expliquer pourquoi les deux alternatives ci-dessus fonctionnent, mais cela est expliqué en détail ici: Quelles sont les nuances de l'héritage prototypique / prototypique de la portée dans AngularJS?

Je ne recommande pas d'utiliser thisdans la fonction addLine (). Il devient beaucoup moins clair quelle portée est accédée / manipulée.

Mark Rajcok
la source
1
Enfin je comprends.
Scott Tesler
1
Même question que @Jess avait, pourquoi est-ce considéré comme un hack?
qbert65536
13
@ qbert65536, c'est essentiellement un hack / fragile car si vous restructurez votre HTML, cela risque de ne plus fonctionner. Par exemple, vous devrez peut-être utiliser $parent.$parent...pour le faire fonctionner. En d'autres termes, l'utilisation $parentfait des hypothèses sur la structure DOM.
Mark Rajcok
6
Le lien de @Jess ci-dessus a été remplacé par ce ngInclude . Lisez toute la page, c'est génial.
mraaroncruz
1
C'est une excellente réponse détaillée, mais je les ai toutes essayées sans succès. J'ai un formulaire avec une entrée à un contrôleur et le résultat d'un contrôleur doit être visualisé sur un autre div. Une fois que j'entre dans une entrée, la synchronicité sera perdue et j'aurai une valeur constante de 0,00 sur le div de vue pendant que l'application est en cours d'exécution.
zahra
33

Au lieu d'utiliser thiscomme le suggère la réponse acceptée, utilisez $parentplutôt. Donc dans votre partial1.htmlvous aurez:

<form ng-submit="$parent.addLine()">
    <input type="text" ng-model="$parent.lineText" size="30" placeholder="Type your message here">
</form>

Si vous souhaitez en savoir plus sur la portée dans ng-includeou sur d'autres directives, consultez ceci: https://github.com/angular/angular.js/wiki/Understanding-Scopes#ng-include

ErwinGO
la source
1
Pour tout lecteur, il signifie $scope.$parentau lieu de $parentn'est pas défini selon Angular.
Sebastialonso
1
Cette réponse me sauve la journée! Merci beaucoup d'avoir signalé l'utilisation de $ parent.
Derek Webb
est $ scope. $ parent passe par référence? ou c'est juste une copie du parent?
OMGPOP
1
@Sebastiallonso a tort. $ scope. $ parent.lineText n'est pas défini. $ parent.lineText fonctionne, this.lineText ou simplement lineText fonctionnent également
OMGPOP
C'est $scope.$parentqui fonctionne pour moi en angulaire 1.3.20
radtek
4

J'ai compris comment contourner ce problème sans mélanger les données parent et sous-étendue. Définissez un ng-ifsur l' ng-includeélément et définissez-le sur une variable de portée. Par exemple :

<div ng-include="{{ template }}" ng-if="show"/>

Dans votre contrôleur, lorsque vous avez défini toutes les données dont vous avez besoin dans votre sous-étendue, définissez show sur true. Le ng-includecopiera à ce moment l'ensemble de données dans votre étendue et le placera dans votre sous-étendue.

La règle d'or est de réduire les données de portée plus profondément, sinon vous avez cette situation.

Max

wascou
la source
J'utilise cette approche pour un problème similaire mais ce n'est pas approprié pour tous les cas. Surtout quand vous voulez que l'élément inclus ne soit jamais caché ...
iSpithash