I have this code:
var home = {
name: 'home',
template: '<div data-ui-view></div>',
url: '/',
templateUrl: 'app/access/partials/home.html',
controller: ['accessService', function (accessService: IAccessService) {
this.ac = accessService;
}],
controllerAs: 'home'
};
var homeAccess = {
name: 'home.access',
url: 'Access',
templateUrl: 'app/access/partials/webapi.html',
controller: ['accessService', function (accessService: IAccessService) {
this.ac = accessService;
}],
controllerAs: 'homeAccess',
resolve: {
abc: ['accessService', function (accessService) {
return accessService.getAbc();
}],
def: ['accessService', function (accessService) {
return accessService.getDef();
}]
}
};
Now that I am using controllerAs is there a way that I can simplify this code so as to eliminate adding the accessService into both of the controllers and into the two parts of the resolve? Also if I did this then how could I get to the access service inside the home.html and also the webapi.html?
There are probably a few different ways, just thinking out loud here.
Your home state is the parent of home.access and it uses a ui-view to show the child state. As such the template for the child state can reference the controller in the parent state. This is just the regular inheritance of $scope in Angular views, although it's much cleaner b/c you are using the controllerAs syntax.
For example, your views might end up looking like this:
<home-template>
<p>{{home.someValue}}</p>
<!-- included by the ui-view -->
<home-access-template>
<p>{{homeAccess.anotherValue}}
<!-- this works b/c home is on the parent scope -->
<p>{{home.someOtherValue}}</p>
</home-access-template>
<home-template>
So if it makes sense in your scenario, you only need to inject your accessService into the parent controller. The child views will use the service through methods of the parent controller.
A similar thing can also be done with the resolves: by declaring them on the parent state, they are available to the child states. This is more useful when there are many child states for a given parent.
Related
I am trying to manage CRUD for lists and list elements.
I want to have one state, one template and one controller per element for each case(create/edit).
I create the state:
state('list', {
parent: 'root',
url: '/list?list_id',
views: {
'content#root': {
templateUrl: 'list.tmpl.html',
controller: 'ListController',
controllerAs: 'vm',
}
}
})
Which is perfect, in the controller i can check for a list_id and toggle betweend create/edit.
The problem occurs when the state above becomes a parent.
When the child is introduced:
state('list-element', {
parent: 'list',
url: '/element?list_id&element_id',
views: {
'content#root': {
templateUrl: 'element.tmpl.html',
controller: 'ElementController',
controllerAs: 'vm',
}
}
})
I can no longer have the uncertainty i need.
To put it more simple, i want the url structure to look like this:
/list?list_id - if list_id toggle edit
/list/element?list_id&element_id - if element_id toggle edit
Note that when the list element is created, the parent state does not have a parameter.
Now, i can work around this by creating two states for the list:
/list (parent) create
?list_id (child for 1) edit
/element?list_id&element_id (child for 1) create or edit
but this would break the "one state, one template and one controller" that i want.
Anyway that i can achieve to do it the way i want?
I know you're looking for a specific implementation, but I think part of the reason you're hitting trouble is because that approach may not be the best for what you're trying to do, since it seems like you're confusing the role of a route versus a DOM element.
Components (and directives) are designed to help you bundle together controllers and templates explicitly for elements on the page. Routing is just what gets you there, and tells you which ones to load. Here's how you might handle this using a more conventional approach:
stateHelperProvider.state({
name: 'list',
url: '/list'
// This is possible with https://github.com/marklagendijk/ui-router.stateHelper
children: {
name: 'element',
// Example: /list/123
url: '/{list_id:int}?element_id',
resolve: {
item: ['$stateParams', 'FooService', ($stateParams, FooService) => {
// Get whatever info you need, based on the ID.
return FooService.getItem($stateParams.list_id);
}
},
views: {
'content#root': {
// This will use the resolved `item` and pass it into the component.
template: '<list-element item="$resolve.item"></list-element>'
}
}
}
});
// The component
(function () {
'use strict';
angular.module('yourModule')
.component('listElement', {
templateUrl: 'element.tmpl.html',
bindings: {
item: '<'
}
});
})();
// Sample component template
<h2 ng-bind="$ctrl.item.name"></h2>
<div ng-bind="$ctrl.item.description"></div>
For a different route, you may want to check out nested named views in UI Router. This will allow you to specify a different controller, template, and state each. Gist:
<!-- Parent template -->
<div ui-view="editContents"></div>
<div ui-view="previewContents"></div>
Edit: Here is the complete code at Plunker. Though I can not c anything in execution but same code working at local. However gives a console error though
It all works perfect. But due to :id in /news/:id/, i am getting jquery/angular errors in console which can not be tracked anywhere in my code
I can not c What i am doing wrong.
Edit: Solved plunker https://plnkr.co/edit/FWcuBgGpVdMj3CroFrYJ
First of all you are trying to use ui-router but you're including ngRoute script in your plunker. Change it to
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.3.1/angular-ui-router.min.js"></script>
Then everything should work fine!
I suggest you a few changes...
1. Use ui-sref instead of href because it's much easier to define
ui-sref="post({id:1})" which turns into href="#/news/1"
If you would like to change url some day, then you will have to just change your route file, not each href.
$stateProvider
.state('post', {
url: "news/:id"
or
$stateProvider
.state('post', {
url: "archive/:id"
or
$stateProvider
.state('post', {
url: "whatever/:id"
2. Use abstract state
In your example it's a way better to define abstract state which holds header, content and footer - it's a typical use case.
ui-router
Abstract States
An abstract state can have child states but can not get activated
itself. An 'abstract' state is simply a state that can't be
transitioned to. It is activated implicitly when one of its
descendants are activated.
Some examples of how you might use an abstract state are:
To prepend a url to all child state urls. To insert a template with
its own ui-view(s) that its child states will populate. Optionally
assign a controller to the template. The controller must pair to a
template. Additionally, inherit $scope objects down to children, just
understand that this happens via the view hierarchy, not the state
hierarchy. To provide resolved dependencies via resolve for use by
child states. To provide inherited custom data via data for use by
child states or an event listener. To run an onEnter or onExit
function that may modify the application in someway. Any combination
of the above. Remember: Abstract states still need their own
for their children to plug into. So if you are using an
abstract state just to prepend a url, set resolves/data, or run an
onEnter/Exit function, then you'll additionally need to set template:
"".
Here's a plunker which shows how I would do it.
https://plnkr.co/edit/5FvJaelyxdl5MuALt5VY?p=preview
Hope it helps.
Look at the documentation for ui router named views,
You can use following syntax for using multiple views
$stateProvider
.state('state',{
url: '',
views: {
'header': {
templateUrl: 'views/header.html',
controller: 'headerCtrl'
},
'content': {
template: '<div ui-view=" "></div>', //<-- child templates loaded to here
},
'footer': {
templateUrl: 'views/footer.html',
controller: 'footerCtrl'
}
}
})
.state('state.post', {
url: 'news/:id/:KeyWords'
templateUrl: 'views/post.html' //<-- This goes into content's ui-view
});
I'm guessing you want to keep the header and footer and change content views.
You can achieve this by making this state as parent to all other states
suppose
.state('main',{
abstract: true,
views: {
'header': ... ,
'content': {
template: '<ui-view></ui-view>',
}
'footer': ...
}
})
then all the child views will load their views in the ,
ex: in main.child etc, your template will load in the content's <ui-view></ui-view> tag
If you need to use a custom template depending on keywords you can do the following:
.config(['$routeProvider',
function($routeProvider, $routeParams) {
$routeProvider
.when('/news/:id/:keyWords', {
template: '<div ng-include="url"></div>',
controller: "exampleController"
})
then in the exampleController
function($routeParams, $scope) {
$scope.url = $routeParams.keyWords;
}
I want to make tabs with tab-content.
tab-content has it's own view.
Here is code sample
(function () {
angular
.module('infirma.konfiguracja', ['ui.router'])
.config(routeConfig)
;
routeConfig.$inject = ['$stateProvider'];
function routeConfig($stateProvider) {
$stateProvider
.state('app.konfiguracja', {
url: 'konfiguracja/',
views: {
'page#app': {
templateUrl: 'app/konfiguracja/lista.html',
controller: 'konfiguracjaListaCtrl',
controllerAs: 'vm'
}
},
ncyBreadcrumb: {label: "Ustawienia systemu"}
})
.state('app.konfiguracja.dzial', {
url: '{dzial:.*}/',
views: {
'dzial#app.konfiguracja': {
templateUrl: 'app/konfiguracja/dzial.html',
controller: 'konfiguracjaDzialCtrl',
controllerAs: 'vm'
}
},
ncyBreadcrumb: {label: "{{vm.nazwaDzialu}}"}
})
;
}
})();
I want to mark selected tab which is in parent state (app.konfiguracja).
Problem is that when entering url like /konfiguracja/firmy/ there is no $stateParams.dzial in app.konfiguracja controller
How to fix it?
I created working example for your scenario here. I would say, that there at least two ways.
The first, general way, how we should use the UI-Router and its selected params in parent views (to mark selected tab/link), should be with a directive **ui-sref-active**:
ui-sref-active="cssClassToBeUsedForSelected"
So this could be the usage:
<a ui-sref="app.konfiguracja.dzial({dzial: item.id})"
ui-sref-active="selected"
>{{item.name}}</a>
The second approach (my preferred) would be to use a reference Model, created in parent $scope, and filled in a child:
.controller('konfiguracjaListaCtrl', ['$scope', function ($scope, )
{
$scope.Model = {};
}])
.controller('konfiguracjaDzialCtrl', ['$scope', function ($scope)
{
$scope.Model.dzial = $scope.$stateParams.dzial;
// we should be nice guys and clean after selves
$scope.$on("$destroy", function(){ $scope.Model.dzial = null });
}])
usage could be then like this
<span ng-if="item.id == Model.dzial">This is selected</span>
How is the second approach working? check the DOC:
Scope Inheritance by View Hierarchy Only
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states.
Check that all in action here
I have a layout set up for one of my pages that is then seeded with a ton of little views that I use to populate with date. My states currently looks like so:
.state('eventLayout', {
templateUrl: 'partials/layouts/event.html',
controller: 'EventCtrl',
})
.state('event', {
parent: 'eventLayout',
url: '/event/{eventUrl}',
views: {
'event.video': {
templateUrl: 'partials/views/event.video.html'
},
'event.info': {
templateUrl: 'partials/views/event.info.html'
},
'card.event': {
templateUrl: 'partials/views/card.event.html'
},
'card.clip': {
templateUrl: 'partials/views/card.clip.html'
},
'card.upcoming': {
templateUrl: 'partials/views/card.upcoming.html'
},
'card.banner': {
templateUrl: 'partials/views/card.banner.html'
},
'card.comment': {
templateUrl: 'partials/views/card.comment.html'
},
'card.notification': {
templateUrl: 'partials/views/card.notification.html'
},
'card.cube': {
templateUrl: 'partials/views/card.cube.html'
},
'card.mix': {
templateUrl: 'partials/views/card.mix.html'
},
'card.score': {
templateUrl: 'partials/views/card.score.html'
},
'card.sponsor': {
templateUrl: 'partials/views/card.sponsor.html'
},
'card.nobroadcasters': {
templateUrl: 'partials/views/card.nobroadcasters.html'
},
'card.link': {
templateUrl: 'partials/views/card.link.html'
},
'card.suggest': {
templateUrl: 'partials/views/card.suggest.html',
controller: 'SuggestblockCtrl'
},
'card.footer': {
templateUrl: 'partials/views/card.footer.html'
}
}
})
As you can see the parent layout holds my Controller for the page which is called EventCtrl . Now I would expect that all the views now have access to this controller, but that doesn't seem to be the case. Instead I have to wrap the main parent template from eventLayout in a div where I then just use the old school:
<div ng-controller="EventCtrl"></div>
I'd like to at least understand why this is happeneing and what the proper method is to make sure all views have access to the states main controller. Thanks!
EDIT:
To add more context to how im using the views in my current app I have detailed the current set-up below.
From the file partials/layouts/event.html in parent $state eventLayout
<div ng-controller="EventCtrl">
<div ui-view="event.video"></div>
<div ng-repeat="activity in activities.results">
<div ng-if="activity.card_type == 'event'" ui-view="card.event"></div>
<div ng-if="activity.card_type == 'clip'" ui-view="card.clip"></div>
<div ng-if="activity.card_type == 'upcoming'" ui-view="card.upcoming"></div>
</div>
</div>
</div>
As you can see a views are nested within the parent layout. I'm having to wrap it all with ng-controller="EventCtrl" in order to allow each view access to its scope.
The overall angular's ui-router design, is about the view / $scope inheritance - not base controller accesibility. The detailed info could be found here:
Scope Inheritance by View Hierarchy Only
small cite:
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states...
Also these are good reading, which content would be hardly any better explained here:
AngularJS Inheritance Patterns by Minko Gechev
AngularJS–Part 3, Inheritance by Gabriel Schenker
So, let's summarize a bit.
1) We know, that from any template we can access only its own $scope.
2) What is available in the view/template $scope, is a job of its Controller which can extend it with some functions, objects, etc.
3) If any parent controller (from view-nesting perspective) will inject anything into its own/parent scope - we will have access to it as well (so called prototypical inheritance)
Having this, we can create an Events object in the parent $scope, managed by EventCtrl - and consume it in any a child view template:
// the reference (reference to object Events)
// to be shared accross all child $scopes
$scope.Events = {};
// objects
$scope.Events.MyModel = { FirstName: "....
// functions
$scope.Events.save = function () {....
And now in any child template we can use it like this
<div>{{Events.MyModel.FirstName}}</div>
Another technique would be to place the controller into $scope's Events object:
$scope.Events = this; // controller
And then have full access to controller's methods, properties...
I am a n00b to Angular, and trying to get my head around how ui-routing works. I see the how the concept of multiple routes works, and how nested routes work. How would i do both? Or is this not good practice?
In the example bellow I have app/wh working fine, however when some goes to app/wh/toc/123 I want only the middle view to swap.
.state('app.wh', {
url: "/wh",
views: {
'left' : {
templateUrl: "static/partials/leftPane.html"
},
'middle': {
templateUrl: "static/partials/start.html"
},
'right' : {
templateUrl: "static/partials/rightPane.html",
controller: 'AsideCtrl'
}
}
})
.state('app.wh.toc', {
url: "/toc/:id",
views: {
'middle' : {
templateUrl: "static/partials/toc.html",
controller: function($scope, $stateParams, $state){
$scope.title = $stateParams.id;
}
}
}
})
I am guessing that you want your left and right panels to stay static while the middle content changes through the users actions. Personally I am not a big fan of using multiple routes, and instead simply use ng-include if I want to separate portions of a single view into their own files.
If you do this, then you only need to define your parent, and child state:
.state('app.wh', url: '/wh', abstract: true, controller: ...
Note that if you want separate controllers for left and right, you can use ng-controller in your views. Also If this state is common to multiple middle contents, then you might want to make it an abstract state, so that the user cannot actually load it (it only acts as a parent placeholder).
.state('app.wh.toc', url: /toc/:id ...
Then in your view:
<div ng-include="someUrlRefLeft"></div>
<div ui-view=""></div>
<div ng-include="someUrlRefRight"></div>