web-dev-qa-db-fra.com

routeur ui - vues imbriquées avec contrôleur partagé

J'ai une vue parent abstraite destinée à partager un contrôleur avec ses vues imbriquées.

.state('edit', {
    abstract: true,
    url: '/home/edit/:id',
    templateUrl: 'app/templates/editView.html',
    controller: 'editController'
})
.state('edit.details', {
    url: '/details',
    templateUrl: 'app/templates/editDetailsView.html'
})
.state('edit.info', {
    url: '/info',
    templateUrl: 'app/templates/editInfoView.html'
})

Le routage fonctionne comme prévu.

Le problème est que lorsque je mets à jour une variable $scope à partir de l'une des vues imbriquées, la modification n'est pas reflétée dans la vue. Quand je fais la même chose à partir de la vue parent, cela fonctionne bien. Ce n'est pas une situation qui nécessite un $apply.

À mon avis, une nouvelle instance de editController est en cours de création pour chaque vue, mais je ne sais pas pourquoi ni comment la résoudre.

31
aw04

La question ici serait liée à ce Q & A: Comment partager des données $ scope entre des états dans angularjs ui-router? .

La façon de le résoudre est cachée dans le:

_ { Comprendre les étendues } _

Dans AngularJS, une portée enfant hérite normalement de sa portée parent de manière prototypique.
...

Avoir un '.' dans vos modèles garantira que l’héritage prototype est en jeu. 

// So, use
<input type="text" ng-model="someObj.prop1"> 
// rather than
<input type="text" ng-model="prop1">.

Et aussi ça

Héritage de la portée par hiérarchie de vues uniquement }

Gardez à l'esprit que les propriétés de portée n'héritent en aval de la chaîne d'états que si les vues de vos états sont imbriquées. L'héritage des propriétés de portée n'a rien à voir avec l'imbrication de vos états et tout ce qui a trait à l'imbrication de vos vues (modèles).

Il est tout à fait possible que vous ayez des états imbriqués dont les modèles remplissent des vues UI à divers emplacements non imbriqués au sein de votre site. Dans ce scénario, vous ne pouvez pas vous attendre à accéder aux variables d'étendue des vues d'état parent dans les vues d'états enfants.

Ayant cela, nous devrions le faire dans le contrôleur de montage

controller('editController', function ($scope) {
  $scope.Model = $scope.Model || {SomeProperty : "xxx"};
})

Et nous pouvons même réutiliser ce controller: 'editController' _ (nous ne pouvons pas le faire, car le $ scope.Model sera présent - grâce à l'héritage)

.state('edit', {
    abstract: true,
    url: '/home/edit/:id',
    templateUrl: 'app/templates/editView.html',
    controller: 'editController'
})
.state('edit.details', {
    url: '/details',
    templateUrl: 'app/templates/editDetailsView.html',
    controller: 'editController'
})
.state('edit.info', {
    url: '/info',
    templateUrl: 'app/templates/editInfoView.html',
    controller: 'editController'
})

Maintenant, le même contrôleur sera instancié plusieurs fois (parent tous les enfants) mais le $scope.Model sera lancé une seule fois (à l'intérieur du parent) et disponible partout

Cochez cette exemple de travail similaire ici

41
Radim Köhler

Basé sur un commentaire de PilotBob

Est-il possible de faire cela en utilisant le modèle controllerAs donnant à l'état enfant son propre contrôleur? 

J'ai décidé d'ajouter une autre solution, en utilisant controllerAs tout en conservant le ci-dessus/concept original

Il y a un plongeur de travail

Les états auraient maintenant différents contrôleurs et l'état parent le nommera "parentCtrl" (à NE PAS écraser dans une portée enfant avec un contrôleur enfant)}

 .state("main", {
      controller:'mainController',
      controllerAs: "parentCtrl",
      ...
  .state("main.1", {
      parent: 'main',
      controller:'child1Controller',
      controllerAs: "ctrl",
      ...
  .state("main.2", {
      parent: 'main',
      controller:'child2Controller',
      controllerAs: "ctrl", 
      ... 

Et ce sont des contrôleurs:

.controller('mainController', function ($scope) {
    this.Model =  {Name : "yyy"};
})
.controller('child1Controller', function ($scope) {
    $scope.Model = $scope.parentCtrl.Model;
})
.controller('child2Controller', function ($scope) {
    $scope.Model = $scope.parentCtrl.Model; 
})

Vérifiez-le en action ici

13
Radim Köhler

Une autre alternative en utilisant resolve

.state('edit', {
    abstract: true,
    url: '/home/edit/:id',
    templateUrl: 'app/templates/editView.html',
    controller: 'editController',
    resolve: {
        baseData: function() {
            return {};
        }
    }
})
.state('edit.details', {
    url: '/details',
    templateUrl: 'app/templates/editDetailsView.html',
    controller: 'editController'
})
.state('edit.info', {
    url: '/info',
    templateUrl: 'app/templates/editInfoView.html',
    controller: 'editController'
})


.controller('editController', function (baseData) {
    baseData.foo = baseData.foo || 'bar';
});
1
Mudassir Ali

Dans le contrôleur enfant, vous pouvez faire:

angular.extend($scope, $scope.$parent)

Si le contrôleur est utilisé avec un alias, par ex. 'vm' tu peux faire:

let vm = angular.extend(this, $scope.$parent.vm);
0
IonutAnin