ui-router: intermediate templates - angularjs

Final Edit: working plunker with the transcluded directive.
Edit: I made a first plunker with the solution given in the first answer. It works, but it's not the desired behaviour, because the template contains all the partial.
I made a second plunker with what I hope to achieve (but it doesn't work, obviously). I think it's mostly because the template is not the parent of the partial, but it is contained in it, so ui-router doesn't understand very well what I want.
Any help with this would be greatly appreciated!
We are building a website with Angular Material and ui-router, and all our content page share the same "container", because we always want the same responsive behaviour.
The code of this generic container would be something like:
<div class="layout-content">
<div layout="column" layout-align="center">
<div layout="row" layout-align="center center">
<section class="layout-fixed-width md-whiteframe-z1" flex-sm="100" flex-gt-sm="90">
{{content placed here}}
</section>
</div>
</div>
</div>
The header can differ in all pages, so the structure we have would basically be:
The question is, how can this be achieved in ui-router? We have done some nested views, but I don't see how to do a generic template so the code could be something like:
<form>
<md-toolbar/>
<div ui-view="generic-template">
<div ui-view="my-content"></div>
</div>
</form>
Ideally we would want to define only one time the generic-template view, and use it in all our modules.
In the nested states and nested views documentation I see mostly nested state stuff, but what we want is really only a plain html template, so maybe we are over-complicating this, and an easier way is possible (I'm quite sure it's the case). I've also checked this issue, where one of the answers say that ui-router should be the solution, but not much more.
Maybe we should do a directive instead?

It can be achieved combining named views and abstract states.
The 'key' here is to define an abstract state with a view for the layout (or generic template, if we follow the nomenclature of your original post).
Abstract state:
.state('master', {
abstract: true,
views: {
generic_template: {
templateUrl: 'genericTemplate.html'
}
}
})
Then, you have to set this abstract state as parent to the child views. So, the child view will inherit the generic template view. Example:
.state('one', {
url: '/one',
templateUrl: 'one.html',
parent: 'master'
})
In your index.html, you have to use a named view for the generic template, and inside it, another unnamed view. Something like this:
<body>
<div ui-view="generic_template">
<div ui-view></div>
</div>
</body>
Here is a plunker with a complete working example.
Hope it helps.

Maybe we should do a directive instead?
A directive with a transcluded ui-view certainly seems to give you what you're looking for. This saves you from cluttering up your routing logic with something that has nothing to do with routing.
genericTemplate.html:
<div>
<p>Generic content</p>
<ng-transclude></ng-transclude>
</div>
Somewhere in your js:
angular.module('formApp')
.directive('genericTemplate', function () {
return {
replace: true,
transclude: true,
templateUrl: 'genericTemplate.html'
};
});
In your html:
<body ng-app='formApp'>
<div generic-template>
<div ui-view></div>
</div>
</body>
Edit: working plunker

Related

Use ui-view as the template for state inside ui-router instead of an external template

I have an app that is currently using the angular ui-router module dependency. The only aspect of the ui-router that I'm currently employing is the ability to apply/modify $stateParams to $scope and vice versa so the URL can change the way data is displayed in the controller to a user on arrival (i.e. url?param=something will filter the data by something).
I have the following in my app.config to set the state:
$stateProvider
.state('root', {
url: '/?param',
templateUrl: 'template.html',
controller: 'listController',
params: {
param: {
value: 'something',
squash: true
}
}
});
On my homepage, template.html successfully loads when the app is instantiated as such:
<div ng-app="myApp">
<div ui-view>
</div>
</div>
However, I have reached a roadblock and realize that calling the template from within templateUrl isn't going to work, as this app is being built inside another framework and therefore needs to be called from within the homepage itself to access its full capabilities.
Being a noob at AngualrJS, I was wondering if anyone can tell me what the best way is to accomplish this while still keeping the logic of $stateParams and other ui-router capabilities intact for the future.
For instance, could I just remove the templateUrl parameter from my state and call the controller directly inside the ui-view like this:
<div ng-app="myApp">
<div ui-view>
<div ng-controller="listController">
do something
</div>
</div>
</div>
I also looked into changing the entire logic from using ui-router to simply using the $location service but I'm wondering if there is a way to accomplish this without needing to over-do everything.

How to update variables in ng-include view

I have a form and a view that shows data from that form. I want to separate form and view (which will be more that one).
Here is my code:
<div data-ng-controller="dataController" class="container">
<div data-ng-view></div>
<div ng-include="templates.simple" scope="data"></div>
</div>
And the included view shows initial data good, but does not react on any data change. How do I fix it?
data is and object with some fields.
templates.simple is a scope variable with template url
Code example: http://plnkr.co/edit/ibrsBaq8osYuEODGiM6O
The reason why binding is not working is you are reinitalizing an createDataController which is again creating data object for that ng-view template. This could be solve by removing createDataController controller from route.
Code
$routeProvider
.when('/', {
templateUrl: 'form.html',
//controller: 'createDataController'
})
Plunkr Here
Update
Other way would be if you want to load your controller twice still it doen't make any sense though. You could do this by writing ng-init on outside div, Instead of declaring that variable from controller.
<div data-ng-controller="createDataController" ng-init="data = {name: 'texy'}">
<div data-ng-view></div>
<div data-ng-include="'template.html'"></div>
</div>
Updated Plunkr

Can an angular directive contain a controller?

Or perhaps a better question is, should a directive contain a controller?
For reasons of separation, my index.html is a simple file. Everything is rendered into it via templates. So my index.html is real simple:
<body ng-app="myapp"><mainmenu></mainmenu><div ng-view></div></body>
I don't really need a directive for mainmenu, but it allows me to put the menu in a separate template file. The main menu itself contains user info, login/logout, and a search box.
<div class="leftmenu" ng-show="isLogin()">
<ul class="menu">
<li>Part1</li>
<li>Part2</li>
<li>Part3</li>
</ul>
<div ng-controller="Search" class="Search><input type="text" ui-select2="s2opts" style="width:250px;" ng-model="search" data-placeholder="search"></input></div>
</div>
<div class="rightmenu">
<ul ng-show="isLogin()" class="menu">
<li>My Account</li>
<li>Logout</li>
</ul>
<ul ng-show="!isLogin()" class="menu">
<li>Login</li>
<li>Register</li>
</ul>
</div>
So there is the menu part, with its own controller, and the search, with its own, embedded between the two parts.
Of course, my mainmenu directive unit tests fail because SearchController isn't defined. But this leaves me wondering if I am going about this wrong. Should I even have it like this, a section of html with an explicit ng-controller defined inside it? Doesn't this create all sorts of weird dependencies?
How should I better structure this? A search directive that is included so I can unit test it separately? Something feels wrong here structurally...
UPDATE:
As requested, a basic fiddle: http://jsfiddle.net/nj4n44zx/1/
As specified by the Angular documentation, the best practice is to define a controller inside a directive only to expose an API to another directive. Otherwise the link function is sufficient.
See at the bottom of :
Angular directives
By experience using controllers inside a directives shadow what you are doing in your scope. It does not help to have a easy readable code.
I do prefer using the main controller where the directive is included. With a non isolated scope you have access to everything from the link function.
I usually deal with it like that:
app.directive('topMenu', function() {
return {
restrict: 'E', // or whatever You need
templateUrl: '/partials/topmenu', //url to Your template file
controller: function($scope) {
$scope.foo = "bar";
}
};
});
Then, in that template You don't have to add ng-controller.
sure your directive can contain a controller because you declare a directive like this
myApp.directive('mainMenu', function() {
return {
restrict: 'E',
replace: true,
scope: true,
templateUrl: 'menu.html',
controller:['$scope', function($scope) {
//define your controller here
}]
};
});

using ng-view for hightly dynamic content?

I'm working on a website that allows you to search for different products, for example laptops. This is my index div:
<div class="content" id="main">
<div id="search-wrap">
<div id="logo"><h1>seach</h1></div>
<form id="search">
<input type="text" placeholder="Search " autofocus ng-model="query"/>
</form>
<div style="border: solid 1px blue" ng-show="query">
<ul ng-repeat="x in [] | range:10">
{{ query }}
</ul>
</div>
</div>
I have not yet implemented angular js on this but I'm thinking about how to do it. I'm not sure how to approach this, since its a complex site. Once a user searches for something, they will get results from a product. Will i have to create a different ng-view?
I'm just going by something i read online:
A page gets one ng-view. Assuming you have a single page application, this means you get one view. Use it wisely. Give some thought to what should be in the view. Is this your main content window or is this more of a navigation? Is the actual content (HTML) of this section highly dynamic? These are important decisions to make early in the development of your application if you have more than one distinct content area on your page.
Sorry if my question doesn't make sense, just not sure what to ask. Any tips will help.
thanks
You better try using ng-view and you would get more idea how it works.
There can be one ng-view in a page and it is tightly integrated with the url. When you change urls in browser, effective you are loading a different view into the ng-view area. These are configured using the $routeProvider.
ng-view is like the central content theme\area. Other views including sub-views for the main view and left nav, top nav footer is loaded using ng-include directive which has capability to compile and load any html chunk from server or locally.
For complex routing needs please have a look at ui-router which supports nested views.
For complex view you can try something like this
$stateProvider
.state('login', {
url: '/login',
controller: 'LoginController',
templateUrl: 'login.tpl.html',
access: 0
})
.state('multiple view', {
url: '/main',
access: 1,
views: {
'#': {
templateUrl: 'view1.tpl.html',
controller: 'view1Controller'
},
'page#dashboard': {
templateUrl: 'page.tpl.html',
controller: 'pageController'
}
}
})
More Info

ng-repeat with ng-include not working

I am trying to use an ng-repeat that includes an ng-include. The problem is that the first element in the ng-repeat is just the ng-include template with none of the data from the ng-repeat filled in. Is there a way I can somehow bind the template from the ng-include so it works on the first ng-repeat?
<div ng-repeat="item in items">
<div ng-include src="'views/template.html'"></div>
</div>
For example, if my ng-repeat contains 10 items, then the first item that is rendered will just be the empty template. Items 2-10 WILL be rendered as they should be. What am I doing wrong?
First make sure that the data that is contained in the first index of items actually has the data that you want.
One possible solution to your problem would be to simply not show the first index of the ng-repeat:
<div ng-repeat="item in items" ng-show="!$first">
<div ng-include src="'views/template.html'"></div>
</div>
This may not actually tackle the root of your problem, but it may still get your application working a bit more like what you expect.
Another possible solution:
<div ng-repeat="item in items" ng-include="'views/template.html'"></div>
see example here:
http://plnkr.co/edit/Yvd73HiFS8dXvpvpEeFu?p=preview
One more possible fix just for good measure:
Use a component:
html:
<div ng-repeat="item in items">
<my-include></my-include>
</div>
js:
angular.module("app").directive("myInclude", function() {
return {
restrict: "E",
templateUrl: "/views/template.html"
}
})
I ran into the same problem, and finally figured out that the first element has not been fetched and compiled in time for the first ng-repeat iteration. Using $templateCache will fix the problem.
You can cache your template in a script tag:
<script type="text/ng-template" id="templateId.html">
<p>This is the content of the template</p>
</script>
Or in your app's run function:
angular.module("app").run(function($http, $templateCache) {
$http.get("/views/template.html", { cache: $templateCache });
});
You can also use $templateCache inside your directive, although it's a bit harder to setup. If your templates are dynamic, I would recommend creating a template cache service. This SO question has some good examples of template caching inside a directive and a service:
Using $http and $templateCache from within a directive doesn't return results
Using a directive worked for me: https://stackoverflow.com/a/24673257/188926
In your case:
1) define a directive:
angular.module('myApp')
.directive('mytemplate', function() {
return {
templateUrl: 'views/template.html'
};
});
2) use your new directive:
<mytemplate />
... or if you're concerned about HTML validation:
<div mytemplate></div>

Resources