Aninhamento complexo de parciais e modelos


503

Minha pergunta envolve como lidar com o aninhamento complexo de modelos (também chamados de parciais ) em um aplicativo AngularJS.

A melhor maneira de descrever minha situação é com uma imagem que eu criei:

Diagrama de página AngularJS

Como você pode ver, isso tem o potencial de ser um aplicativo bastante complexo com muitos modelos aninhados.

O aplicativo é de página única, portanto, carrega um index.html que contém um elemento div no DOM com o ng-viewatributo

Para o círculo 1 , você vê que existe uma navegação Primária que carrega os modelos apropriados no ng-view. Estou fazendo isso passando $routeParamspara o módulo principal do aplicativo. Aqui está um exemplo do que está no meu aplicativo:

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

    }]);

No círculo 2 , o modelo carregado na ng-viewpossui uma subavegação adicional . Essa sub-navegação precisa carregar modelos na área abaixo dela - mas como o ng-view já está sendo usado, não tenho certeza de como fazê-lo.

Eu sei que posso incluir modelos adicionais no primeiro modelo, mas todos eles serão bastante complexos. Gostaria de manter todos os modelos separados para facilitar a atualização do aplicativo e não ter uma dependência do modelo pai que precisa ser carregado para acessar seus filhos.

No círculo 3 , você pode ver as coisas ficarem ainda mais complexas. Existe o potencial de que os modelos de subavegação tenham uma segunda subavegação que precisará carregar seus próprios modelos também na área do círculo 4

Como estruturar um aplicativo AngularJS para lidar com esse complexo aninhamento de modelos, mantendo-os todos separados um do outro?


3
Se você ainda está seguindo este tópico, adicionei um link para um novo projeto AngularUI para abordar esta e uma terceira demonstração que é alimentada por sub-roteiros sem precisar de uma diretiva para a minha resposta.
ProLoser

2
que tal este? bennadel.com/blog/…
ericbae

82
mais bonita pergunta que eu já vi em muito tempo :)
Mark Nadig

2
acordado! Amo o uso de maquete
Bryan Hong

4
Seria ótimo adicionar links para as alternativas diretamente na pergunta aqui. github.com/angular-ui/ui-router github.com/artch/angular-route-segment github.com/dotJEM/angular-routing
Jens

Respostas:


171

Bem, como atualmente você pode ter apenas uma diretiva ngView ... eu uso controles de diretiva aninhados. Isso permite configurar modelos e herdar (ou isolar) escopos entre eles. Fora isso, uso o ng-switch ou até apenas o ng-show para escolher quais controles serão exibidos com base no que está vindo de $ routeParams.

EDIT Aqui está um exemplo de pseudo-código para lhe dar uma idéia do que estou falando. Com uma subnavegação aninhada.

Aqui está a página principal do aplicativo

<!-- 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 a sub-navegação

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

modelo para a sub navegação

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

modelo para uma página principal (da navegação 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 uma página principal. (da navegação principal)

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

Diretiva para uma subárea

app.directive('mySubArea1', function(){
    return {
        restrict: 'E',
        templateUrl: 'mySubArea1.html',
        controller: function($scope) {
            //controller for your sub area.
        }
    };
});

9
Gosto mais da solução do ProLoser, fazemos algo assim no nosso aplicativo e funcionou bem. O problema com a solução do blesh é o código do controlador que entra nas diretivas. Normalmente, o controlador que você especifica em uma diretiva é aquele que trabalha em estreita colaboração com a diretiva para ex: ngModelCtrl, que trabalha em estreita colaboração com a entrada e outras diretivas. No seu caso, colocar o código do controlador dentro de uma diretiva seria um cheiro de código, na verdade, é um controlador independente.
ChrisOdney

@blesh, que bom! Parece realmente bom explicado, mas como um bom programador de JS e iniciante no AngularJS, eu não conseguia entender muito bem ... Eu e a comunidade realmente desfrutávamos de um link do JSFiddle para uma amostra de trabalho usando essa abordagem. Diggin-lo ao redor seria fácil de entender! :) Obrigado
Roger Barreto

