Controller from Parent Layout is not access by child views - angularjs

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

Related

ui-router parent state optional parameter

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>

Using ui-router and controllerAs can I simplify the adding of services?

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.

Issue with UI router and variables no longer being in the $scope after transitioning from one to another state

I have three states: one abstract state and two concrete states inheriting from the abstract one. I am transitioning from one state to another and I noticed that the variables that were in the $scope in one state are no longer in $scope after I have transitioned to the other state: see $scope.signupForm.member.email below.
Can someone please advise?
My UI router configuration:
$stateProvider
.state('signup', {
abstract: true,
views: {
'#': {
templateUrl: 'signup/views/signup.html'
}
}
})
.state('signup.form', {
url: '/signup',
views: {
'#signup': {
controller: 'SignupCtrl',
templateUrl: 'signup/views/signup.form.html'
}
}
})
.state('signup.success', {
url: '/signup/success',
views: {
'#signup': {
controller: 'SignupCtrl',
templateUrl: 'signup/views/signup.success.html'
}
}
})
Relevant snippet from my controller:
signupService.signup($scope.signupForm)
.success(function () {
//TODO: issue with controller no longer being in scope: signupForm.member.email is not displayed in template
$state.go('signup.success');
});
My email input (from signup.form.html):
<input type="email" name="email"
placeholder="{{'SIGNUP_FORM_EMAIL' | translate}}"
ng-model="signupForm.member.email" ng-required="true"
ng-pattern="EMAIL_PATTERN"
class="form-control"/>
Where I try to display the email (from signup.success.html):
<div class="panel-body">
success!
check your email at: {{signupForm.member.email}}
</div>
edit 1:
If I pull up the controller one level - by putting it into the abstract state i.e. 'signup', then signupFormCtrl - the angular form controller - is undefined!
<form name="signupFormCtrl" ng-submit="signup()" novalidate>
edit 2:
This is what I tried:
.state('signup', {
abstract: true,
views: {
'#': {
controller: 'SignupCtrl',
templateUrl: 'signup/views/signup.html'
}
}
})
.state('signup.form', {
url: '/signup',
views: {
'#signup': {
templateUrl: 'signup/views/signup.form.html'
}
}
})
.state('signup.success', {
url: '/signup/success',
views: {
'#signup': {
templateUrl: 'signup/views/signup.success.html'
}
}
})
Thre is a working plunker
This is feasable with UI-Router built-in features. We will need to introduce controller for our base state:
.state('signup', {
abstract: true,
templateUrl: 'signup/views/signup.html',
controller: 'SignupBaseCtrl',
})
Inside of this controller we would define a Model inside of a $scope:
.controller('SignupBaseCtrl', ['$scope', function ($scope) {
$scope.signupForm = { member : { email : null }};
}])
And now, if we would work with a model like this:
{{signupForm.member.email}}
In any of our child states, we would be accesing the same model, the same reference object singupForm.
And how it is possible? how it is working? All is clearly explained here:
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.
You can also check: Controller from Parent Layout is not access by child views
Data from one scope can not be accessed from a different scope. try using the rootScope for data that is to be used across scopes use $root in templates as in {{$root.signupForm.member.email}} and $rootScope in controllers as in $rootScope.signupForm.member.email

How to get $stateParams in parent view?

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

Update single view where there are many in AngularJs and UI-Router

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>

Resources