How to bypass routing in Angular UI Router - angularjs

I'm creating a web application in which the bulk of the functionality is deployed as an AngularJS single-page application, but there are also a few static content pages that are served in the traditional way.
Navigation within the SPA employs Angular UI Router and seems to be working correctly, as does the serving of the static pages.
However, the code architecture is such that all scripts are referenced from within the main site template and are thus served with every page, including the static pages. This is a deliberate decision, as there are some scripted features that need to accessible from everywhere, even from within the static pages.
For example, the header region of every page contains a search box with typeahead, that allows users to navigate to a view within the SPA, populated to reflect the selected content.
If a search is invoked from within the SPA, everything works as expected, and the routing mechanism correctly executes the required state transition to render the selected content. However, if the same search is conducted from within one of the static pages, although UI Router correctly processes the state transition, the content is not rendered because the static page does not contain the <ui-view> element that is usually targeted by rendering.
Although I can see what's causing the problem, I'm not sure how best to resolve it. From a novice perspective, it seems that I need to intercept the content search/selection process and execute different logic, depending on whether or not the search was invoked from within the SPA or a static page. I anticipate something like this:
if ($state.current.name) {
// The search was invoked from within the SPA, so only a state change is required.
$state.go("render", { key: selected_key });
} else {
// The search was invoked from a static page, so an SPA page load is required
What can we do here to bypass routing and issue a
server request for the corresponding SPA deep-linked page?
}
But I'm willing to guess that there is a more elegant way of dealing with this issue, maybe one that's baked into Angular UI Router.
Any suggestions please?
Many thanks,
Tim

For anyone facing a similar problem, the solution is actually very simple.
Instead of attempting to intercept and coerce the state transition, the desired behavior can be achieved through a simple modification to the main page template, enabling all pages, including static pages, to participate in Angular view rendering.
In my original implementation only the SPA page used the <ui-view> element, which resulted in the static pages ignoring the rendering that typically occurs during a state transition.
To solve the problem, I eliminated the <ui-view> element from the SPA page and, instead, added a ui-view attribute to the containing element in the main page template:
<html ng-app="myApp">
<head>
... page head content goes here ...
</head>
<body ng-controller="myController">
<div ui-view>
... server-generated page content is injected here ...
</div>
... page scripts are referenced here ...
</body>
</html>
With this change in place, every page is now effectively an SPA and capable of re-rendering to reflect state transitions (albeit that most of the time this never occurs, since the user simply views the pre-rendered content that was delivered by the server).

Related

Special case of mixed client and server side rendering | What would this be called?

Case Study
Consider the following scenerio:
I have a Node-Express server
The server renders a new .html file for every page and sends it to the client
Within each .html file:
there is some dynamic content filled in with EJS (such as the user's name in the navigation bar),
and part of the page (inside a <div id='app'></div>) is a React App (for instance, a table view with filters).
ReactJS, ReactDOM, and my React code is imported using <script src="___.js"> tags
Every time the client clicks on a link to go to another page, the server handles the routing and generates a new .html file that has
some dynamic content (e.g. user name in navbar)
A div containing a new React App, with the react code imported in script tags
Question
My question is Am I right to say that when it comes to Routing, this application uses "Server-Side Routing" and as for Rendering, a mixture of Client and Server-Side Routing?
Regarding your questions #2 and (perhaps) #3:
I would distinguish between static content, which is the same for all users of an app, and dynamic content, which depends on the user and can also change over time.
Server-side rendering is when the server produces HTML page that contain dynamic content. Such pages cannot be cached (at least not in a public cache, and even a private cache would have to be invalidated as the content changes over time). A further consequence of server-side rendering is that the whole HTML page must be reloaded when the dynamic content changes.
An alternative approach is a static (cacheable) HTML page that loads and renders all dynamic content with Javascript code (AJAX). This leads to pages that build up over time, as the dynamic content is loaded, and the "time to visual completeness" can become an issue.
Your approach sounds like a compromise where HTML pages contain some dynamic content, whereas other parts of the dynamic content (that change more frequently, I assume) are handled with AJAX methods.

Add an additional page to an Angular SPA

The Scenario
I'm developing the front-end (CSS only) of an Angular SPA.
I'm not especially familiar with Angular routing.
I'd like to add a standalone page containing Bootstrap components just for development purposes (yes, I know this means it won't be a single page application anymore). This way I have one unified view with all the components so I don't have to switch back and forth while working on the CSS. It also acts as documentation for the Bootstrap for the other devs to refer to.
What I've tried
I originally added a bootstrap.html page to the app folder, alongside the app's index.html This worked at first, but has now stopped working. What would be the best/standard way to achieve something like this?
Update: I've managed to fix some of the JS errors, so the page is up and running again. My question remains though: "is there a way of adding a standalone page that is considered standard/best practise, or is it literally just add a separate HTML page at the app root?"
If you use a target='_self' in your linking anchor tag, this should force a full page reload, and that will avoid the angular routing - which is where I expect your request is getting hijacked (by design).
e.g.
link
Answering your updated question
Not to my knowledge, since (as you correctly pointed out) this mixes the SPA design pattern.