8
Eu acho que essa solução deve ser a primeira resposta para qualquer tipo de pergunta 'exibição aninhada não funcionou'. Só porque é muito mais próximo da ideologia angular em vez de usar o ui-router e etc. Obrigado.
Sergei Panfilov 7/11

Então a resposta é diretivas?
Marc M.

para destacar os itens sub-nav que você pode usar: coder1.com/articles/angularjs-managing-active-nav-elements é uma boa maneira de fazer isso?
precisa saber é o seguinte

198

ATUALIZAÇÃO: Confira o novo projeto do AngularUI para solucionar esse problema


Para subseções, é tão fácil quanto aproveitar as strings no 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 você pode criar um objeto caso tenha links para subpáginas em todo o 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>

Ou você pode até 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>

Você também pode colocar um ng-controller no nível mais alto de cada


8
Eu gosto mais da sua solução. Eu sou um novato em Angular e isso parece muito mais compreensível de como vejo a web até hoje. quem sabe, pode ser que o blesh use mais da estrutura angular para fazer isso, mas parece que você o acertou com menos linhas de código de uma maneira mais sensata. obrigado!
Gleeb

2
@ProLooser talvez você significou este link github.com/angular-ui/ui-router ... o que você colou é quebrado
simonC

4
Estou tendo o mesmo problema de design do OP. Alguém já tentou o mecanismo de estado AngularUI? Estou bastante relutante em usar outra biblioteca de terceiros e prefiro ficar com o 'jeito de fazer as coisas' do AngularJS. Mas, por outro lado, o sistema de roteamento parece ser o calcanhar de Aquiles ... @PhillipKregg, o que você acabou usando para resolver esse cenário?
27413 Sam

4
Você pode especificar <div ng-include="'/home/' + tab + '.html'" ng-controller="SubCtrl"></div>para usar um controlador / escopo separado para o submodelo. Ou apenas especifique a ngControllerdiretiva em qualquer lugar dentro dos seus sub-modelos para usar um controlador diferente para cada parcial.
colllin

2
@DerekAdair o projeto é bastante estável (apesar do número da versão) e está em uso na produção em alguns lugares. Impede o recarregamento desnecessário do controlador e é uma alternativa muito melhor à minha solução sugerida.
ProLoser

26

Você também pode fazer o check-out desta biblioteca com a mesma finalidade:

http://angular-route-segment.com

Parece o que você está procurando e é muito mais simples de usar que o roteador da interface do usuário. No site de demonstração :

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 nível 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 aninhado:

<h4>Section 1</h4>
Section 1 contents.
<div app-view-segment="1"></div>

Artem, a sua é a melhor solução que encontrei até agora! Gosto do fato de você estender a rota angular em vez de substituí-la como a interface do usuário angular.
precisa saber é o seguinte

17

Eu também estava lutando com vistas aninhadas no Angular.

Depois que eu peguei o ui-router , sabia que nunca mais voltaria à funcionalidade de roteamento angular padrão.

Aqui está um exemplo de aplicativo que usa vários níveis de visualização de aninhamento

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 pode ser visto, existem 4 visualizações principais (view1, view2, view3, view4) e view1 possui 3 visualizações filho.


2
Qual é o propósito de injetar $httpProvider? Não o vejo usado em lugar nenhum.
Aaron

@ Aaron app.config não termina neste pedaço de código e pode ser $ httpProvider é usado em outro lugar
Vlad

4

Você pode usar ng-include para evitar o uso de visualizações ng aninhadas.

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

Minha página de índice eu uso o ng-view. Em seguida, nas minhas subpáginas, que eu preciso ter quadros aninhados. Eu uso ng-include. A demonstração mostra uma lista suspensa. Substituí o meu por um link ng-click. Na função eu colocaria $ scope.template = $ scope.templates [0]; ou $ scope.template = $ scope.templates [1];

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.