Imbrication complexe des partiels et des modèles

503

Ma question consiste à savoir comment gérer l'imbrication complexe de modèles (également appelés partiels ) dans une application AngularJS.

La meilleure façon de décrire ma situation est avec une image que j'ai créée:

Diagramme de page AngularJS

Comme vous pouvez le voir, cela peut être une application assez complexe avec beaucoup de modèles imbriqués.

L'application est d'une seule page, elle charge donc un index.html qui contient un élément div dans le DOM avec l' ng-viewattribut.

Pour le cercle 1 , vous voyez qu'il existe une navigation principale qui charge les modèles appropriés dans le ng-view. Je fais cela en passant $routeParamsau module principal de l'application. Voici un exemple de ce qui se trouve dans mon application:

angular.module('myApp', []).
    config(['$routeProvider', function($routeProvider) {
        $routeProvider.                     
            when("/job/:jobId/zones/:zoneId", { controller: JobDetailController, templateUrl: 'assets/job_list_app/templates/zone_edit.html' }).
            when("/job/:jobId/initial_inspection", { controller: JobDetailController, templateUrl: 'assets/job_list_app/templates/initial_inspection.html' }).
            when("/job/:jobId/zones/:zoneId/rooms/:roomId", { controller: JobDetailController, templateUrl: 'assets/job_list_app/templates/room_edit.html' })       

    }]);

Dans le cercle 2 , le modèle chargé dans le ng-viewpossède une sous-navigation supplémentaire . Cette sous-navigation doit ensuite charger des modèles dans la zone située en dessous - mais comme ng-view est déjà utilisé, je ne sais pas comment procéder.

Je sais que je peux inclure des modèles supplémentaires dans le 1er modèle, mais ces modèles vont tous être assez complexes. Je voudrais garder tous les modèles séparés afin de faciliter la mise à jour de l'application et ne pas avoir de dépendance sur le modèle parent devant être chargé pour accéder à ses enfants.

Dans le cercle 3 , vous pouvez voir que les choses deviennent encore plus complexes. Il est possible que les modèles de sous-navigation aient une 2e sous-navigation qui devra également charger ses propres modèles dans la zone du cercle 4

Comment procéder pour structurer une application AngularJS pour gérer une imbrication complexe de modèles tout en les gardant tous séparés les uns des autres?

PhillipKregg
la source
3
Si vous suivez toujours ce fil, j'ai ajouté un lien vers un nouveau projet AngularUI pour résoudre ce problème et une troisième démo alimentée par des sous-routes sans avoir besoin d'une directive pour ma réponse.
ProLoser
2
et celui-ci? bennadel.com/blog/…
ericbae
82
la plus jolie question que j'ai vue depuis longtemps :)
Mark Nadig
2
D'accord! J'adore l'utilisation de maquettes
Bryan Hong
4
Ce serait bien d'ajouter des liens vers les alternatives directement dans la question ici. github.com/angular-ui/ui-router github.com/artch/angular-route-segment github.com/dotJEM/angular-routing
Jens

Réponses:

171

Eh bien, puisque vous ne pouvez actuellement avoir qu'une seule directive ngView ... J'utilise des contrôles de directive imbriqués. Cela vous permet de configurer des modèles et d'hériter (ou d'isoler) des étendues parmi eux. En dehors de cela, j'utilise ng-switch ou même juste ng-show pour choisir les contrôles que j'affiche en fonction de ce qui vient de $ routeParams.

EDIT Voici un exemple de pseudo-code pour vous donner une idée de ce dont je parle. Avec une sous-navigation imbriquée.

Voici la page principale de l'application

<!-- primary nav -->
<a href="#/page/1">Page 1</a>
<a href="#/page/2">Page 2</a>
<a href="#/page/3">Page 3</a>

<!-- display the view -->
<div ng-view>
</div>

Directive pour la sous-navigation

app.directive('mySubNav', function(){
    return {
        restrict: 'E',
        scope: {
           current: '=current'
        },
        templateUrl: 'mySubNav.html',
        controller: function($scope) {
        }
    };
});

modèle pour la sous-navigation

<a href="#/page/1/sub/1">Sub Item 1</a>
<a href="#/page/1/sub/2">Sub Item 2</a>
<a href="#/page/1/sub/3">Sub Item 3</a>

modèle pour une page principale (à partir de la navigation principale)

<my-sub-nav current="sub"></my-sub-nav>

<ng-switch on="sub">
  <div ng-switch-when="1">
      <my-sub-area1></my-sub-area>
  </div>
  <div ng-switch-when="2">
      <my-sub-area2></my-sub-area>
  </div>
  <div ng-switch-when="3">
      <my-sub-area3></my-sub-area>
  </div>
