Złożone zagnieżdżanie częściowych i szablonów


503

Moje pytanie dotyczy sposobu radzenia sobie ze złożonym zagnieżdżaniem szablonów (zwanych także częściowymi ) w aplikacji AngularJS.

Najlepszym sposobem opisania mojej sytuacji jest obraz, który stworzyłem:

Schemat strony AngularJS

Jak widać, może to być dość złożona aplikacja z wieloma zagnieżdżonymi modelami.

Aplikacja jest jednostronicowa, więc ładuje plik index.html zawierający element div w DOM z ng-viewatrybutem.

Dla kręgu 1 widać, że istnieje Nawigacja podstawowa, która ładuje odpowiednie szablony do ng-view. Robię to, przechodząc $routeParamsdo głównego modułu aplikacji. Oto przykład tego, co jest w mojej aplikacji:

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

    }]);

W okręgu 2 szablon ładowany do ng-viewma dodatkową nawigację podrzędną . Ta nawigacja podrzędna musi następnie załadować szablony do obszaru poniżej - ale ponieważ ng-view jest już używany, nie jestem pewien, jak to zrobić.

Wiem, że mogę dołączyć dodatkowe szablony do pierwszego szablonu, ale wszystkie te szablony będą dość złożone. Chciałbym zachować wszystkie szablony osobno, aby aplikacja była łatwiejsza do aktualizacji i nie była zależna od szablonu nadrzędnego, który należy załadować, aby uzyskać dostęp do jego elementów podrzędnych.

W kręgu 3 możesz zobaczyć, że sprawy stają się jeszcze bardziej złożone. Istnieje możliwość, że szablony nawigacji podrzędnej będą miały drugą nawigację podrzędną, która będzie musiała również załadować własne szablony do obszaru w okręgu 4

W jaki sposób buduje się aplikację AngularJS, aby radzić sobie z tak złożonym zagnieżdżaniem szablonów, zachowując je wszystkie oddzielnie?


3
Jeśli nadal śledzisz ten wątek, dodałem link do nowego projektu AngularUI, aby rozwiązać ten problem, oraz do trzeciej wersji demonstracyjnej opartej na podprogramach, która nie wymaga dyrektywy w mojej odpowiedzi.
ProLoser


82
najładniejsze pytanie, jakie widziałem od dłuższego czasu :)
Mark Nadig

2
Zgoda! Uwielbiam używać makiety
Bryan Hong

4
Byłoby wspaniale dodać linki do alternatyw bezpośrednio w pytaniu tutaj. github.com/angular-ui/ui-router github.com/artch/angular-route-segment github.com/dotJEM/angular-routing
Jens

Odpowiedzi:


171

Cóż, ponieważ obecnie możesz mieć tylko jedną dyrektywę ngView ... Używam zagnieżdżonych kontrolek dyrektywy. Umożliwia to konfigurowanie szablonów i dziedziczenie (lub izolowanie) zakresów między nimi. Poza tym używam przełącznika ng lub nawet ng-show, aby wybrać kontrolki, które wyświetlam na podstawie tego, co przychodzi z $ routeParams.

EDYCJA Oto przykładowy pseudo-kod, który daje wyobrażenie o tym, o czym mówię. Z zagnieżdżoną nawigacją podrzędną.

Oto główna strona aplikacji

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

Dyrektywa w sprawie nawigacji podrzędnej

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

szablon nawigacji podrzędnej

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

szablon strony głównej (z podstawowej nawigacji)

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

Kontroler dla strony głównej. (z głównej nawigacji)

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

Dyrektywa dla podobszaru

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

9
Bardziej podoba mi się rozwiązanie ProLoser, robimy coś takiego w naszej aplikacji i działa dobrze. Problem z rozwiązaniem blesh polega na tym, że kod kontrolera przechodzi do dyrektyw. Zwykle kontrolerem, który określasz w dyrektywie, jest ten, który ściśle współpracuje z dyrektywą np. NgModelCtrl, która ściśle współpracuje z wejściowymi i innymi dyrektywami. W twoim przypadku umieszczenie kodu kontrolera w dyrektywie byłoby zapachem kodu, w rzeczywistości jest to niezależny kontroler.
ChrisOdney

@ Rozwiązywanie problemów, miło! Wydaje się to bardzo dobrze wyjaśnione, ale jako dobry programista JS i początkujący w AngularJS nie potrafię tego dobrze złapać ... Ja i społeczność naprawdę podobałoby się łącze JSFiddle do działającej próbki, stosując takie podejście. Wykop go, to byłoby łatwe do zrozumienia! :) Dzięki
Roger Barreto,

