Routing single page application using angular js - angularjs

I am building a web Single Page Application using AngularJS. I need that clicking on link change URI in client browser without http request.
http://example.com/ ---> it shows my single page application and clicking on a specific link I need the URL is http://example.com/about but without send http request and show hidden div.

I don't know what you precisely want to do but if you only want do one http request you can perhaps use angular ui router with something like
$stateProvider
.state('main', {
url: "/",
templateUrl: "main.html"
})
.state('about', {
url: "/about",
templateUrl: "main.html",
controller: function($scope) {
$scope.showDiv = "true";
}
})
That way you can switch state and because everything you need is already loaded, nothing gets loaded anymore. Or perhaps you can also use parameters.
But why is it so bad to have one additional request? That would be something interesting to know! :)
EDIT: The easy approach with $location
(https://docs.angularjs.org/guide/$location)
index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example</title>
<base href="/">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-app="html5-mode">
<div ng-controller="LocationController">
<button ng-click="changeUrl()">Change url</button>
</div>
</body>
</html>
app.js:
angular.module('html5-mode', [])
.controller("LocationController", function ($scope, $location) {
$scope.$location = {};
$scope.changeUrl = function () {
// https://docs.angularjs.org/guide/$location
console.log("The current path: " + $location.path());
console.log("Changing url...");
$location.path('/newValue')
};
})
.config(function ($locationProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
})
Be sure to set the basePath correct.

Take a look at html2js. This is a grunt task to convert your html templates into a pre-cached js file.
Ideally you would run this as part of your build process. As well you can run a watch task to compile your HTML templates into the pre-cache whenever you save a template -- this is nice for development.
If you are already using gulp, there is a package for you. There are many alternatives to html2js that do essentially the same thing. So if it doesn't suit your needs, try another.
So with this in place, when you navigate to another page -- the HTML template will just be pulled out of angular's cache, and not grabbed from the server.

Related

Why Angular UI Router does not load my template?

I am really struggling with Angular since it is very fragile i think and I have a very simple case (probably the simplest case ever) which does not work yet.
Here is my module (so i do inject the library):
angular.module(
'module', ['ui.router']
My index.html:
<html data-ng-app="module">
<head>
... several libraries and my js files including ui-router library js + my app.js where the state definitions are located.
</head>
<body>
<div ui-view></div>
</body>
And why is not my template injected in ui-view?
EDIT: Sorry, i was in a hurry, forgot to add some details.. I have updated the app.js section like this:
.state('default',
{
url: '/',
template: '<h1>default</h1>'
})
.state('x',
{
url: '/x',
template: '<h1>X</h1>'
});
Now default state works as expected. But i call the url "host/x" i get a "Cannot GET /x".. when i call the url like "host/#x", it works.
But i have also this section for html5 mode in my app.js:
$locationProvider.html5Mode({
enabled: true,
requireBase: true
});
I have also this in the head section of my index.html:
<base href="/">
I thought, html5 should already handle the hash(#) part of the url? How can i get rid of that # in URL, so i can call directly "host/x"?
You need to specify the url property and go to this url to see this page. State should be something like:
.state("yourStateName", {
template: "<h1>My Contacts</h1>",
url: "/stateURL"
})
This is working example of url provider form my project:
angular.module("app")
.config(function ($urlRouterProvider, $locationProvider, $stateProvider) {
$locationProvider
.html5Mode(true);
$urlRouterProvider.when('/', '/url1');
$urlRouterProvider.when('', '/url2');
$urlRouterProvider.when('/url3', 'url4');
$urlRouterProvider.otherwise('/url5');
});

Adaptive layout (mobile/desktop) in AngularJS - what is the preferred way to do it (example inside)

What is the preferred way to handle adaptive (it's not about responsive) layout in AngularJS? What I need to do is to have a different layout with different shared components (shared directives and controllers) for desktop and mobile. I was thinking about using ui-router, here is what I have now:
index.html (main file):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.4.x" src="https://code.angularjs.org/1.4.1/angular.js" data-semver="1.4.1"></script>
<script data-require="ui-router#0.2.15" data-semver="0.2.15" src="//rawgit.com/angular-ui/ui-router/0.2.15/release/angular-ui-router.js"></script>
<script src="app.js"></script>
</head>
<body data-ng-app="plunker" data-ng-strict-di>
<header>
<nav>
<ul>
<li><a data-ui-sref="mobile.user">Mobile</a></li>
<li><a data-ui-sref="desktop.user">Desktop</a></li>
</ul>
</nav>
</header>
<main data-ui-view>
</main>
</body>
</html>
desktop.html (wrapper for desktop content):
<h1>Desktop</h1>
<div data-ui-view>
</div>
mobile.html (wrapper for mobile content):
<h1>Mobile</h1>
<div data-ui-view>
</div>
user.html (shared content):
<div data-ng-controller="UserCtrl">
User name: {{name}}
</div>
app.js
var app = angular.module('plunker', ['ui.router']);
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('mobile', {
abstract: true,
url: '/mobile',
templateUrl: 'mobile.html'
})
.state('mobile.user', {
url: '/user',
templateUrl: 'user.html'
})
.state('desktop', {
abstract: true,
url: '/desktop',
templateUrl: 'desktop.html'
})
.state('desktop.user', {
url: '/user',
templateUrl: 'user.html'
})
}]);
app.controller('UserCtrl', ['$scope', function($scope) {
$scope.name = 'John';
}]);
Preview & edit:
http://plnkr.co/edit/gRnTJkMa7hTLnffOERMT?p=preview
Is this a preferred way of doing adaptive layout?
How can I (with this approach):
Add a class to in index.html based on mobile/desktop choice
Dynamically load mobile.css or desktop.css
Best Regards,
I recently ran into same issues, that's why i landed here looking for some answers. According to my findings, looks like i have two different options:
Use your approach
Facts
Two partial different layouts, because header and footer and some other body tags are still shared. Otherwise you will have to apply data-ui-view on html
This can be achieved only if you use ui-router, this isn't working with angular default router
You will load all routes and configs, both from desktop and mobile, because you have a shared config file. This means that somehow you need to load all js files even if user is visiting only mobile version. Otherwise you will have to create a new config file for mobile version and use that one for mobile layout, or dynamic exclude some route based on detected device.
You will be able to switch from mobile to desktop within your Single Page Application without any reload. Not sure if this is an advantage or not.
Good parts:
You are not going to apply any server logic and your are not going to make a separation of main index.html file served from server. This means that the entire logic lies on the client side.
Bad Parts:
You complicate the logic because you need to make a clear separation between devices and avoid loading unused js files if possible. If you are doing this, basically you are doing another SPA, because desktop version won't be available within your SPA.
Share the same app but from server based on domain you can serve different html file. For example: yoursite.com will serve index.html which will eventually load desktop version and m.yourside.com will serve mobile.html which will eventually load mobile version.
Facts:
This is more like a hard separation and implies server logic to make a separation
When switching from mobile and desktop, a reload will take place.
Now, you can't use the same config or other changes from app.js that refers to your desktop version, so it might need to change some files to not refer anymore to libraries that are used for desktop
This approach looks more like a new angularJs app within same project (folder structure/ application folder) with same name maybe and lots of shared components and services, but less dependencies and cleaner.
To me, this sounds more like a logic separation consider that you do not need to server all those js files on your mobile version, maybe you won't use all the features of desktop version to mobile version. The only difference i see here at presentation level, different routing maybe, config file, controllers and views. Still same models and share services and big componentes.
In order to make a decision you need to think on both situations each with good and bad parts. Right now i don't know yet which decision to make, i will investigate maybe a day or two.
Good day and good luck.
Andrei
var isMobile = window.orientation > -1;
var orientationVariable = isMobile ? 'Mobile' : 'DeskTop';
if (orientationVariable == 'Mobile'){
$urlRouterProvider.otherwise("/mobile/main");
$stateProvider
.state('mobile', {
abstract: true,
url: '/mobile',
controller: 'appCtrl',
templateUrl: '../app/tpl/mobile-main.html'
})
.state('mobile.main', {
url: '/main',
controller: 'appCtrl',
templateUrl: '../app/tpl/app-mobile.html'
})}else if(orientationVariable == 'DeskTop') {
$urlRouterProvider.otherwise("/desktop/main");
$stateProvider.state('desktop', {
abstract: true,
url: '/desktop',
controller: 'appCtrl',
templateUrl: '../app/tpl/desktop.html',
})
.state('desktop.main', {
url: '/main',
controller: 'appCtrl',
templateUrl: '../app/tpl/desktop.main.html',
})}

Using ng-route with Google-Apps-Script or some means of rendering a partial html view

I am trying to get ng-route working with a google-apps-script web app. I have managed to get basic angular.js functionality working with google-apps-script, but I can't seem to get ng-route to work. I have placed ng-view tags inside a page and have included a separate JavaScript page that contains the routeProvider function.
The ng-view never gets rendered and as far as I can make out the routeProvider does not get called.
Can anyone offer any advice on using ng-route with google-apps-script or suggest another way of rendering a partial html page with google-apps-script
Any answers greatly appreciated.
Have simplified my code and added below:
Code.gs
function doGet(e) {
var template = HtmlService.createTemplateFromFile('Index');
// Build and return HTML in IFRAME sandbox mode.
return template.evaluate()
.setTitle('Web App Window Title')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function getScriptUrl() {
var url = ScriptApp.getService().getUrl();
return url;
}
index.html
<!-- Use a templated HTML printing scriptlet to import common stylesheet. -->
<?!= HtmlService.createHtmlOutputFromFile('Stylesheet').getContent(); ?>
<html>
<body ng-app="myApp">
<h1>NG View</h1>
<ng-view></ng-view>
<p>angular check {{'is' + 'working!'}}</p>
<? var url = getScriptUrl();?>
<p id="urlid"><?=url?></p>
</body>
</html>
<!-- Use a templated HTML printing scriptlet to import JavaScript. -->
<?!= HtmlService.createHtmlOutputFromFile('JavaScript').getContent(); ?>
Javascript.html
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://code.angularjs.org/1.3.15/angular.js"></script>
<script src="https://code.angularjs.org/1.3.15/angular-route.js"> </script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
<script>
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider){
console.log('routeProvider config');
var url = document.getElementById("urlid").innerHTML;
console.log('routeProvider config->' +url);
$routeProvider.when("/",
{
templateUrl: url+"?page=_app.html",
controller: "AppCtrl",
controllerAs: "app"
}
);
})
.controller('AppCtrl', function() {
var self = this;
self.message = "The app routing is working!";
});
</script>
_app.html
<div>
<h1>{{ app.message }}</h1>
</div>
When this runs angular check {{'is' + 'working!'}} works fine, but the ng-view does not get rendered the java console shows:
Error: [$sce:insecurl] Blocked loading resource from url not allowed by $sceDelegate policy.
The first obstacle is "sce"
$sce is a service that provides Strict Contextual Escaping services to AngularJS.
Refer link https://docs.angularjs.org/api/ng/service/$sce#trustAsResourceUrl
For the purpose of investigation, I disabled sce (this is not recommended, though)
$sceProvider.enabled(false);
Now the error is shifted to XMLHttpRequest cannot load... No 'Access-Control-Allow-Origin' header is present on the requested resource.
XHR requests to the Google Apps Script server are forbidden
Refer link https://developers.google.com/apps-script/guides/html/restrictions
Google Apps is delivering the files from a different origin than the scripts.google.com and angular js client code is not able to fetch the partial htmls from the same origin.
I guess approach of ng-view is not feasible given the restrictions placed by google apps.
Here is the final modified code
<script>
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider,$sceProvider){
$sceProvider.enabled(false);
console.log('routeProvider config');
var url = document.getElementById("urlid").innerHTML;
console.log('routeProvider config->' +url);
$routeProvider.when("/",
{
templateUrl: url+"?page=_app.html",
controller: "AppCtrl",
controllerAs: "app"
}
);
})
.controller('AppCtrl', function() {
var self = this;
self.message = "The app routing is working!";
});
</script>
There has been some time since the question, but I'll post a reply either way.
If your partial html page is not too complicated and big, you can use template instead of templateUrl in the routeProvider, plus create a variable with the html you want to show. Something like this below:
var partial_page = "<span>partial page</span>"
$routeProvider.when("/",
{
template: partial_page,
controller: "AppCtrl",
controllerAs: "app"
}
It worked for me, but I wouldn't advise doing so for a complicated partial page as it may become difficult to read the code

Without giving # in url I'll get a 404 error [duplicate]

This question already has answers here:
Removing the fragment identifier from AngularJS urls (# symbol)
(14 answers)
Closed 7 years ago.
Is it possible to not to give # symbol from url to display record by giving id from url directly?
I am developing a website in which I have only one view page as Login.html.
I need to access it by giving id of user from url as abc.in/1 here 1 is id of user. I set Login.html as a default page and when I try to access it by giving abc.in/1 ,i.e. id in url it can not display the record. but when I give abc.in#/1 it displays the record properly. I dont want to access it by giving # in url.
My code is as follow-
<html ng-app="myApp" style="height: 500px; overflow: auto;">
<head>
<meta charset="utf-8">
<base href="/">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title style="color: white;">c60</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<script src="../app/js/controllers/LoginController.js"></script>
</head>
<body>
---record details
</body>
</html>
and in app.js-
angular.module('myApp', [
'ngRoute', 'ngResource', 'ngCookies',
'myApp.filters',
'myApp.services',
'myApp.directives',
'myApp.controllers',
'ui.bootstrap', 'ngAnimate', 'ngDragDrop' //'ngSanitize',
]).
config(['$routeProvider','$locationProvider', function ($routeProvider,$locationProvider) {
$routeProvider.when('/:id',
{
templateUrl: '/Login.html',
controller: 'LoginController'
});
$routeProvider.otherwise({ redirectTo: '' });
//check browser support
if (window.history && window.history.pushState) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
}
}])
How to do that? I just dont want to give # in url to display a record of perticular id provided in url.I need a clean url.How to do that?
I tried to remove the # from my ulrs using the tip from the following question Removing the # symbol from angular.js urls .Now, the issue is that my url is not working if I try a direct access to them. from the given example in the related question if put the url below directly in the browser http://localhost/phones ; in my case it is abc.in/1
I'll get a 404 error. Any idea how to solve this?
It is possible to suppress the # in the url of an angular application. To do so, you need to configure the $locationProvider which you already have injected in your app config. Add the following after the $routeProvider configuration
$locationProvider.html5Mode(true);
Finally add the following to the head section of your html file
<base href="/">
A couple of things seem to be wrong here. First even when you are having only one view in your app, it is nice to have something like an index.html which will host your ng-view directive. then when you navigate to the one view of your app, that view will be loaded where you have the ng-view directive. Secondly, from your app.js, you have something as shown below
$routeProvider.when('/:id',
{
templateUrl: '/Login.html',
controller: 'LoginController'
});
$routeProvider.otherwise({ redirectTo: '' });
So this clearly says that when you enter '/' or '/1' for example in the address bar of your browser your application will fetch login.html as stated in the templateUrl. However, in your test you are entering abc.in/1 (which I do not know where it comes from)in the url address bar of your browser. when this url is checked in the route provider's routes, no match will be found. Hence you you get a 404 error. To make the matter worse your $routeProvider.otherwise is redirecting to empty string. The right thing to do is state the various routes with their respective templateUrl for your application and set otherwsise to redirect to the root of your application which in your case is still login.html or index.html if you add that now as you have only one view. Hope this helps.

$location / switching between html5 and hashbang mode / link rewriting

I was under the impression that Angular would rewrite URLs that appear in href attributes of anchor tags within tempaltes, such that they would work whether in html5 mode or hashbang mode. The documentation for the location service seems to say that HTML Link Rewriting takes care of the hashbang situation. I would thus expect that when not in HTML5 mode, hashes would be inserted, and in HTML5 mode, they would not.
However, it seems that no rewriting is taking place. The following example does not allow me to just change the mode. All links in the application would need to be rewritten by hand (or derived from a variable at runtime. Am I required to manually rewrite all URLs depending on the mode?
I don't see any client-side url rewriting going on in Angular 1.0.6, 1.1.4 or 1.1.3. It seems that all href values need to be prepended with #/ for hashbang mode and / for html5 mode.
Is there some configuration necessary to cause rewriting? Am I misreading the docs? Doing something else silly?
Here's a small example:
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.3/angular.js"></script>
</head>
<body>
<div ng-view></div>
<script>
angular.module('sample', [])
.config(
['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
//commenting out this line (switching to hashbang mode) breaks the app
//-- unless # is added to the templates
$locationProvider.html5Mode(true);
$routeProvider.when('/', {
template: 'this is home. go to <a href="/about"/>about</a>'
});
$routeProvider.when('/about', {
template: 'this is about. go to <a href="/"/>home</a'
});
}
])
.run();
</script>
</body>
Addendum: in re-reading my question, I see that I used the term "rewriting" without an abundance of clarity as to who and when I wanted to do the rewriting. The question is about how to get Angular to rewrite the URLs when it renders paths and how to get it to interpret paths in the JS code uniformly across the two modes. It is not about how to cause a web server to do HTML5-compatible rewriting of requests.
The documentation is not very clear about AngularJS routing. It talks about Hashbang and HTML5 mode. In fact, AngularJS routing operates in three modes:
Hashbang Mode
HTML5 Mode
Hashbang in HTML5 Mode
For each mode there is a a respective LocationUrl class (LocationHashbangUrl, LocationUrl and LocationHashbangInHTML5Url).
In order to simulate URL rewriting you must actually set html5mode to true and decorate the $sniffer class as follows:
$provide.decorator('$sniffer', function($delegate) {
$delegate.history = false;
return $delegate;
});
I will now explain this in more detail:
Hashbang Mode
Configuration:
$routeProvider
.when('/path', {
templateUrl: 'path.html',
});
$locationProvider
.html5Mode(false)
.hashPrefix('!');
This is the case when you need to use URLs with hashes in your HTML files such as in
link
In the Browser you must use the following Link: http://www.example.com/base/index.html#!/base/path
As you can see in pure Hashbang mode all links in the HTML files must begin with the base such as "index.html#!".
HTML5 Mode
Configuration:
$routeProvider
.when('/path', {
templateUrl: 'path.html',
});
$locationProvider
.html5Mode(true);
You should set the base in HTML-file
<html>
<head>
<base href="/">
</head>
</html>
In this mode you can use links without the # in HTML files
link
Link in Browser:
http://www.example.com/base/path
Hashbang in HTML5 Mode
This mode is activated when we actually use HTML5 mode but in an incompatible browser. We can simulate this mode in a compatible browser by decorating the $sniffer service and setting history to false.
Configuration:
$provide.decorator('$sniffer', function($delegate) {
$delegate.history = false;
return $delegate;
});
$routeProvider
.when('/path', {
templateUrl: 'path.html',
});
$locationProvider
.html5Mode(true)
.hashPrefix('!');
Set the base in HTML-file:
<html>
<head>
<base href="/">
</head>
</html>
In this case the links can also be written without the hash in the HTML file
link
Link in Browser:
http://www.example.com/index.html#!/base/path
Fur future readers, if you are using Angular 1.6, you also need to change the hashPrefix:
appModule.config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('');
}]);
Don't forget to set the base in your HTML <head>:
<head>
<base href="/">
...
</head>
More info about the changelog here.
This took me a while to figure out so this is how I got it working - Angular WebAPI ASP Routing without the # for SEO
add to Index.html - base href="/">
Add $locationProvider.html5Mode(true); to app.config
I needed a certain controller (which was in the home controller) to be ignored for uploading images so I added that rule to RouteConfig
routes.MapRoute(
name: "Default2",
url: "Home/{*.}",
defaults: new { controller = "Home", action = "SaveImage" }
);
In Global.asax add the following - making sure to ignore api and image upload paths let them function as normal otherwise reroute everything else.
private const string ROOT_DOCUMENT = "/Index.html";
protected void Application_BeginRequest(Object sender, EventArgs e)
{
var path = Request.Url.AbsolutePath;
var isApi = path.StartsWith("/api", StringComparison.InvariantCultureIgnoreCase);
var isImageUpload = path.StartsWith("/home", StringComparison.InvariantCultureIgnoreCase);
if (isApi || isImageUpload)
return;
string url = Request.Url.LocalPath;
if (!System.IO.File.Exists(Context.Server.MapPath(url)))
Context.RewritePath(ROOT_DOCUMENT);
}
Make sure to use $location.url('/XXX') and not window.location ... to redirect
Reference the CSS files with absolute path
and not
<link href="app/content/bootstrapwc.css" rel="stylesheet" />
Final note - doing it this way gave me full control and I did not need to do anything to the web config.
Hope this helps as this took me a while to figure out.
I wanted to be able to access my application with the HTML5 mode and a fixed token and then switch to the hashbang method (to keep the token so the user can refresh his page).
URL for accessing my app:
http://myapp.com/amazing_url?token=super_token
Then when the user loads the page:
http://myapp.com/amazing_url?token=super_token#/amazing_url
Then when the user navigates:
http://myapp.com/amazing_url?token=super_token#/another_url
With this I keep the token in the URL and keep the state when the user is browsing. I lost a bit of visibility of the URL, but there is no perfect way of doing it.
So don't enable the HTML5 mode and then add this controller:
.config ($stateProvider)->
$stateProvider.state('home-loading', {
url: '/',
controller: 'homeController'
})
.controller 'homeController', ($state, $location)->
if window.location.pathname != '/'
$location.url(window.location.pathname+window.location.search).replace()
else
$state.go('home', {}, { location: 'replace' })

Resources