First, thanks for your help in advance. I'm a noob to angular though I did search quite a bit trying to resolve this issue. I'm guessing I missed something obvious. I'm using ui-router and using the resolve property to call a resource and can't get it for the life of me to pass the params to the web service.
First, my routes and states:
(function () {
"use strict";
var app = angular.module("MyModule",
["ui.router",
"ngResource",
"common.services"]);
app.config(["$stateProvider", '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider.state("home", {
url: "/",
templateUrl: "welcomeView.html"
})
$stateProvider.state("vehicleList", {
url: "/vehicles",
templateUrl: "App/Vehicles/vehicleListView.html",
controller: "VehicleListController as vm"
})
$stateProvider.state("vehicleEdit", {
url: "/vehicles/edit/:vin",
templateUrl: "App/Vehicles/vehicleEditView.html",
controller: "VehicleEditController as vm",
resolve: {
vehicleResource: "vehicleResource",
vehicle: function (vehicleResource, $stateParams) {
var vehicleVIN = $stateParams.vin;
return vehicleResource.get(
{ vin: $stateParams.vin }).$promise;
}
}
})
$stateProvider.state("vehicleDetails", {
url: "/vehicles/:vin",
templateUrl: "App/Vehicles/vehicleDetailsView.html",
controller: "VehicleDetailsController as vm",
resolve: {
vehicleResource: "vehicleResource",
vehicle: function (vehicleResource, $stateParams) {
var vehicleVIN = $stateParams.vin;
return vehicleResource.get(
{ vin: 'T123432342' }).$promise;
}
}
})
}
]);
}());
Note that you see a couple of varieties of passing the vin is as I've tried numerous ways with passing in the variable vehicleVIN, a string, etc. The vehicleVIN variable does assign properly there so injecting $stateParams or passing the vin to it doesn't seem to be the problem.
Here is the resource:
(function () {
"use strict";
angular
.module("common.services")
.factory("vehicleResource",
["$resource",
"appSettings",
vehicleResource]);
function vehicleResource($resource, appSettings) {
return $resource(appSettings.serverPath + "/api/Vehicle/:vin", { vin: '#vin' },
{
'update': { method: 'PUT' },
'details': { method: 'GET' },
'query': { method: 'GET', isArray = true }
});
}
}());
Here is common.services:
(function () {
"use strict";
angular
.module("common.services", ["ngResource"])
.constant("appSettings",
{
serverPath: "http://localhost:8098"
});
}());
And here is the service end of it (Asp.Net WebAPI):
// GET api/Vehicle
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All, AllowedOrderByProperties = "Name")]
[ResponseType(typeof(IQueryable<IEnumerable<IVehicle>>))]
public IHttpActionResult Get()
{
return ListOfVehicles...blah..blah
}
// GET api/Vehicle/5
[ResponseType(typeof(IQueryable<IVehicle>))]
public IHttpActionResult Get(string vin)
{
return SingleVehicle...blah...blah
}
Every time I execute it, the breakpoint hits Get (without the vin parameter) and when I run fiddler, it looks like it's sending an empty body.
Any ideas on what is going on? Much appreciated!
Related
I have an angular app that needs to do a quick http request to get some config information before the rest of the application is initiated, at least before the controllers. Looked into using $UrlRouterProvider, but I did not figure out how to make the app wait for the http be done.
What I need to be finished:
$http({method: 'GET', url: '/config'}).then(function(res) {
configProvider.setConfig(res.data.config);
}
You can create a separate js file where you can make http request and then initialize/bootstrap your app via js code instead of ng-app in html code.
Refer the below code:
(function() {
var application, bootstrapApplication, fetchData;
application = angular.module('app');
fetchData = function() {
var $http, initInjector;
initInjector = angular.injector(['ng']);
$http = initInjector.get('$http');
$http.get('<url>');
};
bootstrapApplication = function() {
angular.element(document).ready(function() {
angular.bootstrap(document, ['app']);
});
};
fetchData().then(bootstrapApplication);
})();
I hope it helps.
Resolve must be declared on state, not on the view
change
.state('app', {
abstract: true,
url:'/',
views: {
"content": {
templateUrl: "myurl.html"
},
resolve {
myVar: ['$http', 'myService', function($http, myService) {
return $http({method: 'GET', url:'url'})
.then(function(res) { //do stuff })
to
.state('app', {
abstract: true,
url:'/',
views: {
"content": {
templateUrl: "myurl.html"
}
},
resolve {
myVar: ['$http', 'myService', function($http, myService) {
return $http({method: 'GET', url:'url'})
.then(function(res) { //do stuff })...
When i bundle the js files then resolve statement is not working in angular project. Below is the code
app.js
app.config(["$stateProvider",
"$urlRouterProvider",
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider
.state("home", {
// welcome
url: "/",
templateUrl: "app/views/welcomeView.html"
})
.state("productList", {
// Product List
url: "/products",
templateUrl: "app/views/productListView.html",
controller: "ProductListCtrl as vm"
})
.state("productDetail", {
// Product Details
url: "/products/:productId",
templateUrl: "app/views/productDetailView.html",
controller: "ProductDetailCtrl as vm",
resolve: {
productResource: "productResource",
product: function (productResource, $stateParams) {
var productId = $stateParams.productId;
return productResource.get({ id: productId }, function (data) {
return data;
});
}
}
});
}]
);
productDetailCtrl.js
(function () {
"use strict";
angular
.module("productManagement")
.controller("ProductDetailCtrl",["product",ProductDetailCtrl]
);
function ProductDetailCtrl(product) {
var vm = this;
vm.product = product;
vm.title = "Product Detail: " + vm.product.productName;
}
}());
After login i can see product list perfectly but when I try to see details of product nothing happens not error in console as well. If I comment resolve property and remove product parameter in controller then it works fine. This problem occur only after bundling and minification. If I refer bare files its working fine. Please suggest me where I made mistake.
You can use the same pattern of passing an array with the argument names as strings before the function anywhere angular uses dependency injection.
resolve: {
productResource: "productResource",
product: ["productResource", "$stateParams", function (productResource, $stateParams) {
...
}]
}
Use in-line array annotation for the resolver function:
resolve: {
productResource: "productResource",
//product: function (productResource, $stateParams) {
product: ["productResource", "$stateParams", function (productResource, $stateParams) {
var productId = $stateParams.productId;
return productResource.get({ id: productId }, function (data) {
return data;
}).$promise;
//}
}]
}
For more information, see AngularJS Developer Guide - Dependency Injection -- Inline Array Annotation
I have an app that uses angular. In the app.config is where I have to setup my routes, however I want to authorize my routes because not everyone who uses the app can see every page.
So I have already tried to make a factory that gives a boolean to tell the app if that person can see the route or not, and learned that I cannot inject a factory into a config.
So I have made a provider that I can inject into the config:
(function () {
'use strict';
angular.module('services')
.factory('Auth', ['$http', function AuthFactory($http) {
return {
LinkAuth: function (Name) {
return $http({ method: 'GET', url: '/Dashboard/AuthorizeNavItem', data: { Name: Name } });
}
}
}]);
angular.module('services')
.provider('AuthProvider', ['Auth', function (Auth) {
var allowed = false;
this.$get = function (Name) {
Auth.LinkAuth(Name).success(function (data) {
allowed = data.Authorized;
});
return allowed;
}
}]);
})();
My app.config:
(function () {
'use strict';
angular.module('app.it', [])
.config(['$stateProvider', 'msNavigationServiceProvider', 'AuthProvider', function ($stateProvider, msNavigationServiceProvider, AuthProvider) {
$stateProvider
.state('app.it', {
abstract: true,
url: '/information-technology',
})
.state('app.it.users', {
url: '/users',
views: {
'content#app': {
templateUrl: '/IT/Users',
controller: 'ITUserController as vm'
}
}
});
/*
We need to get a bool to say if a user is part of a group that can see this page.
*/
var allowed = true;//I want the provider $get method to return that bool here
if (allowed)
{
//This builds the navigation bar
msNavigationServiceProvider.saveItem('app.authroute', {
title: 'Authorized Route',
icon: 'icon-monitor',
weight: 2
});
//This builds the navigation bar
msNavigationServiceProvider.saveItem('app.authroute.route', {
title: 'Route',
state: 'app.authroute.route'
});
}
}]);
})();
How do I access that AuthProvider $get and store the bool in a variable in the config?
I have some routes defined like this :
$stateProvider
.state('app', {
url: '/',
abstract: true,
views: {
'menuContent': {
templateUrl: 'templates/home.html'
}
}
})
.state('app.restricted', {
url: '/restricted',
views: {
'content': {
templateUrl: 'templates/restricted/restricted-dashboard.html',
controller: 'RestrictedController as vmRestricted'
}
},
resolve: {
isGranted: 'isGranted'
}
})
.state('app.restricted.pending', {
url: '/pending',
views: {
'tabsView': {
templateUrl: 'templates/restricted/restricted-manage-pending.html',
controller: 'RestrictedPendingController as vm'
}
},
resolve: {
isGranted: 'isGranted'
}
})
.state('app.restricted.devices', {
url: '/devices',
views: {
'tabsView': {
templateUrl: 'templates/trusted/restricted-manage-devices.html',
controller: 'RestrictedDevicesController as vm'
}
},
resolve: {
isGranted: 'isGranted'
}
})
.state('app.grant', {
url: '/grant-access',
views: {
'content': {
templateUrl: 'templates/grant-access.html',
controller: 'GrantAccessController as vm'
}
}
})
;
In these routes I have a restricted area and a grant access page to grant access to the restricted area.
When the isGranted resolve provider is rejected I redirect to the app.grant route.
This is the code doing this :
$rootScope.$on(AngularEvents.STATE_CHANGE_ERROR, _onStateChangeError);
function _onStateChangeError(event, toState, toParams, fromState, fromParams, error){
switch (error) {
case 'accessRejected':
$state.go('app.grant');
break;
}
}
Here is the code of my isGranted provider :
(function() {
'use strict';
angular.module('app')
.provider('isGranted', isGrantedProvider);
isGrantedProvider.$inject = [];
function isGrantedProvider() {
this.$get = isGranted;
isGranted.$inject = ['$q', '$log', 'grantService'];
function isGranted($q, $log, grantService){
$log.log('isGrantedProvider');
if (grantService.isGranted()) {
return $q.when(true);
} else {
return $q.reject('accessRejected');
}
}
}
})();
(grantService.isGranted() just returns a boolean value)
The first time I go to the app.restricted route with $state.go('app.restricted') the provider is executed.
The route is rejected because the access is not granted and we are redirected to the app.grant route.
In this page, the user can log in and have access to the restricted area. Once the user is logged in we redirect him to the app.restricted.pending route but the resolve is not called and the route is rejected and we are redirected to the app.grant route again, whereas the access was granted.
Why is the resolve not called?
Is there a way to force it?
EDIT
I have new information after some testing.
I saw that the resolve is not called the second time only when it is a service:
This resolve is always executed when we enter the state:
state('app.restricted', {
url: '/restricted',
views: {
'content': {
templateUrl: 'templates/restricted/restricted-dashboard.html',
controller: 'RestrictedController as vmRestricted'
}
},
resolve: {
isGranted: ['$log', function($log) {
$log.log('RESOLVE');
}]
}
})
But this resolve is only executed once even when I enter again to the state:
state('app.restricted', {
url: '/restricted',
views: {
'content': {
templateUrl: 'templates/restricted/restricted-dashboard.html',
controller: 'RestrictedController as vmRestricted'
}
},
resolve: {
isGranted: 'isGranted'
}
})
angular.module('app')
.provider('isGranted', isGrantedP);
isGrantedP.$inject = [];
function isGrantedP() {
this.$get = isGranted;
isGranted.$inject = ['$q', '$log'];
function isGranted($q, $log){
$log.log('RESOLVE');
}
}
Why isn't this service called each time? Is it because a service is a singleton? How should I proceed?
After a lot of investigations and testing I found the solution!
First, let's see why it is not working
As mentioned in the docs (http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$stateProvider), if the resolve is a string, then it corresponds to a service
factory - {string|function}: If string then it is alias for service.
Otherwise if function, it is injected and return value it treated as
dependency. If result is a promise, it is resolved before its value is
injected into controller.
And as mentioned in the angularjs docs (https://docs.angularjs.org/guide/providers), all services are singletons, meaning that it will be instantiated only once
Note: All services in Angular are singletons. That means that the
injector uses each recipe at most once to create the object. The
injector then caches the reference for all future needs.
Why is it important?
Because resolves do not call a function inside our service. They just use the return value of the instantiated service. BUT because our service will be instantiated only once, the return value will always be the same! (because our service initialization is only called once)
What can we do?
From my tests I could see that a resolve defined like this:
resolve: {
myResolve: ['$log', function($log) {
$log.log('My Resolve!');
}]
}
is always executed, so we can write them this way to make it work correctly.
But how can I do if I want to use my service?
The best working solution I found to be able to use my service and have a syntax that looks similar to this one: myResolve: 'myResolveService' is to declare my resolve like this:
resolve: {
myResolve: ['myResolveService', function(MyResolveService) {
myResolveService.log();
}]
}
And my service like this:
angular.module('app')
.factory('myResolve', myResolve);
myResolve.$inject = ['$log'];
function myResolve($log) {
function service(){
this.log = log;
function log() {
$log.log('My resolve!');
}
}
return new service();
}
This code can also be adapted for resolves that return a promise:
Resolve:
resolve: {
myResolve: ['myResolveService', function(MyResolveService) {
return myResolveService.check();
}]
}
Service:
angular.module('app')
.factory('myResolve', myResolve);
myResolve.$inject = ['$q', 'myService'];
function myResolve($q, myService) {
function service(){
this.check = check;
function check() {
var defer = $q.defer();
if (myService.check()) {
defer.resolve(true);
} else {
defer.reject('rejected');
}
return defer.promise;
}
}
return new service();
}
I am working on an application and I'd like to use the controllerAs syntax to not rely only on $scope. I am using $resource to get data from the API and the problem I encounter is that in the success/error callbacks I can use only $scope, since this is not defined.
Here is some code to better explain the problem.
This is my main module where among other things I configure the router:
angular
.module('app', ['ngRoute', 'ngResource', 'LocalStorageModule', 'app.users', 'app.auth'])
.config(configure)
.controller('MainController', ['$scope', '$location', MainController]);
function configure($routeProvider, localStorageServiceProvider, $resourceProvider) {
// configure the router
$routeProvider
.when('/', {
templateUrl: 'app/homepage.html',
controller: 'MainController',
controllerAs: 'vm',
data: { authRequired: true }
})
.when('/users', {
templateUrl: 'app/users/main.html',
controller: 'UserController',
controllerAs: 'vmu',
data: { authRequired: true }
})
.otherwise({redirectTo: '/'});
}
// the MainController is not relevant here
In the user module I get some info about the users from the API. Here is a simplified example:
angular
.module('app.users', ['ngResource'])
.controller('UserController', ['UserService', UserController])
.factory('UserService', ['$resource', UserService]);
function UserController(UserService) {
this.users = UserService.users.list();
this.getUserInfo = function(userId) {
this.user = UserService.users.single({ id: userId },
function(success) {
// here I'd like to use 'this' but the following line will trigger an error
this.groupRules = UserService.users.rules({ id: success.userGroupId });
// I have to use $scope instead but it is not what I want
// $scope.groupRules = UserService.users.rules({ id: success.userGroupId });
} );
}
}
function UserService($resource) {
var userResource = {};
userResource.users = $resource('https://my.api.com/users/:action',
{},
{
list: { method: 'GET', isArray: true, params: { action: 'list' } }
single: { method: 'GET', params: { action: 'single', id: '#id' } }
rules: { method: 'GET', params: { action: 'getRules', id: '#id' } }
});
return userResource;
}
I'd like to be able to use 'this' in the callback of the $resource, but of course I'll get an error since 'this' is 'undefined' inside the callback.
Using $scope solves the problem, but I need to refactor some code and I'd like to avoid using $scope all the time.
Any workaround? Maybe I should use a different approach?
Thanks in advance for your help and explanations!
You should look into how to use this in javascript and into javascript scopes and closures.
This should work better:
function UserController(UserService) {
var _this = this;
this.users = UserService.users.list();
this.getUserInfo = function(userId) {
_this.user = UserService.users.single({ id: userId },
function(success) {
// here I'd like to use 'this' but the following line will trigger an error
_this.groupRules = UserService.users.rules({ id: success.userGroupId });
} );
}
}