I'm trying to use ui-router resolve to grab some data for use in the controller:
$stateProvider
.state('home', {
url: '/',
templateUrl: 'templates/home.html',
controller: 'homeCtrl',
resolve: {
friends: ['$http', function($http) {
return $http.get('api/friends.json').then(function(response) {
return response.data;
})
}]
}
})
Here is the controller:
angular.module('app').controller('homeCtrl', ['friends', function(friends) {
this.friends = friends;
}]);
HTML:
<section ng-controller="homeCtrl as home">
<ul>
<li ng-repeat="friend in home.friends">
{{friend.name}} : {{friend.age}}
</li>
</ul>
</section>
How can I get this to work? The error I'm getting tells me define the service. Not quite sure how to do that.
Also, I know I can get it to work by injecting $scope. I am hoping to avoid that, as I am trying to learn something new here.
You have included the controller twice. Once in the HTML, and once in the ui-router. Therefore, you're actually getting two instances of the controller.
Solution: remove the controller include from the HTML, and add this to the ui-router $stateProvider:
controllerAs: 'home'
Related
I am trying to display content using Angular, angular UI-Router, and without relying on $scope.
In my mainController, I don't have any issues using directive likes ng-repeat. But I don't know how to access information from my postsController.
I know that I am pulling the correct data in my controller since console.log shows the correct post object. Now I just need to know how to access it.
index.html
<script type="text/ng-template" id="/posts.html" >
{{ }} // What do I put here?
</script>
app.js
app.config([
'$stateProvider',
'$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
...
.state('posts', {
url: '/posts/{id}',
templateUrl: '/posts.html',
controller: 'postsController'
});
$urlRouterProvider.otherwise('home');
}
]);
app.js
app.controller('postsController', [
'$stateParams',
'posts',
function($stateParams, posts) {
var vm = this;
vm.post = posts.posts[$stateParams.id];
console.log(vm.post);
}
]);
You state will be using controllerAs so you need to define controller as below. And by using alias of your controller you could show data on view.
.state('posts', {
url: '/posts/{id}',
templateUrl: '/posts.html',
controller: 'postsController as postCtrl'
});
View
<script type="text/ng-template" id="/posts.html" >
<ul>
<li ng-repeat="p in postCtrl.post">{{p}}</li>
</ul>
</script>
You should have latest version like 0.2.0+ version of ui-router to declare controllerAs like I suggested, for older version you could use #RadimKöhler suggestion.
You should use declaration controllerAs:
.state('posts', {
url: '/posts/{id}',
templateUrl: '/posts.html',
controller: 'postsController',
controllerAs: 'vm' // this will be injected into $scope
});
and the view consume it like this:
{{vm.data | json }}
I needed a way to access the parent scope of a controller without having to inject $scope to do so. The reason behind this is that I'm building a SPA and I wanted to apply a class to the body element depending on the current page. I don't want to have to inject $scope into every single one of my controllers just for the sole purpose of changing the class. Also please let me know if I'm looking at this the wrong way, if there is a way to encapsulate this functionality using $routeprovider or a service that is fine too. I've just recently begun to use Angular so I apologize in advance if the solution is really simple, but I was not able to find anything similar to my situation by googling.
Here is the basic setup:
index.html
<body ng-controller="BodyController as body" class="contact-page">
<nav> ... </nav>
<main ng-view>
<div ng-controller="ContactController as contact>
Contact Page loaded in on click
</div>
</main>
</body>
body.controller.js
(function() {
angular
.module('app')
.controller('BodyController', BodyController);
function BodyController() {
var body = this;
}
})();
contact.controller.js
(function() {
angular
.module('app')
.controller('ContactController', ContactController);
ContactController.$inject = [];
function ContactController($scope) {
var vm = this;
}
})();
app.routes.js
(function () {
angular
.module('app')
.config(routes);
routes.$inject = ['$routeProvider', '$locationProvider'];
function routes($routeProvider, $locationProvider) {
$routeProvider
.when('/', {
templateUrl: 'app/home/home.html',
controller: 'HomeController',
controllerAs: 'home'
}).when('/contact', {
templateUrl: 'app/contact/contact.html',
controller: 'ContactController',
controllerAs: 'contact'
}).otherwise({
redirectTo: '/'
});
$locationProvider.html5Mode(true);
}
})();
My application has two controllers. I have a pageCtrlwhich I use to handle my navigation and sidebar. I also have a calendarCtrl for handling the data on my page. This controller is configured as shown below:
$stateProvider
.state('calendar', {
url: '/calendar/:category/:competition/:team',
controller: 'calendarCtrl',
templateUrl: 'app/modules/calendar/calendarView.html',
})
To make my navigation work I also need access to the :category/:competition/:team-params in my pageCtrl. Can I configure this using the same way? Something like:
$stateProvider
.state("page", {
abstract: true,
controller: 'pageCtrl',
// params: :category/:competition/:team
})
Edit: Using $stateParams in the calendarCtrl works fine. I just can't figure out how I can make sure my pageCtrl also can get read the url.
Since you're using ui.router, inject $stateParams in your controller(s) and then you can access those values like so:
controller.js
function($stateParams){
$stateParams.category
$stateParams.competition
$stateParams.team
My suggestion would be - use more views - the UI-Router built feature.
Multiple Named Views
There is a working plunker
Let's have the 'Parent' state which has this template:
This blue is the parent template. Orange are child views
<!-- HERE is one named view target -->
<div ui-view="title">This is a title filled by child having access to param</div>
...
<!-- HERE is other view target un-named -->
<div ui-view></div>
And its state is very simple. The interesting is the child state, which is taking care about both views:
.state('parent', {
abstract: true,
url: "/parent",
templateUrl: 'tpl.parent.html',
})
.state('parent.child', {
url: "/child/:id",
views : {
'': {
templateUrl: 'tpl.child.html',
},
'title': {
templateUrl: 'tpl.title.html',
controller: 'TitleCtrl',
},
}
})
So, we do have a target for "some other view" title or side bar. Check it here
And we can even place some default implementation there inside of our "non-abstract" parent state.
There is extended plunker with non abstract parent state definition:
.state('parent', {
url: "/parent",
views : {
'': {
templateUrl: 'tpl.parent.html',
},
'title#parent': {
template: 'the parent own TITLE',
},
}
})
Check it here
There is a way, how to grant access to latest/up-to-date $stateParams - including current state and its child(ren) as well. (working example here)
It is surprisingly easy:
.run(['$rootScope', '$state', '$stateParams',
function ($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}])
And that's it. (check similar answer and some discussion here)
With this approach, we will even in parent $scopes have updated reference to the latest $stateParams. While in our own, we will still receive just our own part
.controller('ParentCtrl', ['$scope', '$stateParams', function ($scope, $stateParams) {
$scope.currentStateParams = $stateParams;
}])
The above is valid for states like these:
.state('parent', {
url: "/parent?area",
templateUrl: 'tpl.html',
controller: 'ParentCtrl',
})
.state('parent.child', {
url: "/child/:id",
templateUrl: 'tpl.html',
controller: 'ChildCtrl',
})
Working example to play here.
But I still would say, that this is a bit ... against the UI-Router. I would prefer this answer. Because in that case, each view (while injected into some parent area) is really aware about $stateParams, which belongs to that state. What we are doing here is introduction of some observer pattern (we should watch changes if we want to react in parent) and that would later bring more issues then profit
I'm getting this error:
Error: Error: [$injector:unpr] http://errors.angularjs.org/1.3.7/$injector/unpr?p0=HttpResponseProvider%20%3C-%20HttpResponse%20%3C-%20DealerLeads
Injector Unknown provider
Here's my router (ui.router):
$stateProvider
.state('main', {
url: "/main",
templateUrl: "views/main.html",
data: { pageTitle: 'Main Page' }
})
.state('leads', {
url: "/leads",
templateUrl: "views/leads.html",
data: { pageTitle: 'Dealer Leads' },
controller: 'DealerLeads',
resolve: DealerLeads.resolve
})
Here's my Controller:
function DealerLeads($scope, HttpResponse) {
alert(JSON.stringify(HttpResponse));
}
Here's my resolve:
DealerLeads.resolve = {
HttpResponse: function ($http) {
...
}
}
The data is getting to the controller, I see it in the alert. However, after the controller is done, during the rendering of the view (I think), the issue seems to be happening.
The final rendered view has two controllers: One main controller in the body tag, and the second controller 'DealerLeads' inside of that. I've tried removing the main controller, and the issue is still present.
What am I doing wrong? Is there any more code that is necessary to understand/resolve the issue?
When you use route resolve argument as dependency injection in the controller bound to the route, you cannot use that controller with ng-controller directive because the service provider with the name HttpResponse does not exist. It is a dynamic dependency that is injected by the router when it instantiates the controller to be bound in its respective partial view.
Just remove the ng-controller="DealerLeads" from the view and make sure that view is part of the html rendered by the state leads # templateUrl: "views/leads.html",. Router will bind it to the the template for you resolving the dynamic dependency HttpResponse. If you want to use controllerAs you can specify that in the router itself as:-
controller: 'DealerLeads',
controllerAs: 'leads' //Not sure if this is supported by ui router yet
or
controller: 'DealerLeads as leads',
Also when you do:
.state('leads', {
url: "/leads",
templateUrl: "views/leads.html",
data: { pageTitle: 'Dealer Leads' },
controller: 'DealerLeads',
resolve: DealerLeads.resolve
})
make sure that DealerLeads is accessible at the place where the route is defined. It would be a better practice to move the route definition to its own controller file so that they are all in one place. And whenever possible especially in a partial view of a route it is better to get rid of ng-controller starting the directive and instead use route to instantiate and bind the controller for that template. It gives more re-usability in terms of the view as a whole not being tightly coupled with a controller name and instead only with its contract. So i would not worry about removing ng-controller directive where router can instantiate the controller.
I'm not completely understand you question, and also not an expert as #PSL in angular.
If you just want to pass some data into the controller, maybe below code can help you.
I copied a piece of code from the project:
.state('masthead.loadTests.test',{
url: '/loadTests/:id',
templateUrl: 'load-tests/templates/load-test-entity.tpl.html',
controller: 'loadTestEntityCtrl',
data: { pageTitle: 'loadTests',
csh: '1005'
},
resolve: {
// Get test entity data before enter to the page (need to know running state)
LoadTestEntityData: [
'$stateParams',
'$state',
'LoadTestEntity',
'LoggerService',
'$rootScope',
function ($stateParams, $state, LoadTestEntity, LoggerService, $rootScope) {
// Get general data
return LoadTestEntity.get({id: $stateParams.id},function () {
},
// Fail
function () {
// When error navigate to homepage
LoggerService.error('error during test initiation');
$state.go('masthead.loadTests.list', {TENANTID: $rootScope.session.tenantId});
}).$promise;
}
]
}
})
Here the LoadTestEntityData is the data we injected into the controller,
LoadTestEntity and LoggerService are services needed for building the data.
.factory('LoadTestEntity', ['$resource', function ($resource) {
return $resource(
'/api/xxx/:id',
{id: '#id'},
{
create: {method: 'POST'},
update: {method: 'PUT'}
}
);
}])
Hope it helps!
Plunker: http://plnkr.co/edit/0efF1Av4lhZFGamxKzaO?p=preview
Below is my header, there is an ng-show="cornerLogo" which I only want to be set true on the about, login and register views, but false the home view.
<body id="body_id"
ng-app="myApp"
ng-controller="HomeCtrl">
<header>
<section ng-show="cornerLogo">
<h1>Logo</h1>
</section>
<nav id="main_nav">
<ul>
<li><a ui-sref="about">About</a></li>
<li><a ui-sref="login">Sign In</a></li>
<li><a ui-sref="register">Create Account</a></li>
</ul>
</nav>
</header>
<ui-view></ui-view>
So it works in my HomeCtrl because that is the main controller on the page.
var app = angular.module('app-home', [])
.controller('HomeCtrl', ['$scope', function($scope) {
$scope.cornerLogo = false;
}]);
However when I switch to the about, login or register views I lose that $scope
Is there a way somehow to have a global var set somewhere in my stateProvider for ui-router? Otherwise, how would you go about this issue?
var app = angular.module('bitAge',
['ui.router',
'app-header',
'app-home',
'app-about',
'app-login',
'app-register'])
.config([
'$stateProvider',
'$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/home',
templateUrl: '_views/home.html',
controller: 'HomeCtrl',
})
.state('about', {
url: '/about',
templateUrl: '_views/about.html',
controller: 'AboutCtrl'
})
.state('login', {
url: '/login',
templateUrl: '_views/login.html',
controller: 'LoginCtrl'
})
.state('register', {
url: '/register',
templateUrl: '_views/register.html',
controller: 'RegisterCtrl'
});
// default view:
$urlRouterProvider.otherwise('/home');
}]);
Apart from my comments in the question, to fix your issue you can take this approach.
You have HomeCtrl specified as bound controller in the state registration of home partial. So instead create a main controller for your application. So that you keep the responsibilities separated out. Inject $state and expose a method called hideLogo and use $state.is to determine the logic to show/hide the logo.
i.e:
var app = angular.module('app-home')
.controller('MainCtrl', ['$scope', '$state', function($scope, $state) {
$scope.hideLogo = function(){
return $state.is('home');
}
}]);
In the index html use MainCtrl as your main controller for the app.
<body ng-app="myApp" ng-controller="MainCtrl">
<header>
<section
ng-hide="hideLogo()">
<h1>Corner Logo</h1>
</section>
Plnkr
If you want to use $state directly on the view you would need to inject it in MainCtrland set $state on the $scope object so that you can use it directly. Though i highly recommend not to use this technique, you should not expose state in the scope object and ultimately in the view. Just expose only what is needed in the viewmodel.
i.e in the MainCtrl :
var app = angular.module('app-home')
.controller('MainCtrl', ['$scope', '$state', function($scope, $state) {
$scope.$state= $state;
}]);
and just use it on the view as:
<section
ng-hide="$state.is('home')">
You can check your current state and depends on that, show or not your logo.
<section ng-show="$state.includes('home')">
<h1>Logo</h1>
</section>
Also, your anchor elements to navigate, should be like this <a ui-sref="about">About</a> and so on, because if you use normal href attribute, angular wont change state.
Also, you need to inject $state in your main module and then you can use $state module
var app = angular.module('myApp',
['ui.router',
'app-home',
'app-about']).run(function ($state,$rootScope) {
$rootScope.$state = $state;
})
UPDATE:
Here is the punklr with the answer