I am using Angular and I need to define routing in ng-route config but also in my services to be used by $http in services methods.
The problem is that the route strings change in my application according to language ... For example for an about us page I might have:
"en/about-us", "pt/quem-somos", "fr/ ..."
The application has a RouteProvider that returns route strings by key.
The idea would be to create a script on the fly for a service that injected in angular components would allow to get those routes strings.
<script type="text/javascript">
// Create $routes service using backend code ...
</script>
This would be used on app.config as follows:
app.config(['$routeProvider', function($routeProvider, $routes) {
$routeProvider.
when($routes.getByKey('about.home', {
templateUrl: 'about/home.html',
controller: 'AboutController'
})
}
But also in a service as follows:
application.service('CountryService', function ($http, $routes) {
return {
GetList: function () {
return $http.get($routes.getByKey('api.countries.list');
}
}
});
My problem is how to write the JS part of such a service and to plug it into angular components. Is it possible to create such a service?
I would not conflate view routes from the API endpoints - these are different creatures.
And you don't need to create a route per language - just use language as a parameter:
$routeProvider
.when("/:lang/about-us", {
template: "<h3>About Us</h3> in lang: {{language}}",
controller: function($scope, $routeParams, $location){
$scope.language = $routeParams.lang;
}
})
.when("/:lang/something-else", {
template: "<h3>Something Else</h3> in lang: {{language}}",
controller: function($scope, $routeParams){
$scope.language = $routeParams.lang;
}
})
plunker
Related
I'm following scotch.io's tutorial on building a RESTful API while trying to get familiar with the MEAN stack.
I've followed pretty much everything so far, and got my RESTful API sending out JSON as intended. Should I try to access it via browser address bar or try it out with Postman it works.
I'm having problems with the consumption of said JSON response.
According to the tutorial, the Angular app is divided in controllers and services. The service uses $http to call the RESTful endpoint. My doubt is where and how should I use that service to call for the data.
Is it in the controller? Is the service exposed in a way that I can add its response to $scope?
I'm new to Angular/client-side routing, so please be gentle:) My code is below.
(Blog) Controller:
angular.module('BlogCtrl', []).controller('BlogController', function($scope, $http) {
$scope.tagline = 'Blog page!';
// can and should I call the service here?
});
Service:
angular.module('BlogService', []).factory('Post', ['$http', function($http) {
return {
// call to get all posts
get : function() {
return $http.get('/api/blog');
}
}]);
Routes:
angular.module('appRoutes', []).config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
$routeProvider
// blog page that will use the BlogController
.when('/blog', {
templateUrl: 'views/blog.html',
controller: 'BlogController'
})
$locationProvider.html5Mode(true);
}]);
Angular App:
angular.module('myApp', ['ngRoute', 'appRoutes', 'MainCtrl', 'BlogCtrl', 'BlogService']);
Yes, you can make $http call in your BlogController.
However if you want to use your 'Post' factory, you should inject it to controller
angular.module('BlogCtrl', []).controller('BlogController', function($scope, Post) {...}
and make the request
Post.get().then(
function(response){console.log(response.data)},
function(errorResponse){/*...*/}
);
(I think you should also read about $resource (https://docs.angularjs.org/api/ngResource/service/$resource). Maybe it is something what you could use to replace your Post factory ;))
You want to inject the service into controller ( or anywhere else you would use it) and then make the function call using the injected service object
angular.module('BlogCtrl', [])
.controller('BlogController', function($scope, Post) {
$scope.tagline = 'Blog page!';
// Use service to get data
Post.get().then(responsePromise){
$scope.someVariable = responsePromise.data;
}).catch(function(err){
console.warn('Ooops error!')
});
});
I am really stuck with this REST API restangular config for my MEAN web application. So I have created the express server with node-restful API and can use POSTMAN to perform CRUD operations and my data is being saved to a MongoDB via Mongoose.
The client side application is a yeoman bootstrap using grunt and I can see hardcoded data in scope if I define it as per below in the controller, however when I attempt to GetList() with Restangular I am having the issue I can see posted previously with returning data from the API Restangular setbaseURL http://localhost:8080/horse.
I understand getList doesn't return data, the factory service is doing this, but I have it set like the examples online and cannot get it to return all records in the MongoDB to the angular view. Also tried ng-resource and I cannot understand why I can't add data from the API to scope.
Code below returns Horsename to "ngview" fine from HorseCtrl using <td>horse in horses</td> table
angular.module('HorseApp')
.controller('HorseCtrl', function ($scope) {
$scope.horses = [
{
horseName: 'MAYKBE DIVA'
}
];
});
below wont return data and my factory is set in app.js
'use strict';
angular.module('HorseApp', ['restangular']);
app.controller('HorseCtrl', function($scope, Restangular) {
$scope.horses = Restangular.all('horses').getList().$object;
});
app.js
'use strict';
angular.module('HorseApp', [
'ngRoute'
'restangular'
])
.config(function ($routeProvider, RestangularProvider) {
RestangularProvider.setBaseUrl('http://localhost:8081/horse');
$routeProvider
.when('/', {
templateUrl: '/views/index.html',
controller: 'HorsesCtrl'
});
})
.factory('HorseRestangular', function(Restangular) {
return Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setRestangularFields({
id: '_id'
});
});
})
.factory('Horse', function(HorseRestangular) {
return HorseRestangular.service('horse');
})
I have added the src to index.html as per below, please help :)
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-ui-select/dist/select.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
<script src="bower_components/bootstrap-sass-official/assets/javascripts/bootstrap.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="bower_components/restanuglar/dist/restangular.js"></script>
From what I can see, you are globally setting the base url for Restangular to http://localhost:8081/horse, and in your HorseCtrl you are asking Restangular to access the horses endpoint and get the list. This will effectively access the data at http://localhost:8081/horse/horses. I am not sure if this is the intended path.
Your HorseRestangular and Horse factory are not being used. If you were to change the Restangular injection of the controller to Horse, it would access http://localhost:8081/horse/horse, and have the id available through the _id property. You don't necessarily need all of the services unless you will be communicating with more than one API through restangular. To simplify it, you can do this:
angular.module('HorseApp', ['restangular'])
.config(function ($routeProvider, RestangularProvider) {
RestangularProvider.setBaseUrl('http://localhost:8081');
$routeProvider
.when('/', {
templateUrl: '/views/index.html',
controller: 'HorsesCtrl'
});
});
})
.controller('HorseCtrl', function ($scope, Restangular) {
$scope.horses = Restangular.all('horses').getList().$object;
});
I am using the Angular $routeProvider service to wire-up my single-page HTML5 applciation. I am using the following routing configuration:
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/show-order/:orderId', {
templateUrl: 'templates/order.html',
controller: 'ShowOrdersController'
});
}]);
Within the ShowOrdersController I need access to the RESTful URL parameter described above as :orderId. It is suggested that to best achieve this, I should use the $routeParams service in my controller:
app.controller('ShowOrderController', function($scope, $routeParams) {
$scope.order_id = $routeParams.orderId;
});
I have serious concerns about this. My routing logic has now bled through to my controller! If I want to drastically change the routing scheme, I would have to go through all my controller code and correct all the references to $routeParams.
Furthermore, if I want to re-use the ShowOrderController for multiple routes, it's going to enforce all of the routes to use the same token variable :orderId.
This just seems like poor coding to me. It would make more sense to provide some linking mechanism, so the router can specify well-known parameters to the controller.
This would be just like how a modal's resolve method works:
$modal.open({
controller: 'ShowOrderController',
resolve: {
orderId: function () {
return $routeParams.orderId;
}
}
});
app.controller("ShowOrderController", ["orderId", function (orderId, $scope) {
$scope.orderId = orderId;
}]);
Is there any way to achieve this or something similar with the out-of-the-box AngularJS routing services?
As per AngularJS - How to pass up to date $routeParams to resolve? it is possible to reference the current route's parameters in the resolve method of the $routeProvider using $route.current.params:
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/show-order/:orderId', {
templateUrl: 'templates/order.html',
controller: 'ShowOrdersController',
resolve: {
orderId: function( $route ) {
return $route.current.params.orderId;
}
}
});
}]);
This will then honour the suggestion above, that the controller can declaratively specify its parameters:
app.controller("ShowOrderController", ["orderId", function (orderId, $scope) {
$scope.orderId = orderId;
}]);
In conjunction, this effectively decouples the controller from the route's parameters.
I came across this tutorial.
http://justinvoelkel.me/laravel-angularjs-part-two-login-and-authentication/
The author used dependency injection to inject the login controller in app.js like this.
app.js:
var app = angular.module('blogApp',[
'ngRoute',
//Login
'LoginCtrl'
]);
app.run(function(){
});
//This will handle all of our routing
app.config(function($routeProvider, $locationProvider){
$routeProvider.when('/',{
templateUrl:'js/templates/login.html',
controller:'LoginController'
});
});
The login controller file looks like this.
LoginController.js:
var login = angular.module('LoginCtrl',[]);
login.controller('LoginController',function($scope){
$scope.loginSubmit = function(){
console.dir($scope.loginData);
}
});
I don't understand why the dependency injection is required here.
Here is my version of app.js and LoginController.js which works perfectly fine.
app.js:
var app = angular.module('ilapp', ['ngRoute']);
app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider.when('/login', {
controller: 'LoginController'
});
}]);
LoginController.js:
angular.module('ilapp').controller('LoginController', [function () {
this.loginSubmit = function () {
console.dir(this.loginData);
};
}]);
Is there any advantage to the author's approach? What am I missing?
First of all, both are correct way and it should work but you can choose any one method depends upon your project.
Approach 1
In your question, the first approach is modular way. Means, you can register a LoginController controller in a new module LoginCtrl. Here module name LoginCtrl is confusing. you can change the name as loginModule. This approach is helpful for you to organize the files structure for your big application. Also, look this post Angular - Best practice to structure modules
var login = angular.module('loginModule',[]);
login.controller('LoginController',function($scope){
$scope.loginSubmit = function(){
console.dir($scope.loginData);
}
});
var app = angular.module('blogApp',[
'ngRoute',
'loginModule'
]);
app.run(function(){
});
//This will handle all of our routing
app.config(function($routeProvider, $locationProvider){
$routeProvider.when('/',{
templateUrl:'js/templates/login.html',
controller:'LoginController'
});
});
Approach 2
If your application contains minimal pages and no need to split multiple modules, then you can write all your controllers in app.js itself
My AngularJS application has a module admin that I want to be made available only to those in an Admin role. On the server I have placed the files for this module all in one directory and I have this web-config in the same directory. This works and unless the user is in the admin role then they cannot download the javascript files:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<security>
<authorization>
<remove users="*" roles="" verbs="" />
<add accessType="Allow" roles="Admin" />
</authorization>
</security>
</system.webServer>
</configuration>
So my server side solution appears to be solved. However I am completely stuck with what to do on the client, how to download scripts and add a module to my application after it has been bootstrapped. Here's what I have:
The files in the admin directory that I protected with the web-config look like this:
admin.js
angular.module('admin', [])
homeController.js
angular.module('admin')
.controller('AdminHomeController', ['$http', '$q', '$resource', '$scope', '_o', adminHomeController]);
function adminHomeController($http, $q, $resource, $scope, _o) {
....
...
}
My application level files look like this:
app.js
var app = angular
.module('app',
['ui.router', 'admin', 'home',])
.run(['$rootScope', '$state', '$stateParams', '$http', '$angularCacheFactory', appRun])
function appRun($rootScope, $state, $stateParams, $http, $angularCacheFactory) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}
app.config.js
app.config(['$controllerProvider', '$httpProvider', '$locationProvider', '$sceProvider', '$stateProvider', appConfig]);
function appConfig($httpProvider, $locationProvider, $sceProvider, $stateProvider) {
// I added this to help with loading the module after
// the application has already loaded
app.controllerProvider = $controllerProvider;
//
$sceProvider.enabled(false);
$locationProvider.html5Mode(true);
var admin = {
name: 'admin',
url: '/admin',
views: {
'root': {
templateUrl: '/Content/app/admin/partials/home.html',
},
'content': {
templateUrl: '/Content/app/admin/partials/overview.html',
},
}
};
var adminContent = {
name: 'admin.content',
parent: 'admin',
url: '/:content',
views: {
'root': {
templateUrl: '/Content/app/admin/partials/home.html',
},
'content': {
templateUrl: function (stateParams) {
return '/Content/app/admin/partials/' + stateParams.content + '.html';
},
}
}
};
var home = {
name: 'home',
url: '/home',
views: {
'root': {
templateUrl: '/Content/app/home/partials/home.html',
},
'content': {
templateUrl: '/Content/app/home/partials/overview.html',
},
}
};
var homeContent = {
name: 'home.content',
parent: 'home',
url: '/:content',
views: {
'root': {
templateUrl: '/Content/app/home/partials/home.html',
},
'content': {
templateUrl: function (stateParams) {
return '/Content/app/home/partials/' + stateParams.content + '.html';
},
}
}
};
$stateProvider
.state(admin)
.state(adminContent)
.state(home)
.state(homeContent);
}
When a user logs on then I know if it is an Admin role user as I have a security token returned to me that shows:
{
"access_token":"abcdefg",
"token_type":"bearer",
"expires_in":1209599,
"userName":"xx",
"roles":"Admin",
".issued":"Fri, 30 May 2014 12:23:53 GMT",
".expires":"Fri, 13 Jun 2014 12:23:53 GMT"
}
If an Admin role user then I want to
Download the Admin module scripts: /Content/app/admin/admin.js and /Content/app/admin/homeController.js from the server. I already have it set up like this for $http calls: $http.defaults.headers.common.Authorization = 'Bearer ' + user.data.bearerToken; so the Bearer token would need to be sent when getting the scripts:
Add the admin module to the app
Can someone give me some suggestions on how I can do these two things. After reading about require.js I feel that I would not like to use it as a solution. I would like something as simple as possible.
From what I understand until AngularJS allows it then I need to make it so that I can inject my controller. So I already added this to the appConfig:
app.controllerProvider = $controllerProvider;
But how can I download the two javascript files and how can I add these to AngularJS so that the user can start using the features of the controller inside the admin module? I saw something about $script.js being used by the Angular team. Is this a good solution and how I could I implement this to meet my fairly simple need.
You can add a resolve property to your admin routes.
var admin = {
name: 'admin',
url: '/admin',
resolve: {
isAdminAuth: function($q, User) {
var defer = $q.defer();
if (User.isAdmin) {
defer.resolve();
} else {
defer.reject();
}
return defer.promise;
}
},
views: {
'root': {
templateUrl: '/Content/app/admin/partials/home.html',
},
'content': {
templateUrl: '/Content/app/admin/partials/overview.html',
},
}
};
You can chain this as well.
resolve: {
adminPermissions: function($q, User) {
var defer = $q.defer();
if (User.permissions.isAdmin) {
defer.resolve(User.permissions.admin);
} else {
defer.reject();
}
return defer.promise;
},
hasAccessToHome: function($q, adminPermissions) {
var defer = $q.defer();
if (adminPermissions.hasAccessToHome) {
defer.resolve(true);
} else {
defer.reject();
}
return defer.promise;
},
},
The result of resolve properties will also be passed to the controller if resolved. If rejected, the route will not load. You can access it like this.
function adminHomeController($scope, adminPermissions, hasAccessToHome) {
$scope.adminPermissions = adminPermissions;
}
You can also manually bootstrap an app:
<!doctype html>
<html>
<body>
<div ng-controller="WelcomeController">
{{greeting}}
</div>
<script src="angular.js"></script>
<script>
var isAdmin = true; <!-- injected variable from server here -->
var app = angular.module('demo', [])
.controller('WelcomeController', function($scope) {
$scope.greeting = 'Welcome!';
});
angular.bootstrap(document, ['demo']);
</script>
</body>
</html>
[reference] - https://docs.angularjs.org/api/ng/function/angular.bootstrap
Instead of injecting a variable or some other server side templating method you can make a request using jQuery:
$.getJSON('/my/url', function(data) {
if (data.isAdmin) {
// bootstrap app with admin module
} else {
// bootstrap app without admin module
}
});
Here is an IE8+ compatible example alternative to the above (not jQuery):
request = new XMLHttpRequest();
request.open('GET', '/my/url', true);
request.onreadystatechange = function() {
if (this.readyState === 4){
if (this.status >= 200 && this.status < 400){
data = JSON.parse(this.responseText);
if (data.isAdmin) {
// bootstrap app with admin module
} else {
// bootstrap app without admin module
}
}
}
};
request.send();
request = null;
[reference] - http://youmightnotneedjquery.com/#json
I would highly recommend the use of some module loader.
I understand that you want to keep things simple however writing AMD (asynchronous module definition) or CommonJS modules would help reduce some of the problems associated with dependency management (like loading things in the wrong order, overriding of libraries, etc).
If you really insist, you can use jQuery.getScript() to load additional javascript sources (assuming you have jQuery as a dependency).
Pure javascript without jQuery will require you to do something like the loadScript example found in this answer.
In both cases, this would be done in the callback set for the controller; take caution because the script will be executed in the global scope so any variables, functions in the remote file MAY override things that already exist if you're not careful.
If you're still interested in a module loader but you don't like require.js, take a quick look at curl.js.
Ido Sela talks about authorization in an Angularjs app and how they handle loading (or unloading) of modules here: http://youtu.be/62RvRQuMVyg?t=2m35s
I would definitely look at this as it will give you some of the best guidance I have seen for how to handle authorization and loading of modules conditionally. (It's how I should have been handling it in my prototype, and will in future versions once I have some time to clean it up.)
If you do need to authorize after bootstrapping the application and then need to conditionally load modules, you might consider making this a server concern. For instance, using a script loading mechanism of some sort, only render the logic based on the authorization of the request.
For example, in ASP.net MVC, I would consider delivering my admin resources via a controller (not as static content) and only render the actual scripts if the user was authorized.
Update
Sorry for not taking your entire question into consideration. It sounds like you've already secured the server side. So now module loading is the problem. I would still consider a controller that would filter your requests per authorization (i.e. User.IsInRole("FooAdmin") or create an actual filter to scale your usage)