What is the proper way of using client-side routing? - javascript

I have a question regarding AngularJS and Node.js.
I have a web application and I use client-side routing with routeProvider to navigate through the pages of my web application.
And I get the data through a RESTful API server-side. But all of logic is done in AngularJS, because with the client-side routing, all I do in Node.js is :
exports.partials = function(req, res, err) {
var name = req.params.name;
res.render(name);
};
So, I only use Node.js to render the template layout and the partial view, but all of the logic is in AngularJS.
Is it the proper way to use it?

Angular.js is a javascript framework to create a SPA or Single page application.
It creates its own navigation system using the hash(#) or hashbang(#!) in the url to represent the different states or pages of your application but all this happens in your home page. The browser never changes to another page because all application state will be lost in a page refresh (HTTP is a stateless protocol).
Usually you need 3 parts to create an Angular application each one with it's own routing system.
Your angular application: All the scripts and resources are loaded in the home page. The routing system is provided by $routeProvider and the hash(#). Eg: http://mywebsite/#/products or http://mywebsite/#/providers. All this is relative to your home page.
Your templates: This is retrieved using ajax and can be routed however you want, Eg: http://mywebsite/product.html or http://mywebsite/templates/product.html serving static html files or even http://mywebsite/templates/products using a restful approach and a server side routing mechanism. There is no general rule here because basically depends on the server technologies chosen and your own design.
Your data: Usually a Resful API to supply you application with business data stored in a database. Rest creates some basic rules you must follow like treat everything as a resource and manipulate it with verbs. Eg: GET http://mywebsite.com/api/products or POST http://mywebsite.com/api/providers
This is an example of an Angular route provider
.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/Book/:bookId/ch/:chapterId', {
templateUrl: 'chapter.html',
controller: 'ChapterController'
});
In this case angular will fetch your home page from http://mywebsite.com initially and the template from the content of your chapter.html file located in http://mywebsite.com/chapter.html and your data from whatever configuration you set to your $http service. As long as you set your routes in a way that don't conflict with each other you are safe. In your case you can use express.js to create a restful routing system for your templates or serve them directly from the public folder as html.

Related

Wildcard route for static content in Angular 7

I am learning Angular 7 by studying this example app. The example app uses a wildcard route to handle all otherwise-unhandled routes.
Specifically, this app-routing.module.ts directs all miscellaneous routes to AppConfig.routes.error404, which is handled by Error404PageComponent.ts, which then ultimately serves up error404-page.component.html for every possible route that is not specified by its own component and named route.
What specific changes would need to be made to the code in this sample app in order for the wildcard route serve different static content for different submitted routes?
For example, if a web user typed in the route /i-am-a-jelly-donut, what changes would need to be made so that the request would 1.) continue to go through Error404PageComponent.ts, but have the user's browser receive a new i-am-a-jelly-donut.page.html instead of the error404-page.component.html view?
The Error404PageComponent.ts would still serve up error404-page.component.html for every non-specified route. However, we would be adding logic to give special handling inside Error404PageComponent for a specific static route in addition to the logic for every non-specified route.
The goal here is to be able to handle static routes without having to create a separate component for each and every route. Think, for example, of a blog where most of the routes have an identical template, but with different content in each blog entry.
Templates are compiled into the components at build time and you are not going to be able to change which template a component uses at runtime but you can hide and show sections based on conditions. Inject the router into your component
constructor(private router: Router) {}
Now you can set a variable on your component based on if the route contains 'i-am-a-jelly-donut'
jellyDonut = this.router.url.indexOf('i-am-a-jelly-donut') !== -1;
and in your template
<ng-container *ngIf="jellyDonut">
Jelly Donut
</ngcontainer>
<ng-container *ngIf="!jellyDonut">
Other stuff
</ngcontainer>

How does Express and React Routes work on initial GET request from browser?