8
Myślę, że to rozwiązanie powinno być pierwszą odpowiedzią na wszelkie pytania typu „widok zagnieżdżony nie działał”. Tylko dlatego, że jest o wiele bardziej zbliżony do ideologii Angulara zamiast korzystania z routera ui itp. Dzięki.
Siergiej Panfilow,

Więc odpowiedzią są dyrektywy?
Marc M.

aby podświetlić elementy nawigacji podrzędnej, których możesz użyć: coder1.com/articles/angularjs-managing-active-nav-elements to dobry sposób, aby to zrobić?
uciekł kot

198

AKTUALIZACJA: Sprawdź nowy projekt AngularUI, aby rozwiązać ten problem


W przypadku podrozdziałów jest to tak proste, jak użycie ciągów w 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>

Możesz też utworzyć obiekt na wypadek, gdybyś miał linki do podstron w całym miejscu:

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

Lub możesz nawet użyć $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>

Możesz także umieścić kontroler ng na najwyższym poziomie każdej części


8
Bardziej podoba mi się twoje rozwiązanie. jestem nowicjuszem w Angular i wydaje mi się to o wiele bardziej zrozumiałe z tego, jak widzę sieć do dziś. kto wie, być może blesh używa do tego więcej frameworka, ale wygląda na to, że przybiłeś go mniejszą liczbą linii kodu w bardziej rozsądny sposób. dzięki!
Gleeb

2
@ProLooser może miałeś na myśli ten link github.com/angular-ui/ui-router ... ten, który wkleiłeś , jest zepsuty
simonC

4
Mam ten sam problem projektowy co OP. Czy ktoś próbował mechanizmu stanu AngularUI? Bardzo niechętnie korzystam z innej biblioteki innej firmy i wolałbym trzymać się AngularJS „sposobu robienia rzeczy”. Ale z drugiej strony, system routingu wydaje się być piętą achillesową ... @PhillipKregg, czego użyłeś do rozwiązania tego scenariusza?
Sam

4
Możesz określić <div ng-include="'/home/' + tab + '.html'" ng-controller="SubCtrl"></div>, aby użyć osobnego kontrolera / zakresu dla pod-szablonu. Lub po prostu określ ngControllerdyrektywę w dowolnym miejscu w swoich pod-szablonach, aby użyć innego kontrolera dla każdej części.
colllin

2
@DerekAdair projekt jest dość stabilny (pomimo numeru wersji) i używany w produkcji w kilku miejscach. Zapobiega to niepotrzebnemu przeładowaniu kontrolera i jest znacznie lepszą alternatywą dla mojego sugerowanego rozwiązania.
ProLoser

26

Możesz pobrać tę bibliotekę również w tym samym celu:

http://angular-route-segment.com

Wygląda na to, czego szukasz, i jest o wiele prostszy w użyciu niż interfejs użytkownika. Ze strony demonstracyjnej :

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 najwyższego poziomu:

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

Zagnieżdżony HTML:

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

Artem, twoje jest najlepszym rozwiązaniem, jakie do tej pory znalazłem! Podoba mi się to, że przedłużasz trasę kątową, a nie zastępujesz ją tak, jak robi to interfejs użytkownika.
LastTribunal

17

Ja również walczyłem z zagnieżdżonymi widokami w Angular.

Kiedy już złapałem interfejs użytkownika , wiedziałem, że nigdy nie wrócę do domyślnej funkcjonalności routingu kątowego.

Oto przykładowa aplikacja korzystająca z wielu poziomów zagnieżdżania widoków

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

Jak widać, istnieją 4 główne widoki (widok1, widok2, widok3, widok4), a widok1 ma 3 widoki potomne.


2
Jaki jest cel wstrzykiwania $httpProvider? Nigdzie go nie widzę.
Aaron

@Aaron app.config nie kończy się na tym fragmencie kodu i może być $ httpProvider jest używany gdzie indziej
Vlad

4

Możesz użyć ng-include, aby uniknąć używania zagnieżdżonych widoków ng.

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

Moja strona indeksu używam ng-view. Następnie na moich podstronach, które muszę mieć zagnieżdżone ramki. Używam ng-include. Demo pokazuje menu rozwijane. Mój zastąpiłem linkiem ng-click. W funkcji wstawiłbym $ scope.template = $ scope.templates [0]; lub $ scope.template = $ scope.templates [1];

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.