</ng-switch>

Contrôleur pour une page principale. (à partir de la navigation principale)

app.controller('page1Ctrl', function($scope, $routeParams) {
     $scope.sub = $routeParams.sub;
});

Directive pour une sous-zone

app.directive('mySubArea1', function(){
    return {
        restrict: 'E',
        templateUrl: 'mySubArea1.html',
        controller: function($scope) {
            //controller for your sub area.
        }
    };
});
Ben Lesh
la source
9
J'aime mieux la solution de ProLoser, nous faisons quelque chose comme ça sur notre application et cela a bien fonctionné. Le problème avec la solution de blesh est le code du contrôleur entrant dans les directives. Habituellement, le contrôleur que vous spécifiez dans une directive est un contrôleur qui fonctionne étroitement avec la directive ex: ngModelCtrl qui fonctionne étroitement avec les directives d'entrée et autres. Dans votre cas, mettre le code du contrôleur dans une directive serait une odeur de code, c'est en fait un contrôleur indépendant.
ChrisOdney
@blesh, sympa! Cela semble vraiment bien expliqué, mais en tant que bon programmeur JS et débutant dans AngularJS, je ne comprends pas très bien ... Moi et la communauté apprécierions vraiment un lien JSFiddle vers un échantillon de travail en utilisant cette approche. Diggin it around serait facile à comprendre! :) Merci
Roger Barreto
8
Je pense que cette solution devrait être une première réponse à toute question de type «vue imbriquée qui n'a pas fonctionné». Tout simplement parce qu'il est beaucoup plus proche de l'idéologie angulaire au lieu d'utiliser ui-router et etc. Merci.
Sergei Panfilov
La réponse est donc des directives?
Marc M.
pour mettre en évidence les éléments de sous-navigation, vous pouvez utiliser ceci: coder1.com/articles/angularjs-managing-active-nav-elements est-ce une bonne façon de le faire?
escapedcat
198

MISE À JOUR: Découvrez le nouveau projet d'AngularUI pour résoudre ce problème


Pour les sous-sections, c'est aussi simple que d'utiliser les chaînes dans ng-include:

<ul id="subNav">
  <li><a ng-click="subPage='section1/subpage1.htm'">Sub Page 1</a></li>
  <li><a ng-click="subPage='section1/subpage2.htm'">Sub Page 2</a></li>
  <li><a ng-click="subPage='section1/subpage3.htm'">Sub Page 3</a></li>
</ul>
<ng-include src="subPage"></ng-include>

Ou vous pouvez créer un objet au cas où vous auriez des liens vers des sous-pages partout:

$scope.pages = { page1: 'section1/subpage1.htm', ... };
<ul id="subNav">
  <li><a ng-click="subPage='page1'">Sub Page 1</a></li>
  <li><a ng-click="subPage='page2'">Sub Page 2</a></li>
  <li><a ng-click="subPage='page3'">Sub Page 3</a></li>
</ul>
<ng-include src="pages[subPage]"></ng-include>

Ou vous pouvez même utiliser $routeParams

$routeProvider.when('/home', ...);
$routeProvider.when('/home/:tab', ...);
$scope.params = $routeParams;
<ul id="subNav">
  <li><a href="#/home/tab1">Sub Page 1</a></li>
  <li><a href="#/home/tab2">Sub Page 2</a></li>
  <li><a href="#/home/tab3">Sub Page 3</a></li>
</ul>
<ng-include src=" '/home/' + tab + '.html' "></ng-include>

Vous pouvez également mettre un ng-controller au niveau le plus haut de chaque partiel

ProLoser
la source
8
J'aime mieux votre solution. je suis un newb à Angular et cela semble beaucoup plus compréhensible de la façon dont je vois le web jusqu'à aujourd'hui. qui sait, peut-être que blesh utilise davantage le cadre angulaire pour le faire, mais il semble que vous l'ayez cloué avec moins de lignes de code de manière plus sensible. Merci!
Gleeb
2
@ProLooser peut-être que vous vouliez dire ce lien github.com/angular-ui/ui-router ... celui que vous avez collé est cassé
simonC
4
J'ai le même problème de conception que l'OP. Quelqu'un at-il essayé le mécanisme d'état AngularUI? Je suis assez réticent à l'idée d'utiliser une autre bibliothèque tierce et je préfère m'en tenir à la «façon de faire» d'AngularJS. Mais d'un autre côté, le système de routage semble être le talon d'Achille ... @PhillipKregg, qu'avez-vous fini par utiliser pour résoudre ce scénario?
Sam
4
Vous pouvez spécifier <div ng-include="'/home/' + tab + '.html'" ng-controller="SubCtrl"></div>afin d'utiliser un contrôleur / portée distinct pour le sous-modèle. Ou spécifiez simplement la ngControllerdirective n'importe où dans vos sous-modèles pour utiliser un contrôleur différent pour chaque partiel.
colllin
2
@DerekAdair le projet est assez stable (malgré le numéro de version) et utilisé en production à quelques endroits. Il empêche le rechargement inutile du contrôleur et constitue une bien meilleure alternative à ma solution suggérée.
ProLoser
26

