Ok. I have a url setup to log a user out. On the server, there is no html. The session on the server simply gets destroyed, and then the user is redirected to an address.
This works fine with plain html, but with Angular i am having issues. I've been routing all main routes using $routeProvider.when('/foo', {templateUrl: '/foo.html', controller: 'Ctrl'}) and that works fine for normal templated routes.. however, if there is no template it will not work.
So, how do i support the route /logout in the same fashion as above, when there is no html template?
A workaround is to use template instead of templateUrl. From the Angular docs:
template – {string=} – html template as a string that should be used
by ngView or ngInclude directives. this property takes precedence over
templateUrl.
This can be used as follows:
$routeProvider.when("/foo", {template: " ", controller: "Ctrl"});
Note: You must use " " instead of an empty string "" because Angular uses an if (template) check before firing the controller, and an empty string evaluates to false.
-- EDIT --
A better way to do it is to use the resolve map. See the Angular Docs:
resolve - {Object.=} - An optional map of
dependencies which should be injected into the controller.
This can be used like this:
$routeProvider.when('/foo', {resolve: {redirect: 'RedirectService'}});
Note: I've changed it from "Ctrl" to "RedirectService", because what you're describing in the question isn't really a "controller" in the Angular sense. It doesn't set up scope for a view. Instead, it's more like a service, which ends up redirecting.
I am writing the solution based on the already accepted answer and the github issue mentioned in it's comments.
The approach I am using is a resolve parameter in the $routeProvider. In my case I was trying to create a nice solution to logout in my application, when user goes to /logout.
Example code of $routeProvider:
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
...
when('/logout', {
resolve: {
logout: ['logoutService', function (logoutService) {
logoutService();
}]
},
}).
...
}]);
In the resolve part you specify a service (factory) by name and later on you have to call it. Still it is the nicest solution around.
To make the example complete I present my logoutService:
angular.module('xxx').factory('logoutService', function ($location, Auth) {
return function () {
Auth.setUser(undefined);
$location.path('/');
}
});
Works great!
Related
So I followed the Kontent doc from the github which allows to retrieve content from a link (https://github.com/Kentico/kontent-delivery-sdk-net/wiki/Resolving-links-to-content-items)
First I implement a resolver to redirect when we click on the link like this :
public class CustomContentLinkUrlResolver : IContentLinkUrlResolver
{
public string ResolveBrokenLinkUrl()
{
return "/404";
}
public string ResolveLinkUrl(ContentLink link)
{
switch(link.ContentTypeCodename)
{
case "author":
return $"/author/{link.UrlSlug}";
default:
return $"/not_found";
}
}
}
Then I register my resolver within a IDeliveryClient
client = DeliveryClientBuilder
.WithProjectId(myid)
.WithContentLinkUrlResolver(new CustomContentLinkUrlResolver())
.Build();
At this moment if i click on the link it will redirect to /author/linkName with an error on the page what I think is normal
I don't get the last part of the doc (how just by doing a getString on the contentItem the link will work ?) so I would like to know how to display the content on the redirect page
I don't know if i was clear enough and sorry for my english
Here is the error thrown on the redirect page
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
The last part of the wiki article refers to something that you already have:
At this moment if i click on the link
If you have a link that you can click on, then you have done what that part of the article describes.
What you need is to resolve the request. If you are getting a 404 that you expect, then you know that you need to add a route to your application to handle the request. In the handler (a controller, a component, etc.) extract the urlSlug from the route and use it with a IDeliveryClient to retrieve the item and then render the content. You will need to filter the GetItems call with something like new EqualsFilter("elements.urlSlug", urlSlug).
I'm trying to get the URL parameters into my component to search using an externsre service. I read I can use angular2 routes and get params using RouteParams but I don't think that's what I need because the first page of my angular application is a Razor view.
I'll try to explain better.
My URL looks like:
http://dev.local/listing/?city=melbourne
I'm loading my root component in a razor view with something like:
<house-list city = "#Request.Params["city"]"></house-list>
then on my root component I have:
export class AppComponent implements OnInit {
constructor(private _cityService: CityService) { }
response: any;
#Input() city: any;
ngOnInit() {
this.getHouses();
}
getHouses() {
this.__cityService.getHousesByCity(this.city)
.subscribe(data => this.response = data);
}
}
So I was expecting that the 'city' in my component gets the string passed from the razor view but it's undefined.
What am I doing wrong? Is there a way of getting the params without using the routing? If not, what is the right way of using razor?
Angular doesn't provide special support for this. You can use How can I get query string values in JavaScript?
From what I have seen Location is planned to be reworked to provide support for get query params even without using the router.
I have a grails app backing an angularjs front-end. They are deployed as a single WAR. I've removed the context path from the app so that it runs on http://localhost:8080.
I have a list of articles and I have the $routeProvider setup to redirect / to /articles at which point the controller takes over and pulls the list via $http. Pretty standard stuff.
Initially, I was using the default location provider config in that hashes (#) are used in the URL. I've changed it via
$locationProvider.html5Mode(true);
and everything still works. However, if I change the URL directly in the address bar and hit enter, or if I just refresh the browser when it is at /articles, the server side takes over and I just get my list of articles as json. No angular. I understand why this happens and for now what I've done is detected a non-ajax request on the server and am issuing a redirect to / which will allow angular to kick into gear.
I'm wondering if this is the right thing. Or is there something else I can do that is a better practice.
Redirecting is the right solution.
I was able to make it work using url mapping. So far it works :-)
I started with something like this:
"/**" (controller: 'app', action: 'index')
with app/index being the angular app page. But this will also match everything else (e.g. /$controller/$action). I had to explicitly map each $controller/$action to the correct controller. Not so good... ;-)
To solve this problem I'm prefixing all uris with /client for angular routes and /server for grails uris. This makes url mapping easy and it helps to distinguish angular routes from template uris etc.
My final url mapping looks like this:
class UrlMappings {
static excludes = [
"/lib/**",
"/css/**",
"/js/**"
]
static mappings = {
// - all client uris (routes) will start with '/client/',
// - all server uris (load) will start with '/server/'
// redirect /$appName/ to /$appName/client
"/" (controller: 'redirect', action: 'redirectTo') {
to = '/client/'
permanent = true
}
// redirect any angular route
"/client/**" (controller: 'app', action: 'index')
// standard controller/action mapping
"/server/$controller/$action/$id?" {
constraints {
}
}
}
}
You can't redirect directly in the url mapping, so I use a simple controller:
class RedirectController {
def redirectTo () {
redirect (uri: params.to, permanent: params.permanent)
}
}
The routing entries look like this:
$routeProvider.when ('/client/login', {templateUrl: './server/security/login'});
just answered on this question here
angularjs html5mode refresh page get 404
be sure that you rewrite rules on server work correctly
I'm planning to build a SPA with asp.net MVC4 but I'm not quite sure how I have to Setup my Project because of the Routing. Most SPA's work with hashrouting like this mypage/#/News/today for instance.
What would happen if the browses directly to mypage/News/today if I haven't specified a Controller named News with an action today?
The App should handle both types of Routing, how can I achieve this?
Do I have to build my App in a classic way, like Adding several Controllers with appropriate Actions and views and also build a clientside MVC structure with knockout, jquery etc?
You'll have to let all routes to "pages" fall through to let your SPA handle them (including essentially fake 404s if it's not to a real page in your SPA), but at the same time, need to make sure that you get the correct responses for API calls and/or file requests.
Below is the setup I have (I am using Vue as the js framework but that doesn't matter much for this, and not at all for the server-side piece).
First, add this to your Startup.cs, in addition to your default route setup:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.Use(async (context, next) =>
{
await next();
var path = context.Request.Path.Value;
// If there's no available file and the request doesn't contain an extension, we're probably trying to access a page
if (context.Response.StatusCode == (int)HttpStatusCode.NotFound && !Path.HasExtension(path) && !path.StartsWith("/api"))
{
context.Request.Path = "/Home/SpaRedirect"; // attempts to redirect to the URL within the SPA
context.Response.StatusCode = (int)HttpStatusCode.OK; // Make sure we update the status code, otherwise it returns 404
await next();
}
});
...
}
So the newly added SpaRedirect to HomeController looks like this, and just stores the requested URL in ViewData...
public IActionResult SpaRedirect()
{
ViewData["RequestUrl"] = HttpContext.Request.Path;
return View("Index");
}
Then in Index.cshtml, just capture that requested url in session storage so we have it available on the client-side:
<script src="~/dist/main.js" asp-append-version="true">
sessionStorage.setItem("redirectAttempt", #ViewData["RequestUrl"]);
</script>
Then in your boot script file (the entry-point for your SPA), add something like:
let redirectAttemptUrl = sessionStorage.getItem("redirectAttempt");
if (redirectAttemptUrl) {
router.push(redirectAttemptUrl);
sessionStorage.removeItem("redirectAttempt");
}
Which just checks for the presence of a requested url, and then the SPA's router attempts to navigate to it (in the example above it is a vue-router), then removes it from storage.
So this way, if a user attempts to navigate directly to a URL by entering it in the url bar (or via a bookmark) the app will load and take them to the right place, IF it exists... which takes us to the last piece...
Finally, you have to handle "404s" within your SPA, which is done by adding a catch-all route to your routes defs that takes user to a 404 component page you set up, which for Vue would look like this:
// adding an explicit 404 path as well for programmatically handling when something is not found within the app, i.e. return this.$router.push('/404')
{ path: '/404', component: NotFound, name: '404', alias: '*' }, // remove alias to not show the actual url that resulted in our little 404 here
{ path: '*', redirect: '/404' }, // this is the catch-all path to take us to our 404 page
Caveat: I'm no expert so could be missing something, would love additional comments on how to improve this. One thing that this doesn't handle is if the user is ALREADY in the SPA and for some reason edits the URL directly to navigate to someplace else, it would still trigger a server call and full reload, which ideally wouldn't be the case, but this is a pretty trivial issue I'd say.
i'm looking around for javascript routing libraries and i come to Sammy, so i'm learning it.
All examples that i've seen so far show hot to proceed routing based from a domain as a base url, like www.mydomain.com/# and then all routes goes on
but i'm doing some trials within a nested dir within my localhost dir, say /wwwroot/play/sammy/ so my base url would be
http://localhost/~rockdeveloper/play/sammy/#
and then all routes must go on, like:
http://localhost/~rockdeveloper/play/sammy/#/products
http://localhost/~rockdeveloper/play/sammy/#/clients
http://localhost/~rockdeveloper/play/sammy/#/search
is there any way to set this base url so i can proceed to config sammy routes like this ?
get('#/products')
get('#/clients')
get('#/search')
by now i have to concatenate the main string to the route, and i wish it would be more smart than this...
baseurl='/~rockdeveloper/play/sammy/#/search';
get(baseurl + '#/products');
thanks.
Sammy is a frontend framework, you don't need to provide a baseurl, since all calls are made at clientside on the base of url, the rest of the path after the pound sign are there just for sammy use.
Ex:
http://localhost/~rockdeveloper/play/sammy/#/products
http://localhost/~rockdeveloper/play/sammy/#/products/iphone
http://localhost/~rockdeveloper/play/sammy/#/products/iphone/cases/all
Your base path IS
http://localhost/~rockdeveloper/play/sammy/
In all of them. The rest are just sammy routes, (think of them as GET params)
Also your routes must be defined in a way that can be called seamlessly from your anchors. Here is a portion of my routes file:
app = $.sammy('body', function() {
//define events
this.bind('addNew',function(e,data){
//data.name = data.name.split(' ').join('_');
for(x in mapa[data.element]){
if(mapa[data.element][x].name.toLowerCase() == data.name.toLowerCase()){
return alert('There is already an element with the same name');
break;
}
}
ap('Adding: '+data.element+' with the name: '+data.name);
mapa[data.element].push({name:data.name});
this.trigger('sections',{action:data.element});
});
// define routes
this.get('#/', function() {
$('#menuright').html('');
$('.customMenu').remove();
$('#holder').html('').attr({style:''});
});
this.get('#/someroute/:variable', function() {
/*
...
...
...
*/
});
this.before(function(){
if(typeof(app.historial)=='undefined'){
app.historial = [];
}
app.historial.unshift(this.path.split('#')[1]);
if(app.historial.length>2) app.historial.length = 2;
do_something();
});
});
And from your views you can just simply use something like:
<i class="icon-bookmark"></i> {{name}}
//le me here using mustache :{