what is the difference between hashbang and HTML5 pushState? [duplicate]

I'm asking this because a couple of times now, I've tried to play around with the $locationProvider.html5Mode(true) command along with <base href="/"> and ran into a lot of errors calling the scripts/styles/images for my project. I guess there must be something I am doing wrong, but is there a certain folder structure you should follow so you don't run into these errors? Or is there a specific way that the base href works that I'm not quite understanding?
Recently, I thought I'd try it on a very, very small app. It's effectively a static website, but I want to take advantage of Angular's routing to make sure all of the pages can load instantly. So my structure would be something like this:
my-project
css
images
js
angular
app.js
app.routes.js
mainCtrl.js
views
home.html
about.html
contact.html
index.html
So I know that this folder structure isn't great, but I'll only be using Angular in this project for routing, nothing more, so it fits my needs.
I put into the head <base href="/">, put in body ng-app and ng-controller, and inside the body put a <div ng-view> somewhere too.
I added in the $locationProvider.html5Mode(true) and tried the app out. All of my scripts are then being loaded as http://localhost:8888/script.js which is incorrect. The project is located in a folder so that index.html is located in http://localhost:8888/my-project/index.html. So, it should be loading the scripts from http://localhost:8888/my-project/js/angular/app.js for example.
Is there something that I'm not understanding about the base href? Eventually I may host this app somewhere online, so I want the URLs to scripts etc to all be relevant to the file really. Anyone have any ideas?
Alright, so above the base href tag I would have my CSS styles which would be linked as css/style.css and at the bottom of my body tag I would have my scripts loaded as js/init.js or js/angular/app.js for example. This would try to load it as if the js folder is located directly at localhost:8888/js.
The Angular framework is a Single Page Application (SPA) that is able to run in a browser by essentially tricking the browser into running code snippets rather than make server calls, by making use of the "hash" (#) page anchor. Normally, a URL with a # would jump to a specific anchor point in the page; in the case of Angular or other similar SPA frameworks, the # is redirected to a code segment instead.
Ideally, you would like to not have to reference this # in your page URLs. This is where Html5Mode comes into play. Html5Mode is able to hide the #, by using the HTML5 Push State (aka history API).
When Html5Mode is enabled, the normal links on the page are silently replaced by Angular with event listeners. When these events are triggered, the current page is pushed into the browser history, and the new page is loaded. This gives the illusion that you are navigating to a new page, and even allows for the back button to operate.
This is all fine when you are dealing with links which are clicked from within the running application, but relying on event listeners can't work if you navigate to the page from an external source, where Angular isn't loaded into memory yet. To deal with this, you must be loading your pages from a web server which supports URL rewrites. When the server receives a request for a URL that there isn't a physical page for, it rewrites the URL to load the base HTML page, where Angular can be loaded and take over.
When Angular receives a request for a route which has been rewritten in this manner, it must first determine what the intended route was. This is where the Base HTML Tag comes into play. Angular uses the Base reference to help it to determine which part of the URL is on the server, and which part is a client route. Essentially, where the # in the URL would be if Html5Mode was not enabled.
Unfortunately, Base is an HTML Tag that is used by the browser for more than just Angular. The browser also uses this tag to determine the correct location to load scripts and resources using relative paths from, regardless of the path in the location bar. In general, this isn't a problem if all of the scripts and resources are relative to the location of the Index.html file. When Base is omitted, the browser will load scripts from the apparent base path determined by the current URI. However, once you provide it, the browser will use whatever value you have supplied.
In general, unless you are hosting angular on a sub-page of your site and you want your users to expect something specific in the URL string, you should always control the base on your server, and use Base="/" on the client side.

Angular UI Router - How to preserve views while switching views

I am I new to Angular and UI Router.
Plunk http://plnkr.co/edit/1wfyrGryfGG5RtXozPFY?p=preview
Setup I have three top level application nav buttons Home, Projects, Help. They load different views home.html, projects.html and help.html using the Angular UI Router ui-view directive. This works good.
The Projects.html view has a tab bar with each tab corresponding to a project: D1, D2 D3 etc., I show the corresponding project tab using url router attributes.
Every time I click the Projects button it is reloading the tab bar completely. I loswe the current tab and hopefully if any nested views inside it. Basically the page contents of Project.html, invoking the controller as well.
I read through the wiki documents and couldnt figure out how to implement my required functionality. I am sure I am missing something. Will it always reload the view?
Question: How to avoid reloading the projects view contents so that I can retain the selected tab and all the contents as-is before switching to Home. Because I would have a lot of nested views and models on each project.
I wanted similar functionality too, but ui-router doesn't yet support it. I forked ui-router to support "parallel states" and submitted it to the project for comment. The gist of the conversation is that ui-router will eventually support some form of parallel states but not yet. In the meantime, you can try my fork of 0.2.10 which provides the parallel states that you want.
Read the conversation here: https://github.com/angular-ui/ui-router/issues/894
View the sample parallel tabs plunk here: http://plnkr.co/edit/YhQyPV?p=preview
Here is the fork; build it with grunt: https://github.com/christopherthielen/ui-router
One option would be to implement a service that can be used to maintain the previous state. Services persist over controller changes, thus they can be used to maintain the previous page state and updated when the route changes. something similar to this would work.
app.factory('persitDataService', [function(currentStateData){
var stateService = {
state:{
//your object data set to passed in data
}
//other functions here
};
return stateService
});
then in the controllers just inject the service and assign to a scope value. When the route changes just reset the data in service to new state and inject into new controller
This should work for the previous page state. If you are wanting to save the states of all previous pages then this becomes a larger problem but should be accomplished in much the same way only with a more complicated service setup.
This could also be combined with local and session storage

Using AngularJS routing with a single-page web app

I read about angularJS routing. I want to implement it in my web app, but unfortunately I have a rather difficult situation changing to routing now I think. This is how my app works now (and I know it's probably not the way it should, but it does work):
I have one controller for the whole app.
The view is built with some divs, one of which is a menu div. The others are 'partial' views as angularjs calls them I guess. But the problem I see here is that two of my partial views can be shown at the same time (page is built like this, partial view only takes a portion of the page for itself).
So what I am doing is: I click the button on the menu -> one partial view shows up (ng-show), then I can click something on this partial view to get the second partial view opened on the same page (menu and first partial must stay the way they are).
At the moment I include partials within some divs with php include (which is I am sure the wrong way) and the divs have ng-show on them so that nothing is shown on the beginning. Then I manipulate all the clicks in the menu with setting ng-show parameters of all my partials (views). So if one button is clicked I hide all the others (with ng-click and a function inside controller). But this is tedious work and not the angularJS way and that is why I am asking this question here.
Example of my included partial (stripped of all unnecessary css classes etc):
<div ng-show="showNames">
<?php include_once("views/AREA1/names.php") ?>
</div>
And names.php has for instance just some few elements with ng-repeat and other angularJS directives… I have many includes like that and they work with just ng-show manipulation very well. But now that I grasped some of the AngularJS concepts I see that I made a mistake…
To sum up: how can I use angularJS routes (with ng-view perhaps?-not necessary) to show views within my web app? (taking into account the situation that I have described above). I just want user to be able to know on what "part of page" he is at any given moment.
EDIT: I went trough this and I reckon I could work it out: I need a structure similar to the one in this example 2.1 Online Demo, but furthermore I need to be able to click something on ng-view which should open another view (first one should stay in place). Any idea how to accomplish this?
By using routing feature in AngularJS, the html content of ng-view will be totally replaced by the new partial. You should not use ng-view for such a purpose like showing multiple partials at the same time.
But you can think about mix the ng-view and ng-include.
Let's say, we click each item on the menu, ng-view changes the sub-partial, you can have ng-include in your sub-partials which we can all it here like sub-sub-partial.
Try reading ng-include
AngularJS has ng-view which would contain the main theme of current context, rest of the UI elements are all managed by ng-include. Routes also work in sync with nv-view.
If your view requirement are complex look at ui-router component that supports various combinations.

Resources