Angular - Multiple controllers using ControllerAs in route - angularjs

I have an issue which I will go on to explain. When using ControllerAS at the top of a html page such like this:
<div ng-controller="MyController as myControllerVM">
I can refer to things in my controller using the myControllerVM name.
Ie in my controller I set up a variable this and assign other variables to it like this:
var vm = this;
vm.number = 1;
I can then refer to this variable in the html like this:
myControllerVM.number
as long as at the top of my page I have
<div ng-controller="MyController as myControllerVM">
What I want to do is accomplish the same thing only through routing. I have tried this:
.when(myROUTE, {
templateUrl: 'template.html',
controller: 'MyController',
controllerAs: 'myControllerVM'
and assigned everything in the controller to the variable vm which points to this as before. But in my html page these values are not getting pulled through as they were when the "controller as" name as at the top of the html page, i.e. I can no longer refer to the values in the html as myControllerVM.name (the value isn't being pulled through). The reason I need it in the route is because I want to use different controllers for the one html page.
Why isn't using controllerAs in the route working.

I can't see all your code but you can find working example here
http://plnkr.co/edit/aedTeP2DbicGYwdGWKvU?p=preview
ie:
myApp.config(function($stateProvider, $urlRouterProvider) {
//
// For any unmatched url, redirect to /state1
$urlRouterProvider.otherwise("/state1");
//
// Now set up the states
$stateProvider
.state('state1', {
url: "/state1",
templateUrl: "state1.html",
controller: 'state1Ctrl',
controllerAs:'vm'
})
.state('state2', {
url: "/state2/",
templateUrl: "state2.html",
controller: 'state2Ctrl',
controllerAs:'vm'
});
});
myApp.controller('state2Ctrl', function(){
var vm = this;
vm.title= "That is my second"
});
myApp.controller('state1Ctrl', function($scope){
var vm = this;
vm.title= "fist controller";
});

You're doing everything correct with
.when(myROUTE, {
templateUrl: 'template.html',
controller: 'MyController',
controllerAs: 'myControllerVM'
I'm not sure if Angular documentation is clear enough on that, but the scope of router's current controller is ngView's scope. So you can use MyController scope variables (including the ones that use myControllerVM identifier) only in template or ngView element attributes. I guess it is the source of the problem.

Related

Transition from ng-view to ui-view

I have an older app (Angular 1.5.3) and I would like to take advantage of angular-ui-router and components for a few of my pages.
Is it possible to use the old routeProvider in conjunction with the stateProvider that ui-router provides?
IE I would like to transition a few pages to use components here and there as I get time, while leaving the rest.
I am pretty sure using ng-view and ui-view I am having a hard time getting that to work (probably cause you are not supposed to do that). Does that mean I have to transition my entire project from routeProvider to stateProvider all at once?
Is it possible to use the old routeProvider in conjunction with the
stateProvider that ui-router provides?
Short answer
No. Similar structure but different syntax
Long answer
No, but ... You can easily convert ng-view to ui-view a.e. from $routeProvider to $stateProvider.
Consider example ng-view:
$routeProvider
.when('/Book/Add', {
template: '<div class="box" ng-class="classname">Add</div>',
controller: function($scope) {$scope.classname="add"}
})
.when('/Book/Error', {
templateUrl : 'error.html',
controller: 'ErrorController'
})
.otherwise({redirectTo: '/Book/Error'});
Consider example ui-view:
$stateProvider
.state('book', {
url: '/Book',
abstract: true,
templateUrl: 'views/Book.html'
})
.state('book.add', {
url: '/inbox',
template: '<div class="box" ng-class="classname">Add</div>',
controller: function($scope) {$scope.classname="add"}
})
.state('book.error', {
url: '/Error',
templateUrl : 'error.html',
controller: 'ErrorController'
})
$urlRouterProvider.otherwise(function ($injector, $location) {
return '/Book/Error';
});
Keep in mind that routing syntax will change too.
For example:
if ($state.current.name === 'login') {
$state.go('book.add', {});
}
Instead Add we will write <a ui-sref="book.add">Add</a>
And so on ......
As you can see, the syntax is a bit similar. I'm sure you will find a lot of references about power of $stateProvider. For example https://stackoverflow.com/a/21024270/1631379
Hope I answered on your question

Angular + UI Router breaks when using dot notation in state name

I had my old code which worked just fine:
$urlRouterProvider.otherwise('/next');
$stateProvider
.state('next', {
url: '/next',
templateUrl: 'partials/next.html',
controller: function($scope, $state){
}
});
But then I got the brilliant idea to better organize my code, so I ended up with:
$urlRouterProvider.otherwise('/next');
$stateProvider
.state('app', {
abstract: true
})
.state('app.next', {
url: '/next',
templateUrl: 'partials/next.html',
controller: function($scope, $state){
}
});
Which is basically the same thing, but uses a dot notation, and an abstract state (not that it matters; even if I remove the abstract state, it still won't work).
The app does take me to /next, however the page is blank (only the base template is shown, not the content of /partials/next.html. The request for it is made, but it's simply not shown.
The relevant HTML code is just:
<div class="container" ui-view>
</div>
I was (somewhat) following the tutorial from https://scotch.io/tutorials/angular-routing-using-ui-router if that helps anything.
What am I doing wrong?
add in abstract state property:
template : '<div ui-view></div>'
It should looks like:
$urlRouterProvider.otherwise('/next');
$stateProvider
.state('app', {
abstract: true,
template : '<div ui-view></div>'
})
.state('app.next', {
url: '/next',
templateUrl: 'partials/next.html',
controller: function($scope, $state){
}
});
of course you can use templateUrl intead template. It is also very usefull to use layout templates in abstract state.
edit to answer comment:
Can I ask why this is necessary?
It is necessary, because angular run first abstract state, and its template. And then your actual state. But angular needs to know where put content of normal state template in parent abstract state template.
This template : '<div ui-view></div>' means that abstract state has simple template with only position of normal state template.

Using 'controller as' with the ui-router isn't working as expected

I have the following state setup for a page using an abstract state and the controller as syntax:
# Details page route
.state 'title',
url: '/title',
abstract: true,
template: '<ui-view/>',
.state 'title.show',
url: '/:titleId',
templateUrl: 'title/show.html'
controller: 'Title as t'
For the purpose of this demo lets say I assign a variable to the 't' instance of the 'Title' controller and I do this inside of the Title controller function.
angular.module('title').controller 'Title',
['$state', ($state) ->
this.name = 'Test'
and inside my view 'title/show.html' I attempt to print out that variable I just created to the page:
{{t.name}}
I don't see anything. But if I remove the controller our of the ui-router and onto the element that wraps the 'title/show.html' page like this:
<div ng-controller="Title as t">
Then everything works great. Has anyone come across this problem before. I have it working fine in another app so I'm still trying to figure out what might be different in this app, maybe a difference in js library versions.
In your state configuration :
Instead of controller: 'Title as t', try :
controller: 'Title',
controllerAs: 't'
Edit : Just implemented a minimal app with ui-router and the syntax controller: Title as t also works, in versions 0.2.0 of ui-router to the most recent one as of today. I can see the t instance when I inspect angular scopes.
Your controller needs to return the value of this in order for the controllerAs feature to work properly. Since CoffeeScript implicitly returns the last line, you need to write:
return this
or if you are using the vm syntax and have written:
vm = this
you can write at the very end of the controller:
return vm
If this helps anyone my problem came about from using templated views but specifying the controllerAs outside the views element. This took forever to figure out. Credit to this thread https://github.com/driftyco/ionic/issues/3058
** WRONG **
views: {'content#': { templateUrl: 'views/listing.html' }},
controller: 'ListingCtrl',
controllerAs: 'ctrl'
** RIGHT **
views: {
'content#': { templateUrl: 'views/listing.html' },
controller: 'ListingCtrl',
controllerAs: 'ctrl'
}
Seems like these answers might be working for a lot. I came here with a different problem :
In my UI Router Javascript file, my controllers are defined like this :
state('groupHome', {
url: '/groupHome',
templateUrl: 'app/modules/group-home/groupHome.html',
controller: 'GroupHomeController',
controllerAs: 'groupHomeController'
And in my template file if I try to access the controller with the name groupHomeController it is not able to access.
But on the other hand when I changed my code to this :
state('groupHome', {
url: '/groupHome',
templateUrl: 'app/modules/group-home/groupHome.html',
controller: 'GroupHomeController as groupHomeController'
It works perfectly fine.
You will have to return this; at the end of your controller function for the controllerAs syntax to work.
angular.module('title').controller 'Title',
['$state', ($state) ->
this.name = 'Test'
return this
If you are working with $scope, you'll have to return $scope instead.
angular.module('title').controller 'Title',
['$state','$scope', ($state, $scope) ->
$scope.name = 'Test'
return $scope
Good Luck.

Angular-ui.router: Update URL without view refresh

I have an Angular SPA that presents a variety of recommendation lists, and a Google Map of locations, based on different cuts of some restaurant data (see m.amsterdamfoodie.nl). I want each of these lists to have their own URL. In order for Google to crawl the different lists I use <a> tags for the offcanvas navigation.
At present the <a> tag causes a view refresh, which is very noticeable with the map.
I can prevent this using ng-click and $event.preventDefault() (see code snippets below), but then I need to implement a means of updating the browser URL.
But in trying Angular's $state or the browser's history.pushstate, I end up triggering state changes and the view refresh...!
My question is therefore how can I update a model and the URL, but without refreshing the view? (See also Angular/UI-Router - How Can I Update The URL Without Refreshing Everything?)
I have experimented with a lot of approaches and currently have this html
Budget
In the controller:
this.action = ($event) ->
$event.preventDefault()
params = $event.target.href.match(/criteria\/(.*)\/(.*)$/)
# seems to cause a view refresh
# history.pushState({}, "page 2", "criteria/"+params[1]+"/"+params[2]);
# seems to cause a view refresh
# $state.transitionTo 'criteria', {criteria:params[1], q:params[2]}, {inherit:false}
updateModel(...)
And, what is I think is happening is that I am triggering the $stateProvider code:
angular.module 'afmnewApp'
.config ($stateProvider) ->
$stateProvider
.state 'main',
url: '/'
templateUrl: 'app/main/main.html'
controller: 'MainCtrl'
controllerAs: 'main'
.state 'criteria',
url: '/criteria/:criteria/:q'
templateUrl: 'app/main/main.html'
controller: 'MainCtrl'
controllerAs: 'main'
One possible clue is that with the code below if I load e.g. http://afmnew.herokuapp.com/criteria/cuisine/italian then the view refreshes as you navigate, whereas if I load http://afmnew.herokuapp.com/ there are no refreshes, but no URL updates instead. I don't understand why that is happening at all.
This is an example of the way to go if I understand correctly:
$state.go('my.state', {id:data.id}, {notify:false, reload:false});
//And to remove the id from the url:
$state.go('my.state', {id:undefined}, {notify:false, reload:false});
From user l-liava-l in the issue https://github.com/angular-ui/ui-router/issues/64
You can check the $state API here: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state
Based on our previous discussions, I want to give you some idea, how to use UI-Router here. I believe, I understand your challenge properly... There is a working example. If this not fully suites, please take it as some inspiration
DISCLAIMER: With a plunker, I was not able to achieve this: http://m.amsterdamfoodie.nl/, but the principle should be in that example similar
So, there is a state definition (we have only two states)
$stateProvider
.state('main', {
url: '/',
views: {
'#' : {
templateUrl: 'tpl.layout.html',
controller: 'MainCtrl',
},
'right#main' : { templateUrl: 'tpl.right.html',},
'map#main' : {
templateUrl: 'tpl.map.html',
controller: 'MapCtrl',
},
'list#main' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
'map' : {
templateUrl: 'tpl.map.html',
controller: 'MapCtrl',
},
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
}];
This would be our main tpl.layout.html
<div>
<section class="main">
<section class="map">
<div ui-view="map"></div>
</section>
<section class="list">
<div ui-view="list"></div>
</section>
</section>
<section class="right">
<div ui-view="right"></div>
</section>
</div>
As we can see, the main state does target these nested views of the main state: 'viewName#main', e.g. 'right#main'
Also the subview, main.criteria does inject into layout views.
Its url starts with a sign ^ (url : '^/criteria/:criteria/:value'), which allows to have / slash for main and not doubled slash for child
And also there are controllers, they are here a bit naive, but they should show, that on the background could be real data load (based on criteria).
The most important stuff here is, that the PARENT MainCtrl creates the $scope.Model = {}. This property will be (thanks to inheritance) shared among parent and children. That's why this all will work:
app.controller('MainCtrl', function($scope)
{
$scope.Model = {};
$scope.Model.data = ['Rest1', 'Rest2', 'Rest3', 'Rest4', 'Rest5'];
$scope.Model.randOrd = function (){ return (Math.round(Math.random())-0.5); };
})
.controller('ListCtrl', function($scope, $stateParams)
{
$scope.Model.list = []
$scope.Model.data
.sort( $scope.Model.randOrd )
.forEach(function(i) {$scope.Model.list.push(i + " - " + $stateParams.value || "root")})
$scope.Model.selected = $scope.Model.list[0];
$scope.Model.select = function(index){
$scope.Model.selected = $scope.Model.list[index];
}
})
This should get some idea how we can use the features provided for us by UI-Router:
Absolute Routes (^)
Scope Inheritance by View Hierarchy Only
View Names - Relative vs. Absolute Names
Check the above extract here, in the working example
Extend: new plunker here
If we do not want to have map view to be recreated, we can just omit that form the child state def:
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
// 'map' : {
// templateUrl: 'tpl.map.html',
// controller: 'MapCtrl',
//},
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
Now our map VIEW will be just recieving changes in the model (could be watched) but view and controller won't be rerendered
ALSO, there is another plunker http://plnkr.co/edit/y0GzHv?p=preview which uses the controllerAs
.state('main', {
url: '/',
views: {
'#' : {
templateUrl: 'tpl.layout.html',
controller: 'MainCtrl',
controllerAs: 'main', // here
},
...
},
})
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
controllerAs: 'list', // here
},
},
})
and that could be used like this:
<h4>{{main.hello()}}</h4>
<h4>{{list.hello()}}</h4>
The last plunker is here
you can use scope inheritance to update url without refreshing view
$stateProvider
.state('itemList', {
url: '/itemlist',
templateUrl: 'Scripts/app/item/ItemListTemplate.html',
controller: 'ItemListController as itemList'
//abstract: true //abstract maybe?
}).state('itemList.itemDetail', {
url: '/:itemName/:itemID',
templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
controller: 'ItemDetailController as itemDetail',
resolve: {
'CurrentItemID': ['$stateParams',function ($stateParams) {
return $stateParams['itemID'];
}]
}
})
if child view is inside parent view both controllers share same scope.
so you can place a dummy (or neccessary) ui-view inside parent view which will be populated by child view.
and insert a
$scope.loadChildData = function(itemID){..blabla..};
function in parent controller which will be called by child controller on controller load. so when a user clicks
<a ui-sref="childState({itemID: 12})">bla</a>
only child controller and child view will be refreshed. then you can call parent scope function with necessary parameters.
The short answer ended up being do not put the map inside a view that changes. The accepted answer provides a lot more detail on how to structure a page with sub-views, but the key point is not to make the map part of the view but to connect its behaviour to a view that does change and to use a Controller to update the market icons.

Angular, UI-Router: How do I get access to both a Ctrl and stateParam values within a template?

Based on these set-ups (Angular UI-Router testing scope inheritance, Angular ui-router - how to access parameters in nested, named view, passed from the parent template?), I did the following (the third holds the relevant issue):
.state("patients", {
url: "/dashboard/patients",
templateUrl: 'patients/index.html',
controller: "patientCtrl"
})
.state("sharedPatients", {
url: "/dashboard/patients/shared",
templateUrl: 'patients/shared_patients.html',
controller: "patientCtrl"
})
.state('showPatient', {
url: "/dashboard/patients/:id",
templateUrl: 'patients/show.html',
controller: ("patientCtrl", ['$scope', '$stateParams', function($scope, $stateParams) {
$scope.patient_id = $stateParams.id;
}])
})
Patients and sharedPatients work without a problem. I can also go to showPatient and access the variable patient_id. However, I cannot access any of the functions or variables established in patientCtrl. Thoughts?
The controller scope inheritance has nothing to do with the state inheritance.
Your controllers only inherit from each other if their views are nested in the DOM.
Also, the syntax you're using there is misleading. controller: ("patientCtrl", [...]) will just ignore that first part. It'll only use the controller inside the array.

Resources