Flexible routing and 'passing through' certain URLs? - javascript

I have a Django web application serving an Angular JS client application.
My URL tree looks like this:
> /
> /admin/
> /login/
> /logout/
> /static/
> /admin/
My Angular application's base URL is /admin/, and its static content lives in /static/admin/(js|css|views).
Here's the configuration for the route provider:
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/admin/', {
controller: 'IndexController',
templateUrl: '/static/admin/views/index.html'
}).otherwise({ redirectTo: '/admin/' });
});
app.config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode(true);
});
I have a few problems here. The first problem is URL flexibility. What if I decide to move my base URL to something like /angularadmin/? I'll have to rewrite a lot of JavaScript and a lot of <a> links. The second problem is that when
I provide a link to /logout/, this link hits the otherwise clause and redirects back to /admin/.
How do I tell Angular to pass through links to /logout/ and how can I make configuration of the base URL here much more flexible?

You should always have your base path in configuration and then use that when you need to specify the URL.
Here is an example:
// Define the configuration
app.constant('config', {
serviceRoot: '/api/',
staticRoot: '/static/admin/',
appRoot: '/admin/'
});
//Use the configuration
app.config(['$routeProvider', 'config', function ($routeProvider, config) {
$routeProvider
.when(config.appRoot, {
controller: 'IndexController',
templateUrl: config.staticRoot + 'views/index.html'
})
.otherwise({ redirectTo: config.appRoot });
}]);
UPDATE:
As stated in the comments in order to open an external link use target="_self".
If you need the configuration values in the views/templates you can inject the configuration object in the $rootScope and access it from the views:
// We add the configuration as part of the root scope
app.run(['$rootScope', 'config', function($rootScope, config) {
$rootScope.appConfig = config;
}]);
Then you can use it in the view like this:
<a ng-href="{{appConfig.appRoot}}someroute/">...</a>

Step 1. update your base tag in your angular application progmatically so that if you move your angular application, you don't have to change your code.
in index.html - <script src='setbase.js' />
var baseTag = document.createElement('base');
var base = document.location.href;
//console.log('Detecting Document Base: ',base);
// remove query parameters and anchors
base = base.replace(/\#.*/,'').replace(/\?.*/,'');
//console.log(' Removing Anchors and Query Parameters: ',base);
// remove documents, leaving only path
base = base.replace(/\/?[^\/]]*$/,'/');
//console.log(' Removing Documents: ',base);
baseTag.href = base;
document.head.appendChild(baseTag);
document.scripts[document.scripts.length-1].remove();
step 2: setup angular to use hash tags in html5 mode so that it rewrites anchor links. Include this module and then in your own module configire this module as a dependency.
(function(window,angular)
{
'use strict';
var module;
module = angular.module('aLinkRewrite',[]);
module.config
(
[
'$provide','$locationProvider',
function (p,lp)
{ // (decorator is undocumented... *sigh*)
p.decorator( // decorate the
'$sniffer', // sniffer service
[
'$delegate', // obtain a delegate object to modify the sniffer
function(d) // which we will call 'd'
{
d.history = false; // tell angular that we don't have html5 history capabilities
return d;
}
]
);
lp.html5Mode(true); // turn on html5 mode link rewriting
}
]
).run(['$location',function($location){}]); // inject $location into the browser
})(window,window.angular);
From now on, all of your routes and links in angular do not include your base. If angular is located in '/admin/index.html', then your route for '/admin/' is now just '/'
All of your angular links in html can be referenced as <a href="whatever">, and they will directed to admin/whatever because of the base tag, and show up in the browser bar as admin/#/whatever
because your static content is outside of your base, you continue to link to it as /static/whatever. This is also true with your login and logout links.

Related

How to force a browser to reload angular template file and not cache it?

