I'm working on upgrading a project I found to the latest version of Angular and preparing it for Angular 2 conversion etc. So, I'm using some nested views in Angular using ui-router, and I can only get the nested views to display if they are explicitly included in the index page as ng-template files.
index.jade
script(type="text/ng-template", id="404")
include partials/404
script(type="text/ng-template", id="home")
include partials/home
script(type="text/ng-template", id="private/layout")
include partials/private/layout
script(type="text/ng-template", id="private/home")
include partials/private/home
script(type="text/ng-template", id="private/nested")
include partials/private/nested
script(type="text/ng-template", id="private/nestedAdmin")
include partials/private/nestedAdmin
app.routes.js
(function() {
'use strict';
angular
.module('app.routes', [
'ui.router'
])
.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
var access = routingConfig.accessLevels;
// Public routes
$stateProvider
.state('public', {
abstract: true,
template: "<ui-view/>",
data: {
access: access.public
}
})
.state('public.home', {
url: '/',
templateUrl: 'home'
})
.state('public.404', {
url: '/404',
templateUrl: '404'
});
// Regular user routes
$stateProvider
.state('user', {
abstract: true,
template: "<ui-view/>",
data: {
access: access.user
}
})
.state('user.profile', {
url: '/profile',
templateUrl: 'profile',
controller: 'profileController',
controllerAs: 'vm'
})
.state('user.private', {
abstract: true,
url: '/private',
templateUrl: 'private/layout'
})
.state('user.private.home', {
url: '/',
templateUrl: 'private/home'
})
.state('user.private.nested', {
url: '/nested',
templateUrl: 'private/nested'
})
.state('user.private.admin', {
url: '/admin',
templateUrl: 'private/nestedAdmin',
data: {
access: access.admin
}
});
});
})();
I wanted to remove the whole ng-template part as I felt this would not be efficient when scaling the app up, so I removed the ng-template scripts from the index page. When I do this, the first layer of nested routes work, so routes such as public.home work okay. The problem comes with the second layer of nested views, so now routes such as user.private.home and user.private.nested do not work and aren't displayed.
Here is the generated HTML with and without the ng-templates scripts:
WITH ng-templates scripts
<div data-ui-view="" class="col-md-6 col-md-pull-4 ng-scope">
<p class="ng-scope">Only visible to users</p>
</div>
WITHOUT ng-templates
<div data-ui-view="data-ui-view" class="col-md-6 col-md-pull-4 ng-scope"></div>
Any ideas?
Found my own answer here: https://github.com/angular-ui/ui-router/issues/247
Turns out it's an issue with Jade/ui-router, I'm not sure which. The solution is to either include ui-view using pure HTML <div ui-view></div> or by placing doctype html at the top of your partial file.
It was a confusing issue because the nested templates worked fine when injected into $templateCache using ng-template as shown in my post.
Related
We have to design a angular multi-page application. And the pages look something like this:
I planned to design the page in such a way that each section of the page will have a particular angular controller associated to it and a template will be defined which will be added via the ng-include directive. So basically the page 1 (route ==> '/') will have a 4 different sections which will have 4 different controllers.
Now ofcourse this works fine on a single page, but I am unsure how to define the routes here.
1) Should I have nested controllers, so for page 1 we have a page1Controller and all the other controllers will be under this. Will this be a good design?
or
2) Should I have one controller per page which will make the routing easy and have directives defined for each sections of the page?
I think I would recommend just using multiple named views. Each named view can have its own controller:
$stateProvider
.state('home', {
url: '/',
views: {
'': {
templateUrl: 'templates/app.tpl.html',
},
'section1': {
controller: 'Section1Controller as vm',
templateUrl: 'templates/section1.tpl.html'
},
'section2': {
controller: 'Section2Controller as vm',
templateUrl: 'templates/section2.tpl.html'
},
'section3': {
controller: 'Section3Controller as vm',
templateUrl: 'templates/section3.tpl.html'
},
'section4': {
controller: 'Section4Controller as vm',
templateUrl: 'templates/section4.tpl.html'
}
}
})
.state('page2', {
url: '/page2',
views: {
'': {
templateUrl: 'templates/page2.tpl.html',
},
'section1': {
controller: 'Section1Controller as vm',
templateUrl: 'templates/section1.tpl.html'
},
'section2': {
controller: 'Section2Controller as vm',
templateUrl: 'templates/section2.tpl.html'
},
'section3': {
controller: 'Section3Controller as vm',
templateUrl: 'templates/section3.tpl.html'
}
}
})
Then, when you lay out the views, they look something like this:
<div ui-view="section1"></div>
<div ui-view="section2"></div>
<div ui-view="section3"></div>
<div ui-view="section4"></div>
I would use directives to allow multiple controllers, to re-use code between page1 and page 2, and to prepare for the migration to Angular 2.
Your page would look like:
<section1></section1>
<section2></section2>
<section3></section3>
<section4></section4>
And you will have to write a directive per section:
module.directive('section1', function() {
return {
scope: {},
bindToController: {
},
controller: function() { },
controllerAs: 'vm',
template: `
<div>This is section1
</div>
`
}
});
Here is an article to approximate module in Angular 1.x
If you are interested in using TypeScript, here is an tutorial that includes two pages with 2 shared sections using directives as explained above. Look at the section close to the end called 'Sample pages with shared directives.' The tutorial includes a github repository.
In that tutorial, page1 looks like
h1 page1
page1-section1
page1-section2
And, the second page shared the same sections:
h1 page2
page2-section2
page2-section1
The controllers between page1 and page2 are very similar and creates the section tags using the same/shared directives (DigiSection1.Section1Directive):
angular
.module('digiangularjs.page1', [])
.controller('agendaController', Page1Controller)
.directive("page1Section1", [() => new DigiSection1.Section1Directive()])
.directive("page1Section2", [() => new DigiSection2.Section2Directive()])
;
And for the second page, we use the same directives, but
angular
.module('digiangularjs.page2', [])
.controller('page2Controller', Page2Controller)
.directive("page2Section1", [() => new DigiSection1.Section1Directive()])
.directive("page2Section2", [() => new DigiSection2.Section2Directive()])
;
Going off Mike's answer I would define your route-level templates as single components that are high level layout containers.
.state('page1', {
url: '/page1',
template: '<page1></page1>'
})
.state('page2', {
url: '/page2',
template: '<page2></page2>
});
Then in your <page> components (which just dictate layout of nested directives/components) you could do:
.component('page1', {
template: [
'<section1></section1>',
'<section2></section2>',
'<section3></section3>'
].join('')
});
Also I realize you wrote "multi page applicaton" which would suggest you don't plan on using a router at all. If that is the case your backend will have to take care of the dynamic layout generation, which is a totally different question.
I am using angularjs ui-router for a cordova app. I am trying to reuse a ui-view template (left-panel) for multiple states. This ui-view is for almost all the states except one state. I tried to refer many tutorials but still not able to implement what I want. Here is my code in app.js:
var angularApp = angular.module('angularApp', [
'ui.router',
]);
angularApp.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('root', {
abstract: true,
views: {
'left-panel': {
templateUrl: 'templates/common-left-panel.html',
},
}
})
.state('root.home', {
url: '/',
views: {
'container#': {
templateUrl: 'templates/home.html',
}
}
})
.state('root.settings', {
url: 'settings',
views: {
'container#': {
templateUrl: 'templates/settings.html',
}
}
})
.state('root.category', {
url: 'category/:catId',
views: {
'container#': {
templateUrl: 'templates/category-nodes.html',
controller: 'ListCatNodesCtrl'
}
}
})
});
This is in index.html
<div ui-view="left-panel"></div>
<a ui-sref="root.settings">Settings</a>
<div ui-view="container"></div>
With this code, the home page is rendered properly. But when I click on the settings link, there isn't any change in screen or url. In rendered DOM, I get <a ui-sref="root.settings" href="#settings">Settings</a>. The same holds for category page as well. Basically I am developing an android app using cordova and angularjs. Loads of thanks in advance.
I am creating a web app to help students in science, history and math. When you first land on the site I have a home/landing page. When you click get started I route to /exam/instructions. Each of my steps instructions, math and science our templates that I load into the ui-view="exam-detail". Currently the whole ui-view loads when I navigate to and from instructions through sciences. Ideally I simply want an area for pagination and an area for the subject matter and only want the ui-view="exam-detail" to update with the correct template.
I have not used UI-Router at all and any assistance would be greatly appreciated.
index.html
<div ui-view></div>
state-exam>exam.html
<div class="state-exam">
<nav ui-view="exam-pagination"></nav>
<section ui-view="exam-detail"></section>
</div>
route.js
(function() {
'use strict';
angular
.module('studentPortal')
.config(routeConfig);
function routeConfig($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainController',
controllerAs: 'main'
})
.state('exam', {
url: '/exam/:step',
abstract: true,
templateUrl: 'app/state-exam/exam.html',
controller: 'ExamController',
controllerAs: 'examController',
})
.state('exam.instructions', {
url: '/instructions',
views: {
'exam-pagination':{
templateUrl: 'app/state-exam/exam-pagination.html'
},
'exam-detail' : {
templateUrl: 'app/state-exam/exam-instructions.html'
}
}
})
.state('exam.math', {
url: '/math',
views: {
'exam-pagination':{
templateUrl: 'app/state-exam/exam-pagination.html'
},
'exam-detail' : {
templateUrl: 'app/state-exam/exam-math.html'
}
}
});
$urlRouterProvider.otherwise('/');
}
})();
There is a working plunker
There is a similar Q & A in fact, with working plunker:
Angular UI Router - Nested States with multiple layouts
Solution here, is to move the static view from child to parent. It won't be reloaded for each child (view is reloaded only if parent state is changed). We will use absolute naming (see included links for more details)
So this is the code adjustment
.state('exam', {
url: '/exam/:step',
abstract: true,
// the root view and the static pagination view
// will be defined here, so we need views : {}
views: {
'':{
templateUrl: 'app/state-exam/exam.html',
controller: 'ExamController',
controllerAs: 'examController',
},
// absolute naming targets the view defined above
'exam-pagination#exam':{
templateUrl: 'app/state-exam/exam-pagination.html'
},
}
})
.state('exam.instructions', {
url: '/instructions',
views: {
// 'exam-pagination':{}, // defined in parent
'exam-detail' : {
templateUrl: 'app/state-exam/exam-instructions.html'
}
}
})
.state('exam.math', {
url: '/math',
views: {
// 'exam-pagination':{}, // defined in parent
'exam-detail' : {
templateUrl: 'app/state-exam/exam-math.html'
}
}
});
Also check this to get more details about absolute view naming
Angular UI router nested views
Angular-UI Router: Nested Views Not Working
The working example is here
I have a problem trying to load a JavaScript method in nested views using ui-router. I have this code:
$stateProvider
.state('/', {
url: '/',
controller: 'MainCtrl',
views: {
'': { templateUrl:'partials/home.html'},
'navbar#': {
templateUrl: 'includes/navbar.html'
},
'sidebar#': {
templateUrl: 'includes/sidebar.html'
}
}
})
and in the index.php
<div ui-view="navbar"></div>
<div ui-view="sidebar"></div>
the content is loaded but some methods do not work. For example this:
$(function() {
$('#side-menu').metisMenu();
});
If I put the sidebar and navbar code directly in the index.php, the JavaScript method works. However, if the code is in nested view, it doesn't work.
ps: I'm using this template http://startbootstrap.com/templates/sb-admin-2/
edit: Based on the answer by #actor2019 I want to update my question to better explain the problem:
Using Angular UI-Router(v0.0.2), I've setup the app to properly navigate between main "pages"/state, while inheriting the base state.
Index.html:
<div ui-view></div>
base.html:
<!-- Header -->
<div>
<!-- Header markup -->
<!-- Search View -->
<div ui-view="search"></div>
</div>
<!-- Page Content view -->
<div ui-view></div>
The issue is here in the app.js file. When I add the views parameter to the base state, everything stops working(100% blank page). Without that parameter, the page renders correctly, but I have no search view.
app.js:
$urlRouterProvider.otherwise('/');
//
// Now set up the states
$stateProvider
.state('base', {
abstract: true,
templateUrl: 'views/base.html',
views: {
"search": {
templateUrl: "views/search.html"
}
}
})
.state('base.home', {
url: "/",
templateUrl: "views/home.html"
})
.state('base.page2', {
url: "/page2",
templateUrl: "views/page2.html"
});
How do I add views to this parent 'base' state?
UPDATE:
The problem with #actor2019's answer here is that the search view gets reinitialized when the state changes. I'd like the views off the base level to persist through state changes.
The first obvious mistake:
You can't specify controller and template on the state while your using views. They are mutually exclusive...
This is because when there is no "views" but a controller and template on the state, UI-Router automatically creates the "views" property and pulls those properties to an "empty" view...
.state('base', {
abstract: true,
templateUrl: 'views/base.html', //Can't do this
views: { // when this is there.
"search": {
templateUrl: "views/search.html"
}
}
})
Instead do:
.state('base', {
abstract: true,
views: {
"": {
templateUrl: 'views/base.html'
},
"search": {
templateUrl: "views/search.html"
}
}
})
Second problem:
How views targeting works with nested views etc. is not very logical, it may work well if you restrict your self to one view in one view all the way down, but ones you start working with multiple named views it all gets confusing... Add unnamed views on top and many people gets lost...
The way views work in UI-Router is the worst part of UI-Router...
Given you example I am not even entirely sure of the way to target the search view from your abstract parent state... Might be:
.state('base', {
abstract: true,
views: {
"": {
templateUrl: 'views/base.html'
},
"search#base": {
templateUrl: "views/search.html"
}
}
})
If it can even be made to work... Alternatively you can move the search view out of base.html, but I guess you added it in there for a reason.
The whole view concept is the biggest reason why I ended up writing https://github.com/dotJEM/angular-routing instead.
The Child state should be home.search instead of header.search. In your case, you may want to write some abstract state to hold the layout,
base.html
<div class="row-fluid">
<div class="header">
<div class="span3" ui-view="logo"></div>
<div class="span9" ui-view="menu"></div>
</div>
</div>
<div class="row-fluid">
<div class="content">
<div class="span2" ui-view="sidebar"></div>
<div class="span10" ui-view="entry"></div>
</div>
</div>
in app.js
$stateProvider
.state('base',{
abstract:true,
url:'/',
templateUrl: viewBase+'base.html'
})
.state('base.main',{
url:'',
views:{
"logo":{
templateUrl:viewBase+'main/logo.html'
},
"menu":{
templateUrl:viewBase+'main/menu.html'
},
"sidebar":{
templateUrl:viewBase+'main/sidebar.html'
},
"entry":{
templateUrl: viewBase+'main/entry.html'
}
}})
According to the ui-router documentation, when the application is in a particular state—when a state is "active"—all of its ancestor states are implicitly active as well. So, for example, when the "contacts.list" state is active, the "contacts" state is implicitly active as well, because it's the parent state to "contacts.list". Child states will load their templates into their parent's ui-view. I'd reccomend looking over the section of their documentation entitled Nested States & Views to gain a fuller understanding of how to do this.
In the code you have provided us here, the parent state of the search template is home, while
.state('header.search', {
templateUrl: "views/search.html",
controller: "SearchCtrl"
})
implies that the parent state of the search template should be header in order for the view to get loaded correctly. So, I believe the following changes to your app.js will fix your issue.
app.js
$stateProvider
.state('home', {
url: "/",
views: {
'': {
templateUrl: "views/mainContent.html",
controller: "MainCtrl"
},
'header': {
templateUrl: "views/header.html"
},
'footer': {
templateUrl: "views/footer.html"
},
}
})
.state('home.search', {
views: {
'search': {
templateUrl: "views/search.html",
controller: "SearchCtrl"
}
})
.state('anotherPage', {
url: "/anotherPage",
templateUrl: "views/anotherPage.html"
});
This works for me.
$stateProvider
.state('base', {
abstract: true,
url:'/',
templateUrl: 'views/base.html'
})
.state('base.home', {
url: "",
views: {
"search#base": {
templateUrl: "views/searchOfHome.html"
}
//content#base, contentOfHome.html
}
})
.state('base.page2', {
url: "page2",
views: {
"search#base": {
templateUrl: "views/searchOfPage2.html"
}
//content#base, contentOfPage2.html
});
If 'base' is the root state, you don't need the '#base'