My question involves how to go about dealing with complex nesting of templates (also called partials) in an AngularJS application.
The best way to describe my situation is with an image I created:
As you can see this has the potential to be a fairly complex application with lots of nested models.
The application is single-page, so it loads an index.html that contains a div element in the DOM with the ng-view attribute.
For circle 1, You see that there is a Primary navigation that loads the appropriate templates into the ng-view. I'm doing this by passing $routeParams to the main app module. Here is an example of what's in my app:
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' })
}]);
In circle 2, the template that is loaded into the ng-view has an additional sub-navigation. This sub-nav then needs to load templates into the area below it - but since ng-view is already being used, I'm not sure how to go about doing this.
I know that I can include additional templates within the 1st template, but these templates are all going to be pretty complex. I would like to keep all the templates separate in order to make the application easier to update and not have a dependency on the parent template having to be loaded in order to access its children.
In circle 3, you can see things get even more complex. There is the potential that the sub-navigation templates will have a 2nd sub-navigation that will need to load its own templates as well into the area in circle 4
How does one go about structuring an AngularJS app to deal with such complex nesting of templates while keeping them all separate from one another?
UPDATE: Check out AngularUI's new project to address this problem
For subsections it's as easy as leveraging strings in 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>
Or you can create an object in case you have links to sub pages all over the place:
$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>
Or you can even use $routeParams
$routeProvider.when('/home', ...);
$routeProvider.when('/home/:tab', ...);
$scope.params = $routeParams;
<ul id="subNav">
<li>Sub Page 1</li>
<li>Sub Page 2</li>
<li>Sub Page 3</li>
</ul>
<ng-include src=" '/home/' + tab + '.html' "></ng-include>
You can also put an ng-controller at the top-most level of each partial
Well, since you can currently only have one ngView directive... I use nested directive controls. This allows you to set up templating and inherit (or isolate) scopes among them. Outside of that I use ng-switch or even just ng-show to choose which controls I'm displaying based on what's coming in from $routeParams.
EDIT Here's some example pseudo-code to give you an idea of what I'm talking about. With a nested sub navigation.
Here's the main app page
<!-- primary nav -->
Page 1
Page 2
Page 3
<!-- display the view -->
<div ng-view>
</div>
Directive for the sub navigation
app.directive('mySubNav', function(){
return {
restrict: 'E',
scope: {
current: '=current'
},
templateUrl: 'mySubNav.html',
controller: function($scope) {
}
};
});
template for the sub navigation
Sub Item 1
Sub Item 2
Sub Item 3
template for a main page (from primary nav)
<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>
Controller for a main page. (from the primary nav)
app.controller('page1Ctrl', function($scope, $routeParams) {
$scope.sub = $routeParams.sub;
});
Directive for a Sub Area
app.directive('mySubArea1', function(){
return {
restrict: 'E',
templateUrl: 'mySubArea1.html',
controller: function($scope) {
//controller for your sub area.
}
};
});
You may checkout this library for the same purpose also:
http://angular-route-segment.com
It looks like what you are looking for, and it is much simpler to use than ui-router. From the demo site:
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'}).
Top-level HTML:
<ul>
<li ng-class="{active: $routeSegment.startsWith('s1')}">
Section 1
</li>
<li ng-class="{active: $routeSegment.startsWith('s2')}">
Section 2
</li>
</ul>
<div id="contents" app-view-segment="0"></div>
Nested HTML:
<h4>Section 1</h4>
Section 1 contents.
<div app-view-segment="1"></div>
I too was struggling with nested views in Angular.
Once I got a hold of ui-router I knew I was never going back to angular default routing functionality.
Here is an example application that uses multiple levels of views nesting
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',
});
});
As it can be seen there are 4 main views (view1,view2,view3,view4) and view1 has 3 child views.
You may use ng-include to avoid using nested ng-views.
http://docs.angularjs.org/api/ng/directive/ngInclude
http://plnkr.co/edit/ngdoc:example-example39#snapshot?p=preview
My index page I use ng-view. Then on my sub pages which I need to have nested frames. I use ng-include.
The demo shows a dropdown. I replaced mine with a link ng-click.
In the function I would put $scope.template = $scope.templates[0]; or $scope.template = $scope.templates[1];
$scope.clickToSomePage= function(){
$scope.template = $scope.templates[0];
};
Angular ui-router supports nested views. I haven't used it yet but looks very promising.
http://angular-ui.github.io/ui-router/
Related
link to example: http://goo.gl/jJBMZL
Here two states are defined. 1. home , 2. about
home has 1 view viewA
about has 2 views viewA and viewB but viewB is a sub part of about page. I want to show viewB inside of about.html page and not on index page. Is this possible. Please provide a plunkr link thanks.
what is the best practice if there are various views and each view has sub states
You can include your page 'about-sub-level.html' inside 'about.html' using ng-include like as-
<h1>I am about page</h1>
<div ng-include="'about-sub-level.html'"> </div>
Change your routing like below-
.state('about', {
url: "/about",
views: {
"viewA": {
templateUrl: "about.html"
}
}
})
Hope this may help you..
I am trying to implement ui-router(i.e $stateProvider) with my AngularApp.
My Logic is I have different modules. suppose Album (it have various views.)
What I am trying to implement is creating it as module. That means single controller for all states as well mixed logic in single controller file.
I created 1 files names list.
my state would be like this
.state('album', {url: '/album', templateUrl: 'views/album/list.html'})
//code would like this.
<li class="col-md-4" ng-repeat="item in items">
<p class="list-item">
<i class="fa fa-angle-double-right"></i>
<a class="cursor" href="#/poet/detail/{{item.id}}">{{item.name}}</a>
</p>
</li>
Now when we click it move to detail page. Having various tabs to render on same page. Corresponding states would be like this
.state('album.detail', {url: '/detail/:itemId', templateUrl: 'views/album/album.detail.html'})
.state('album.comment', {url: '/comment/:itemId', templateUrl: 'views/album/album.comment.html'})
.state('album.tracklist', {url: '/tracklist/:itemId', templateUrl: 'views/album/album.tracklist.html'})
.state('album.review', {url: '/review/:itemId', templateUrl: 'views/album/album.review.html'})
//All states have same controller, I would call services on switch condition on (states). Now issue is when I click on detail link from the list page I mentioned earlier it navigates to the base url.
//Instead of linking this http://0.0.0.0:3000/#/poets -> ttp://0.0.0.0:3000/#/poets/detail/12 It transfer to the base url i.e. - http://0.0.0.0:3000/#
Hi I am trying to render a new view based on clicks of a button in a navbar. However, it is not working.
Here is my HTML Code:
<!-- Links to all the pages with data entry forms -->
<div id="data_links" ng-controller="MainCtrl">
<ul>
<li>
Home
</li>
</ul>
</div>
</div>
<!-- Modify views here -->
<div class="main" ng-controller="MainCtrl">
<main role="main">
<div ng-view> <!-- Where we will inject html --> </div>
</main>
</div>
<!-- Application Files -->
<script src="js/app.js"></script>
<script src="js/controllers/main.js"></script>
Here is my app.js:
angular.module('DataEntry', [
'ngRoute'
]).config(function ( $routeProvider ) {
$routeProvider
.when('instructions', {
templateUrl: 'views/instructions.html',
controller: 'MainCtrl'
});
});
Here is my controller main.js:
angular.module('DataEntry')
.controller('MainCtrl',
function MainCtrl ( $scope, $location ) {
'use strict';
$scope.ChangeView = function(view) {
alert(view);
$location.url(view);
}
});
This is my instructions.html page for testing to see if it loads:
<div class="module instructions">
<div class="module_instructions">
<h1> Testing Loaded! </h1>
<p> Test <br>
Test <br> Test <br> Test
</p>
</div>
</div>
I want to eventually be able to click on multiple links within a navbar, such as home, instructions, etc. and render different views within the ng-view section. However, it is not working right now and I am not sure how to scale it so I can add more .html pages for the different views I want to render. Can someone help me move forward?
This line
<a href="#" title="Home Page" ng-click = "ChangeView('instructions')"> Home
Can be changed to this:
Home
You don't need to use a function on your controller to set the url, although you can navigate this way - sometimes you want to redirect the user programatically and this works for those cases.
Also, leaving href="#" in that line is causing you a problem. In a non-angular page # is used as a href placeholder but on an Angular href="#" is actually going to be picked up by $routeProvider which will try to change the contents of the ng-view container. What loads will depend upon how you set up your .config section, but is generally not desirable behavior.
As you create more pages, add paths to your .config section and you can link to them from your html the same way as I did above with the /instructions path.
Here's an example:
angular.module('DataEntry', ['ngRoute'])
.config(function ( $routeProvider ) {
$routeProvider
.when('instructions', {
templateUrl: 'views/instructions.html',
controller: 'MainCtrl'
})
.when('faq', {
templateUrl: 'views/faq.html',
controller: 'FaqCtrl'
})
.when('example', {
templateUrl: 'views/example.html',
controller: 'ExampleCtrl'
})
});
and in your markup:
Home
FAQ
Example
My Angular app has several views. The index.html file which has the <div ng-view></div> also has a header/navbar that has links to each of these views.
The $routeProvider is configured to load respective views and their controllers.
Considering this setup, where each of the views has its own controller, how do I add CSS class="active" to the appropriate link in the header when navigating within the app?
Extra Info.:
I added ng-click="set_page_id(x)" and ng-class={active: active_page_id === x} to the links and realized there's no controller associated with the index.html. I wrote a jQuery function to make this work by listening to click events but it doesn't work when a view itself calls another view. I wonder if there's a better, Angular way.
You have your main or nav controller with something like:
app.controller('mainCtrl', function($scope, $rootScope, $location) {
$scope.menu = [
{label:'Home', route:'/'},
{label:'About', route:'/about'},
{label:'Contact', route:'/contact'}
]
$scope.menuActive = '/';
$rootScope.$on('$routeChangeSuccess', function(e, curr, prev) {
$scope.menuActive = $location.path();
});
});
Then
<li ng-repeat="item in menu" ng-class="{active: item.route == menuActive }"><a href="#{{item.route}}" >{{item.label}}</a> </li>
Heres a Plunker
I have followed the documentation on setting up ngInclude and have it working (loading in HTML partials) and changing the partial when a select option is changed.
$scope.templates = [
{ name: 'Overview', url: 'partials/project/overview.html' },
{ name: 'Tasks', url: 'partials/project/tasks.html' }
];
$scope.template = $scope.templates[0];
----
<div ng-controller="Tasks">
<select ng-model="template" ng-options="t.name for t in templates">
<option value="">(blank)</option>
</select>
<section ng-include="template.url"></section>
</div>
However, I don't want to use a select menu to navigate. I want to use an unordered list. I tried using ngHref, but that doesn't seem to work. I can't find much documentation on binding an element to change an ngInclude partial, but maybe I'm searching for the wrong thing.
Any help on how I could use anchor tags to change the partial being loaded in on click would be great.
Here's the structure I was playing around with:
<ul class="header-menu">
<li><a ng-modal="template" ng-href="{{template[0]}}" class="selected">Overview</a></li>
<li><a ng-modal="template" ng-href="{{template[1]}}">Tasks</a></li>
</ul>
You just need to store the path to your template, and change it based on an action in your list. ngHref isn't related here, because it would load the template itself which isn't what you want.
See this demo plunker:
HTML:
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="template in templates">
{{template.name}}
<button ng-click="selectTemplate(template)">Go!</button>
</li>
</ul>
{{selectedTemplate}}
<div ng-include="selectedTemplate"></div>
</body>
Controller:
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.templates=[{
name:'First Template',
url:'template1.html'
}
,{
name:'Second Template',
url:'template2.html'
}
]
$scope.selectedTemplate=$scope.templates[0].url;
$scope.selectTemplate=function(template){
$scope.selectedTemplate=template.url;
}
});
But I really suggest you to check out to check out ui-router, as it sounds like you are trying to implement some tab system, and changing states for your app, and ui-router does that perfectly.