When loading a javascript file I can force the browser to not use the cache by adding a version when loading the file e.g.
<script src="~/angular/myapp/app.js?version=123"></script>
From inside my angular app I am doing this:
$routeProvider.when("/", {
controller: "userController",
controllerAs: "vm",
templateUrl: "Path/to/Template/Index.html"
});
How can I make sure that any changes I made to the Index.html won't be cached? Or this isn't an issue for some reason?
angular 1.x
You could do the same for your Index.html file. Simply append a "cache buster" query string as you did to your js file. There are a few techniques to do this, but a simple Date.now() timestamp should demonstrate the goal
...
$routeProvider.when('/', {
controller: 'userController',
controllerAs: 'vm',
templateUrl: `Path/to/Template/Index.html?t=${Date.now()}`
});
If your app grows in complexity and you wish to prevent caching of templates altogether, you can achieve this with some middleware...
angular.module('preventTemplateCache').factory('preventTemplateCache', function($injector) {
return {
'request': function(config) {
if (config.url.indexOf('views') !== -1) {
config.url = `${config.url}?t=${Date.now()}`
}
return config;
}
}
}).config(function($httpProvider) {
$httpProvider.interceptors.push('preventTemplateCache');
});
The above example assumes a directory of views for your templates.
Finally, as a comment pointed out, if you are simply wishing to prevent caching for local development, you can toggle settings to disable cached resources through your browsers dev tools.

Several angular modules for routing

