Anidamiento complejo de parciales y plantillas

503

Mi pregunta implica cómo tratar con el anidamiento complejo de plantillas (también llamadas parciales ) en una aplicación AngularJS.

La mejor manera de describir mi situación es con una imagen que creé:

Diagrama de página AngularJS

Como puede ver, esto tiene el potencial de ser una aplicación bastante compleja con muchos modelos anidados.

La aplicación es de una sola página, por lo que carga un index.html que contiene un elemento div en el DOM con el ng-viewatributo.

Para el círculo 1 , verá que hay una navegación principal que carga las plantillas apropiadas en el ng-view. Estoy haciendo esto pasando $routeParamsal módulo principal de la aplicación. Aquí hay un ejemplo de lo que hay en mi aplicación:

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' })       

    }]);

En el círculo 2 , la plantilla que se carga en el ng-viewtiene una subnavegación adicional . Este subnavegador necesita cargar plantillas en el área debajo de él, pero dado que ng-view ya se está utilizando, no estoy seguro de cómo hacerlo.

Sé que puedo incluir plantillas adicionales dentro de la primera plantilla, pero todas estas plantillas serán bastante complejas. Me gustaría mantener todas las plantillas separadas para que la aplicación sea más fácil de actualizar y no depender de la plantilla principal que debe cargarse para acceder a sus elementos secundarios.

En el círculo 3 , puedes ver que las cosas se vuelven aún más complejas. Existe la posibilidad de que las plantillas de subnavegación tengan una segunda subnavegación que también necesite cargar sus propias plantillas en el área del círculo 4

¿Cómo se puede estructurar una aplicación AngularJS para lidiar con un anidamiento tan complejo de plantillas y mantenerlas separadas entre sí?

PhillipKregg
fuente
3
Si aún sigue este hilo, agregué un enlace a un nuevo proyecto de AngularUI para abordar esto y una tercera demostración que funciona con subrutas sin necesidad de una directiva para mi respuesta.
ProLoser
2
¿Qué hay de este? bennadel.com/blog/…
ericbae
82
pregunta más bonita que he visto en mucho tiempo :)
Mark Nadig
2
¡convenido! Me encanta el uso de la maqueta
Bryan Hong
44
Sería genial agregar enlaces a las alternativas directamente en la pregunta aquí. github.com/angular-ui/ui-router github.com/artch/angular-route-segment github.com/dotJEM/angular-routing
Jens

Respuestas:

171

Bueno, dado que actualmente solo puede tener una directiva ngView ... utilizo controles de directiva anidados. Esto le permite configurar plantillas y heredar (o aislar) ámbitos entre ellos. Fuera de eso, uso ng-switch o incluso ng-show para elegir qué controles estoy mostrando en función de lo que viene de $ routeParams.

EDITAR Aquí hay un ejemplo de pseudocódigo para darle una idea de lo que estoy hablando. Con una navegación secundaria anidada.

Aquí está la página principal de la aplicación.

<!-- 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>

Directiva para la subnavegación

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

plantilla para la navegación secundaria

<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>

plantilla para una página principal (desde el navegador principal)

<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>

Controlador para una página principal. (desde el navegador principal)

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

Directiva para una subárea

app.directive('mySubArea1', function(){
    return {
        restrict: 'E',
        templateUrl: 'mySubArea1.html',
        controller: function($scope) {
            //controller for your sub area.
        }
    };
});
Ben Lesh
fuente
99
Me gusta más la solución de ProLoser, hacemos algo así en nuestra aplicación y ha funcionado bien. El problema con la solución de blesh es el código del controlador que entra en las directivas. Por lo general, el controlador que especifique en una directiva es uno que funciona estrechamente con la directiva por ejemplo: ngModelCtrl que trabaja estrechamente con la entrada y otras directivas. En su caso, poner el código del controlador dentro de una directiva sería un olor a código, en realidad es un controlador independiente.
ChrisOdney
@blesh, bien! Parece muy bien explicado, pero como un buen programador de JS y principiante en AngularJS no podría entenderlo bien ... Yo y la comunidad realmente disfrutaríamos de un enlace JSFiddle a una muestra de trabajo utilizando ese enfoque. Diggin alrededor sería fácil de entender! :) Gracias
Roger Barreto
8
Creo que esta solución debería ser la primera respuesta para cualquier tipo de pregunta de 'vista anidada que no funcionó'. Solo porque es mucho más cercano a la ideología angular en lugar de usar ui-router y etc. Gracias.
Sergei Panfilov
Entonces, ¿la respuesta son las directivas?
Marc M.
para resaltar los elementos de navegación secundaria, puede usar esto: coder1.com/articles/angularjs-managing-active-nav-elements ¿ es esta una buena manera de hacerlo?
escapedcat
198