I'm new to the react world and to the fullstack world as a whole but I've searched endlessly for an answer to the following and some guidance would be really appreciated.
I'm creating an app using React and Express. It requires authentication so I was planning on using Passport to help. The client side JS uses React Routers to navigate through the website. That's all fine but my issue is with the initial GET request made by the browser.
I'll first describe my specific app requirements and then generalize what I don't understand.
As I said, my application requires OAuth2 authentication. If you try to GET a path on my website and you're not logged in, it should just load the login page. If you are logged in, then load as normal and find your path. Similar to facebook, I'd like the login URL to be the same as the "feed" page. So similar to how facebook.com '/' route is either the login page or your new feed depending on whether you are signed in, I want the same thing.
From what I understand, Passport authenticates on the back end by checking the request header. So I understand that I should have some kind of middleware that says "if user is signed in, continue down the routes otherwise render sign in page" ... How is this done? What would the code look like? My only experience with Express was from an intro class which used res.render to send back an HTML file and pass it through some template engine like handlebars. But I have no idea how it'd work with react routes. Would i still use res.render()? Something else?
Let's say my index.html has the root div to inject the react into. If I had to guess, I'd send back that index.html page with the .js file with the routes and somehow on the backend send back the route I want it to match on my react routes (either the login one or the user requested)??
More generally, I guess I'm just confused how the initial request to a website using react routes is done. 1) How does the server interact with everything to render what I asked for? 2) What would the code look like for that. My only experience with React is from a basic Udemy course that just used "react-scripts start" to render the page.
After spending the entire day Googling this question it led me to SSR which is a rabbit-hole of its own and I'm not even sure if its what I need to help me. Is it?
I'm clearly missing some fundamental knowledge as this is really tripping me up so if you have any resources to learn more just post them. Thanks!
I understand your struggle as I've had to go through it myself when combining front-end with back-end, specifically React and Node. So first things first, we know that the browser/client will always initiate a request to the server, so how does React Router take control of the routes? Well its plain simple actually, all you have to do is return the entire react app from any route from your express server. The code will look something like this:
const express = require('express');
const app = express();
app.get('/*', (req, res, next) => {
// Return React App index.html
});
app.listen(3000);
Once the react app renders on the user browser (don't worry about paths, as react will automatically render according to the URL based on the code you wrote in the client side, it will also take care of authentication vs feed page when it will scan for your local storage, cookies, etc), it will take control of routing, instead of a request going to the express server. But what happens when we request data from our server, well it returns react app on each route so we need to setup an api route to handle any data requests.
app.get('/api/v1/*', (req, res, next) {
// Return some data in json format
});
Hopefully, this gives you insight about what you were looking for.
I think the fundamental gap you're struggling with stems from that lot of those 'intro courses' shove the entire browser client into the application server to get things up and running quickly, as in, the Node server renders the entire React app AND operates as an API...
// Ajax request from React app to: http://example.com/api
app.use('/api/*'),()=> {
res.send({ <!-- some JSON object -->})
})
// User visits in browser: http://example.com/**/*
app.use('/*',()=>{
res.render(<!-- entire React App sent to browser -->)
})
The first request (assuming the user doesn't visit /api/* ) will just send down the React bundle. Further user navigation within the client would generally send XHR requests (or open WebSockets) from the React app to Express routes running on the same node program.
In many situations it makes sense to have these parts of your program separated, as by having react delivered from a completely different location than where it requests data. There's many reasons for this, but optimizing computing resources to their differing demands of CPU, memory, network .etc and manageability of code/deployment are the big reasons for me.
For example...
User visits: http://example.com *
Nginx, Apache, a 'cloud proxy' .etc direct the traffic to a static React bundle, which has no authentication and never makes contact with your Node server.
If the user has Authenticate previously they will have token in local storage (if you're using JWTs for Authentication) and your React app will be configured to always check for these tokens when you first it is initially loaded.
If the user has a token it will send an Ajax request in the background with the token as a Header Bearer and will send back user data, then redirect them to an 'Authenticated page' like the FB feed you mention.
If they don't have a token or the token Authentication fails then React will redirect them to the Login or Registration page
React
React basically high jacks the browser's native 'location' functionality (whats displayed after you domain name). So any events after the initial page load (buttons clicks and such) are handled entirely by React internally and uses those routes to determine what to display or what data to fetch from the API through Ajax (XHR).
If the user performs a hard page reload then that request will go back to the server and it will perform the whole cycle over again
React Router
Allows you to do 2 things simultaneously...
Manipulate the browser Location and History objects.
Use that History and Location information elsewhere by detecting changes and sending off events.
SSR
I've only toyed around with SSR so I can speak to it, but its provides extremely low latency for initial renders, doing it in 1 network request, so you want to use it areas of your program where thats important.
Not sure if this answers you question, but let me know if you would like me to elaborate on anything or provide some more detailed resources.
SSR is a little bit confuses for developer that has less experience, let forget it for now.
It will be more easier for you to assume that frontend JavaScript (React) and backend Javascript (NodeJS) are two separate apps, and they communicate to each other via API.
here the code that show Login component and Feed component depending on whether you are signed in
import React, { Component } from "react";
import axios from "axios";
class Home extends Component {
constructor() {
const accessToken = localStorage.getItem("accessToken");
this.state = {
accessToken,
feeds: []
};
}
componentDidMount() {
if (this.state.accessToken) {
axios(`api/feeds?accessToken=${this.state.accessToken}`).then(({ data }) => {
this.setState({
feeds: data
});
});
}
}
render() {
if (this.state.accessToken) {
return <FeedsComponent feeds={this.state.feeds} />;
}
return <LoginComponent />;
}
}
and this is your backend
const express = require("express");
const app = express();
app.get('/api/feeds', (req, res, ) => {
const feeds = [
{},
{}
]
res.status(200).json(feeds);
});
app.listen(3001);
just keep in mind that they are two separate apps, the can be in two different folder, different server, different port.
Simply point Express to the folder containing your React files or build files.
app.use(express.static(__dirname + '/dist'));
where 'dist' contains the build files
See docs for more details

Disable vue-router for certain routes? Or how to run SPA backend, and non-SPA frontend?

so I'm developing a website/webapp in Laravel 5.3, and Vue 2. SEO is important, so I'm wanting to keep the frontend/crawable section of the site in Laravel + Blade, and only small non necessary sections in Vue 2.0, so I've not got to use Ajax to load the page content, and allowing crawlers like Google to crawl and index the site. (AFAIK, Google does load JS, but doesn't wait for Ajax to load, so it's hit/miss).
However, on the backend, I want to go fully SPA with Vue and VueRouter.
How do I best separate the two?
I want my backend to be accessible via /manager
My solution so far is to:
# routes.php
### Laravel/Frontend routes go here (before SPA) ###
Route::get('/manager/{spaPlage?}', ['middleware' => 'auth', function () {
return view('manager.index');
}])->where(['spaPlage' => '.*'])->name('manager.index');
and then in Vue, I use:
const routes = [
{ path: '/', name: 'dashboard.index', component: require('./pages/manager/Dashboard.vue') },
{ path: '/categories', name: 'categories.index', component: require('./pages/manager/categories/Index.vue') },
{ path: '/category/:id', name: 'category', component: require('./pages/manager/categories/Category.vue') }
];
const router = new VueRouter({
routes,
base: '/manager'
})
const app = new Vue({
el: '#app',
router
...
Which does work. However, it doesn't feel right. Because the view router still loads on my frontend (appends hash/hashbang).
So, is there a better way to separate my Laravel frontend with my Vue SPA backend?
Just to clarify some misconceptions for anyone in the future.
Google can crawl JS based front end websites without the need to pre-render or server-side render. With one caveat, Google uses Chrome 41 to crawl and render the site, so your javascript has to be polyfilled at least well enough to support Chrome 41 features (or at least your Vue-based data needs to be polyfilled to support Chrome 41). I haven't had any trouble just using babel thus far.
Laravel frontend with my Vue SPA backend
This is backwards. If you are doing this, don't. Laravel should be your backend & frontend (via blade + bootstrap + Vue/react), or your backend only. Vue.js is a frontend framework and shouldn't be used as a "backend." All your database queries, authentication, calculations, emailing, etc. should be handled by Laravel. Laravel is great for backend work and it's completely reasonable to code an entire API up with laravel and use Vue exclusively for front end. Sure you might be able to hack it together and make it somehow work, but you shouldn't, and you'd just be creating extra headache for yourself for no reason.
But to also answer the question, because there is a case to be made for a website that has only part of it's site as an SPA (maybe the blog is an SPA site, but is complete separate from the "about us", "contact us", etc like pages (for instance, you might be revamping a website in parts, and would like to roll out updated pages over time instead of tackling everything at once). In this case you can specify specific routes for Vue Router to run on, and all others will be handled by laravel.
Route::get('/DIR/{vue_capture?}', function () {
return view('vue.index');
})->where('vue_capture', '[\/\w\.-]*');
This would allow Vue Router to handle everything in the DIR sub directory, but allow other routes to coexist along side of it.
But if your application requires Vue Router to have access at the root domain, then just put that at the very end of your routes file, because when route matching is done, it quits after the first match. So by placing it at the end of your routes file, you are essentially using it as a "catch all" for any routes not specified above it. Think of it like returning early.
For anyone who comes across this, I wasn't able to find anything on disabling VueRouter for specific links. I don't think it's possible, also, it's probably not worth the hassle.
So I've decided to re-write all my codebase to be a SPA. It's about time, and there's no real reason not to. I'm using Prerender.io's self hosted service for pre-rendering for SEO, etc.

AngularJS oauth endpoint with hash in url (SpotifyAPI)

I want to receive the oauth callback from Spotify and have problems with the # in my URL.
routes.js
app.config(['$routeProvider',
function ($routeProvider) {
$routeProvider.
...
when('/callback', {
templateUrl: 'views/callback.html',
controller: 'CallbackCtrl'
})
...
}]);
So to access that route the URL is e. g. http://test.com/#/callback
For the spotify endpoint the redirect_uri has to be url-encoded:
https://accounts.spotify.com/authorize/?client_id=...&response_type=code&redirect_uri=http%3A%2F%2Ftest.com%2F%23%2Fcallback&scope=user-read-private%20user-read-email&state=profile%2Factivity
The redirect from spotify:
http://test.com/%23/callback?code=..&state=profile%2Factivity
Which results in a 404
I know there are some workarounds like using the / route or enabling html5mode to get rid of the # but I hope there is a true solution for this.
Well your situation is not that awkward at all.
Since you can't control the way that spotify's redirect after OAuth works, it leaves you with two options:
1) Create a URL Path for example http://test.com/redirect_uri/?code=.... which will automatically redirect the user to the webapp in the state of logged in.
This method is not a good practice, unless you really know exactly what you are doing. The major problem here is security. Unless you add really good Mechanism to the redirection page.
2) Easier, and actually better in all aspects:
$locationProvider.html5Mode(true);
A small introduction on this:
Why does this remove the #? Because when HTML5 mode is on, it will use history to refer to native paths in your app.
But hold on this is not the only thing you have to do:
You have to redirect all request to go through the main page normally index.html.
I use to do this with .htaccess, But since of AngularJS 1.3 I know there is another method with adding meta tag of <base href=/base/path/of/app/directory">. Usually <base href="/">
But I still prefer .htacces rewriterule or w/e webserver you are using accordingly.
It looks like you are going to implement the Authorization Code flow client-side, exposing the secret key you were provided when you register your app. This is wrong, since someone might generate tokens on behalf of your application using the client id and secret key.
A better approach is to either use Implicit Grant (in example how it is done on https://github.com/possan/webapi-player-example) or implement the token exchange server-side and pass the token to your AngularJS webapp.
Check out this article on Scotch.io for "pretty URL's" that should fix this issue.
You will have to set html5Mode to true:
// Remember to inject $locationProvider
$locationProvider.html5Mode(true);
Then, include a base in the of your html file:
<base href="/">
This will let you navigate the webpage using relative links.
The article I mentioned also goes into fallbacks for older browsers.

What's the AngularJS "way" of handling a CRUD resource

I am interested in moving a lot of my client's "logic" away from Rails routing to AngularJS. I have slight confusion in one topic and that is linking. Now, I do understand there's more than one way to handle this, but what is the common practice in the AngularJS community for handling URLs on handling CRUD for resources. Imagine in the case of an athlete we have a URL such as the following to list all athletes:
http://example.com/athletes
To view an individual athlete:
http://example.com/athletes/1
To edit an individual athlete:
http://example.com/athletes/1/edit
To create a new athlete:
http://example.com/athletes/new
In AngularJS, is it common practice to reroute to similar URLs to create/edit/update? Would you just have one URL handle all of the CRUD type actions in one interface and never change the URL? If you were to change the URL, does that get handled via ng-click and in the click event would you use the $location object to change URLs? I'd love to be able to read up on common practices such as these, but having a difficult time in finding more recent literature on it in an AngularJS context.
** NOTE **
I totally get that you can still use RESTful routes to the backend in order to interact with server-side resources. My question is, what is the style that is recommended to use when updating URLs on the client-side. Are you using AngularJS to do that for each of the CRUD operations?
I would definitely recommend separate URLs for each operation (to enable direct linking). The ones you suggest look fine.
In AngularJS you can use the $route service in combination with the ngView directive to load the appropriate template for each operation and handle the browser location and history mechanics for you.
Step 7 of the AngularJS tutorial gives an example of using Views, Routing and Templates the way I describe here. The following is a simplified version for your example:
Define the routes
In your main application script (e.g. app.js):
angular.module('AthletesApp', []).
config(['$routeProvider', function($routeProvider, $locationProvider) {
// Configure routes
$routeProvider.
when('/athletes', {templateUrl: 'partials/athletes-list.html', controller: AthleteListCtrl}).
when('/athletes/:athleteId', {templateUrl: 'partials/athlete-detail.html', controller: AthleteDetailCtrl}).
when('/athletes/:athleteId/edit', {templateUrl: 'partials/athlete-edit.html', controller: AthleteEditCtrl}).
when('/athletes/:athleteId/new', {templateUrl: 'partials/athlete-new.html', controller: AthleteNewCtrl}).
otherwise({redirectTo: '/athletes'});
// Enable 'HTML5 History API' mode for URLs.
// Note this requires URL Rewriting on the server-side. Leave this
// out to just use hash URLs `/#/athletes/1/edit`
$locationProvider.html5Mode(true);
}]);
We also enable 'HTML Mode' for URLs, see note below.
2. Add an ngView directive to your HTML
In your main index.html you specify where the selected partial template will go in the overall layout:
<!doctype html>
<html ng-app="AthletesApp">
...
<!-- Somewhere within the <body> tag: -->
<div ng-view></div>
...
</html>
3. Create templates and controllers
Then you create the partial view templates and matching controllers for each of the operations. E.g. for the athlete detail view:
partials/athelete-detail.html:
<div>
... Athete detail view here
</div>
athleteDetailCtrl.js:
angular.module('AthletesApp').controller('AtheleteDetailCtrl',
function($scope, $routeParams) {
$scope.athleteId = $routeParams.athleteId;
// Load the athlete (e.g. using $resource) and add it
// to the scope.
}
You get access to the route parameter (defined using :athleteId in the route config) via the $routeParams service.
4. Add links
The final step is to actually have links and buttons in your HTML to get to the different views. Just use standard HTML and specify the URL such as:
Edit
Note: Standard vs Hash URLs
In older browsers that don't support the HTML5 History API your URLs would look more like http://example.com/#/athletes and http://example.com/#/athletes/1.
The $location service (used automatically by $route) can handle this for you, so you get nice clean URLs in modern browsers and fallback to hash URLs in older browsers. You still specify your links as above and $location will handle rewriting them for older clients. The only additional requirement is that you configure URL Rewriting on the server side so that all URLs are rewritten to your app's main index.html. See the AngularJS $location Guide for more details.
The angular way is the restful way:
GET all http://example.com/athletes
GET one http://example.com/athletes/1
POST new http://example.com/athletes
PUT edit http://example.com/athletes/1
DELETE remove http://example.com/athletes/1
Note that $resource also expects a few other things, like resource URLs not ending with a slash, PUT requests returning the updated resource, etc.
If your API doesn't meet these criteria, or you simply need more flexibility, you can build your own $resource-like CRUD service based on the lower-level $http service. One way of doing the latter is explained here
Option 1: $http service
AngularJS provides the $http service that does exactly what you want: Sending AJAX requests to web services and receiving data from them, using JSON (which is perfectly for talking to REST services).
To give an example (taken from the AngularJS documentation and slightly adapted):
$http({ method: 'GET', url: '/foo' }).
success(function (data, status, headers, config) {
// ...
}).
error(function (data, status, headers, config) {
// ...
});
Option 2: $resource service
Please note that there is also another service in AngularJS, the $resource service which provides access to REST services in a more high-level fashion (example again taken from AngularJS documentation):
var Users = $resource('/user/:userId', { userId: '#id' });
var user = Users.get({ userId: 123 }, function () {
user.abc = true;
user.$save();
});
Option 3: Restangular
Moreover, there are also third-party solutions, such as Restangular. See its documentation on how to use it. Basically, it's way more declarative and abstracts more of the details away from you.
In AngularJS you can definitely use RESTful server side data sources, there is build in service called $resource.
Alternatively you can also use restangular which has additional features over $resource.
If you want to have full control you can always use $http service which is low level angular component for interacting with http.
Simply implement something that is RESTful, that is the angularJS way. If you have no idea what RESTful is or, know a little and want to know a lot more, then I would recommend that you read this article.
Basically, REST is what is understood to be, an intuitive implementation of WEB URIs, it also makes use of all HTTP verbs, their correct use actually. REST is an approach, and architecture to building web apps.

Categories

Resources