Vous pouvez également consulter cette bibliothèque dans le même but:

http://angular-route-segment.com

Il ressemble à ce que vous recherchez, et il est beaucoup plus simple à utiliser que ui-router. Depuis le site de démonstration :

JS:

$routeSegmentProvider.

when('/section1',          's1.home').
when('/section1/:id',      's1.itemInfo.overview').
when('/section2',          's2').

segment('s1', {
    templateUrl: 'templates/section1.html',
    controller: MainCtrl}).
within().
    segment('home', {
        templateUrl: 'templates/section1/home.html'}).
    segment('itemInfo', {
        templateUrl: 'templates/section1/item.html',
        controller: Section1ItemCtrl,
        dependencies: ['id']}).
    within().
        segment('overview', {
            templateUrl: 'templates/section1/item/overview.html'}).

HTML de premier niveau:

<ul>
    <li ng-class="{active: $routeSegment.startsWith('s1')}">
        <a href="/section1">Section 1</a>
    </li>
    <li ng-class="{active: $routeSegment.startsWith('s2')}">
        <a href="/section2">Section 2</a>
    </li>
</ul>
<div id="contents" app-view-segment="0"></div>

HTML imbriqué:

<h4>Section 1</h4>
Section 1 contents.
<div app-view-segment="1"></div>
artch
la source
Artem, la vôtre est la meilleure solution que j'ai trouvée à ce jour! J'aime le fait que vous étendez la route angulaire plutôt que de la remplacer comme le fait l'interface utilisateur angulaire.
LastT Tribunal
17

Moi aussi, je me débattais avec des vues imbriquées dans Angular.

Une fois que j'ai mis la main sur ui-router, je savais que je ne retournerais jamais à la fonctionnalité de routage par défaut angulaire.

Voici un exemple d'application qui utilise plusieurs niveaux d'imbrication de vues

app.config(function ($stateProvider, $urlRouterProvider,$httpProvider) {
// navigate to view1 view by default
$urlRouterProvider.otherwise("/view1");

$stateProvider
    .state('view1', {
        url: '/view1',
        templateUrl: 'partials/view1.html',
        controller: 'view1.MainController'
    })
    .state('view1.nestedViews', {
        url: '/view1',
        views: {
            'childView1': { templateUrl: 'partials/view1.childView1.html' , controller: 'childView1Ctrl'},
            'childView2': { templateUrl: 'partials/view1.childView2.html', controller: 'childView2Ctrl' },
            'childView3': { templateUrl: 'partials/view1.childView3.html', controller: 'childView3Ctrl' }
        }
    })

    .state('view2', {
        url: '/view2',
    })

    .state('view3', {
        url: '/view3',
    })

    .state('view4', {
        url: '/view4',
    });
});

Comme on peut le voir, il y a 4 vues principales (view1, view2, view3, view4) et view1 a 3 vues enfant.

Dan Ochiana
la source
2
Quel est le but de l'injection $httpProvider? Je ne le vois utilisé nulle part.
Aaron
@Aaron app.config ne se termine pas dans ce morceau de code et peut être $ httpProvider est utilisé ailleurs
Vlad
4

Vous pouvez utiliser ng-include pour éviter d'utiliser des vues ng imbriquées.

http://docs.angularjs.org/api/ng/directive/ngInclude
http://plnkr.co/edit/ngdoc:example-example39@snapshot?p=preview

Ma page d'index j'utilise ng-view. Puis sur mes sous-pages dont j'ai besoin d'avoir des cadres imbriqués. J'utilise ng-include. La démo montre une liste déroulante. J'ai remplacé le mien par un lien ng-click. Dans la fonction, je mettrais $ scope.template = $ scope.templates [0]; ou $ scope.template = $ scope.templates [1];

$scope.clickToSomePage= function(){
  $scope.template = $scope.templates[0];
};
Henry Mac
la source
2

L'interface utilisateur angulaire prend en charge les vues imbriquées. Je ne l'ai pas encore utilisé mais semble très prometteur.

http://angular-ui.github.io/ui-router/

Adriaan Bouman
la source