Can i create several modules for routing in AngularJS app like:
1. First route management file:
angular.module('app.p.routes', ['ngRoute'])
.config(function ($routeProvider, $locationProvider) {
$routeProvider
.when('/forbidden',
{
templateUrl: 'app/views/pages/forbidden.html'
})
.......................
2. Second route management file:
angular.module('app.admin.routes', ['ngRoute'])
.config(function ($routeProvider) {
$routeProvider
.when('/admin-dashboard',
{
templateUrl: 'app/views/pages/admin/dashboard.html',
controller: 'dashboardController',
controllerAs: 'dashboard'
})
.............................
3. Main app file:
angular.module('mainApp',
[
'ngAnimate', //animating
'app.p.routes', //public routing
'app.admin.routes',
'ui.bootstrap',
'ngParallax', //parallax effect
'ngFileUpload'
])
When i tried to use this approach page hangs and angular throws error:
> VM188:30554 WARNING: Tried to load angular more than once.
I need an approach to split public and admin routing management.
You can have as many AngularJS modules as you like. There are no rules against that, however, you've attempted to include the Angular source twice which is why you're seeing this warning...
> VM188:30554 WARNING: Tried to load angular more than once.
The simplest solution to your issue that I can think of, is to add an event listener to the $routeChangeStart event. With this you'll be able to verify that the current user has the correct permissions to view anything before they actually to do so.
A simple Service to store some basic information on the current user could like this.
var app = angular.module('app', ['ngRoute']);
app.service('AuthenticationService', function () {
// Set the User object
this.setUser = function () {
this.$user = user;
};
// Get the User object
this.getUser = function (user) {
return this.$user
};
});
And then upon receiving the $routeChangeStart event, you can retrieve the user object and confirm that they are allowed to proceed to the chosen resource.
Here's an example, whereupon a user needs to be an Administrator to view any route that has "/admin" in it.
app.run(function ($rootScope, $location, AuthenticationService) {
// Create a listener for the "$routeChangeStart" event
$rootScope.$on('$routeChangeStart', function () {
// Is the user is an Administrator? Are they attempting to access a restricted route?
if ($location.url().indexOf('/admin') && !AuthenticationService.getUser().isAdmin) {
// Redirect the user to the login page
$location.path('/login');
}
});
});
If you want a more advanced solution however, have a look at this: https://github.com/Narzerus/angular-permission
This will enable you to achieve a more in-depth ACL implementation across your application.

AngularJS Partial HTML not able to use jquery lib (Which is loaded in index.html)

I am using a web templates which is use jquery image gallery or other etc.
I want to convert this template to angularjs structure. I make some partial html pages like slider.html or contact.html and its controller.
when i call partial html to index ,slider can not able to use jquery libraries which ı loaded them in index.
İf i refresh the page when partial is open it works again.
I read some article about directives or other etc. but ı am not writing any script they are just loaded in index and html uses them so i do not need any directive to work a script i just need use all library file in partial html.
is there any way ?
i try to load all libraries which partial will use but in this way all libraries loaded each call and it works but it is not a good way
I have some tips: First of all, your libraries should not be included in the view. You should include the library just in one point of your application. Could be asynchronous (via JavaScript), or by adding the script tag for the library in your index file.
If you decide add the library via JavaScript, take into account to do a check to prevent multiples includes, e.g.,
if (!window.jQuery) {
//include jQuery library
}
Also you can add the script tag in your index/main html file and if you want to load it asynchronously, you can add the attributes async or defer.
-- Take a look at HTML 5 <script> Tag.
Now, related to AngularJS, when you load a partial view, is raised the event $includeContentLoaded which is emitted every time the ngInclude content is reloaded.
If you have two or more partial views included, you should ask for the specific view where you want to load a library/plugin or make some DOM manipulation, e.g.,
controller.js
angular
.module('myApp')
.controller('HomeCtrl', [
'$scope', '$window',
function ($scope, $window) {
'use strict';
var homeCtrl = this,
$ = jQuery; //safe alias
//Emitted every time the ngInclude content is reloaded.
$scope.$on('$includeContentLoaded', function (event, source) {
if ('src/pages/home/zipcodeForm/view.html' === source) {
//initialize your jQuery plugins, or manipulate the DOM for this view
}
else if('src/pages/home/section1/view.html' === source){
//initialize your jQuery plugins, or manipulate the DOM for this view
}
});
//Can be used to clean up DOM bindings before an element is removed
//from the DOM, in order to prevent memory leaks
$scope.$on('$destroy', function onDestroy() {
//destroy event handlers
});
}
]);
The code that matters here is the event $includeContentLoaded. Visit the site for more information: https://docs.angularjs.org/api/ng/directive/ngInclude
If you are using ng-view you should have no problems, just register the view in the router (there are a lot of ways to achieve the same)
module.js
angular
.module('myApp', ['ngRoute' /*, other dependecies*/]);
router.js
angular
.module('myApp')
.config(['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
'use strict';
$routeProvider
.when('/', {
templateUrl: 'src/pages/home/view.html',
controller: 'HomeCtrl',
controllerAs: 'homeCtrl',
reloadOnSearch: false
})
.when('/index.html', {
redirectTo: '/'
})
.when('/home', {
redirectTo: '/'
})
.when('/404', {
templateUrl: 'src/pages/error404/view.html',
controller: 'Error404Ctrl',
controllerAs: 'error404Ctrl'
})
.otherwise({
redirectTo: '/404'
});
/*
Set up the location to use regular paths instead of hashbangs,
to take advantage of the history API
$locationProvider.html5Mode(true);
*/
}
]);
When the view is loaded, it emits the event: $viewContentLoaded meaning the DOM is loaded, then you can initialize jQuery plugins safely.
controller.js
angular
.module('myApp')
.controller('HomeCtrl', [
'$scope', '$window',
function HomeCtrl ($scope, $window) {
'use strict';
var homeCtrl = this, //the controller
$ = jQuery; //safe alias to jQuery
//Emitted every time the ngView content is reloaded.
$scope.$on('$viewContentLoaded', function() {
$('.slickSlider').slick({
slidesToShow: 4,
arrows: true,
slidesToScroll: 1
});
});
}
]);
Important: In the previous code, the order where scripts are included does matter.
module.js
router.js
controller.js
I highly recommend using automation tools such as gulp

Angular ui-router with express

I have set up ui router with the following states:
$locationProvider.html5Mode(true);
$urlRouterProvider.otherwise('/');
$stateProvider
.state('adminprojects', {
url: '/adminprojects',
templateUrl: 'views/adminProjects.html',
controller: 'adminProjectsCtrl'
})
.state('adminprojectsdetails', {
url: '/adminprojects/:id',
templateUrl: 'views/adminProjectsDetails.html',
controller: 'adminProjectsDetailsCtrl'
})
adminprojects view contains a list with ui-sref links. eg
<li><a ui-sref="adminprojectsdetails({id: project.id})" href="/adminprojects/1">Project 1</a></li>
If I click this link for example adminprojects/1 the adminProjectsDetails.html view is shown correctly. However if I refresh this page or navigate directly to this url then the view does not load.
This however works as expected if I have html5 mode set to false;
In express I have this
router.get('/*', function(req, res) {
res.sendfile(__dirname + '/public/index.html');
});
Controller are just pretty much boilerplate for now:
angular.module('jhApp')
.controller('adminProjectsCtrl', function($scope, projects) {
$scope.projects = projects.items;
});
and
angular.module('jhApp')
.controller('adminProjectsDetailsCtrl', function($scope, $stateParams) {
console.log($stateParams);
});
Any ideas why this is not working?
Did you create your project generator-angular ?
If so put <base href="/"/> inside your HEAD above all other elements.
Generator angular create index.html using relative paths for src folders like :
<script src="xxx/yyy.js"/>
When you refresh the page , browser looking for /adminprojects/xxx/yyy.js (.css , *) file which is not found. Because of that you can get lots of script and style errors. Using base tag fixes that problem.
Or simply just change your all src locations to "xxx/yyy." -> src="/xxx/yyy."

Can angularjs routes have default parameter values?

Can I set a default value of a parameter of a route in AngularJS? Is there a way to have /products/123 and /products/ handled by the same route ?
I'm looking to refactor my existing code, which looks like:
myModule.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/products/', {templateUrl: 'products.html', controller: ProductsCtrl}).
when('/products/:productId', {templateUrl: 'products.html', controller: ProductsCtrl})
}]);
function ProductsCtrl($scope, $routeParams) {
$scope.productId = typeof($routeParams.productId) == "undefined" ? 123 : $routeParams.productId;
}
It works, but it's not very elegant. Is there a better way ?
I recognize that this question is old, but still: Why don't you just redirect the "empty" URL to one containing the default productId?
myModule.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/products/', {redirectTo: '/products/123'}).
when('/products/:productId', {templateUrl: 'products.html', controller: ProductsCtrl})
}]);
AngularJS does not allow default values for route parameters.
But routes (in AngularJS) should not have default parameters.
Resources could have default parameters.
In AngularJS if you want a route with an optional parameter, these are actually two different routes.
Why?
Routes should be simple
Routes does not allow regular expressions matching for parameters
Routes are not something which exposes an API to work in your application (unlike Resources do). Routes are just configuration which connects a URL with a template and a controller. Thus having more routes is better:
It is clear which route maps to which url.
It is more verbose, but simpler to read. Having more complex routes would create a steeper learning curve where AngularJS does not need one.
Unlike server-side frameworks which have routes
AngularJS routes do not have names.
You do not build URLs from the defined routes.
You do not have logic (a.k.a functions) in the routes definitions.
Simpler routes = more lines to define them = less headaches working with them.
NOTE: Please keep in mind the question and this answer are for an old version of AngularJS (1.0 I think) pre-dating the new routes/resources implementation.
I had a similar requirement. What i did was to create a function to resolve. Something like below
myModule.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/products/', resolveProduct()).
when('/products/:productId', resolveProduct())
}]);
function ProductsCtrl($scope, $routeParams) {
$scope.productId = $routeParams.productId;
}
function resolveProduct() {
var routeConfig = {
templateUrl: 'products.html',
controller: ProductsCtrl,
resolve: {
productId: ['$route', function($route){
var params = $route.current.params;
params.productId = params.productId || 123;
}]
}
}
return routeConfig;
}
With url: "/view/:id/:status?", You can indicate an optional parameter.
Just thought someone may need it.
Not sure if this question is specific to $routeProvider but in $stateProvider, you can achieve this by
myApp.config(function($stateProvider) {
$stateProvider
.state('products', {
url: '/:productId',
templateUrl: "/dashboard/products.html",
controller: 'ProductController',
params: {
productId: {
value: "defaultValue",
squash: true // or enable this instead to squash `productId` when empty
}
}
});
});

Categories