ACTUALIZACIÓN: Echa un vistazo al nuevo proyecto de AngularUI para abordar este problema


Para subsecciones es tan fácil como aprovechar cadenas en 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>

O puede crear un objeto en caso de que tenga enlaces a subpáginas en todo el lugar:

$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>

O incluso puedes usar $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>

También puede poner un controlador ng en el nivel superior de cada parcial

ProLoser
fuente
8
Me gusta más tu solución. Soy un novato en Angular y esto parece mucho más comprensible desde cómo veo la web hasta hoy. quién sabe, puede que Blesh use más del marco angular para hacerlo, pero parece que lo has clavado con menos líneas de código de una manera más sensata. ¡Gracias!
Gleeb
2
@ProLooser tal vez quisiste decir este enlace github.com/angular-ui/ui-router ... el que
pegaste
44
Tengo el mismo problema de diseño que el OP. ¿Alguien ha probado el mecanismo de estado AngularUI? Soy bastante reacio a usar otra biblioteca de terceros y prefiero seguir con la 'forma de hacer las cosas' de AngularJS. Pero, por otro lado, el sistema de enrutamiento parece ser el talón de Aquiles ... @PhillipKregg, ¿qué terminaste usando para resolver este escenario?
Sam
44
Puede especificar <div ng-include="'/home/' + tab + '.html'" ng-controller="SubCtrl"></div>para usar un controlador / ámbito separado para la sub-plantilla. O simplemente especifique la ngControllerdirectiva en cualquier lugar dentro de su (s) sub-plantilla (s) para usar un controlador diferente para cada parcial.
colllin
2
@DerekAdair el proyecto es bastante estable (a pesar del número de versión) y en uso en la producción en algunos lugares. Evita la recarga innecesaria del controlador y es una alternativa mucho mejor a mi solución sugerida.
ProLoser
26

Puede retirar esta biblioteca con el mismo propósito también:

http://angular-route-segment.com

Parece lo que está buscando, y es mucho más simple de usar que ui-router. Desde el sitio de demostración :

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 nivel superior:

<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 anidado:

<h4>Section 1</h4>
Section 1 contents.
<div app-view-segment="1"></div>
artch
fuente
¡Artem, la tuya es la mejor solución que he encontrado hasta la fecha! Me gusta el hecho de que extiendas la ruta angular en lugar de reemplazarla como lo hace la interfaz de usuario angular.
Último tribunal
17

Yo también estaba luchando con vistas anidadas en Angular.

Una vez que obtuve el enrutador ui, supe que nunca volvería a la funcionalidad de enrutamiento angular predeterminado.

Aquí hay una aplicación de ejemplo que usa múltiples niveles de anidamiento de vistas

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',
    });
});

Como se puede ver, hay 4 vistas principales (view1, view2, view3, view4) y view1 tiene 3 vistas secundarias.

Dan Ochiana
fuente
2
¿Cuál es el propósito de la inyección $httpProvider? No lo veo usado en ningún lado.
Aaron
@Aaron app.config no termina en este código y puede ser $ httpProvider se usa en otro lugar
Vlad
4

Puede usar ng-include para evitar el uso de ng-views anidadas.

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

Mi página de índice uso ng-view. Luego, en mis subpáginas que necesito tener marcos anidados. Yo uso ng-include. La demostración muestra un menú desplegable. Reemplacé el mío con un enlace ng-click. En la función, pondría $ scope.template = $ scope.templates [0]; o $ scope.template = $ scope.templates [1];

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