$ ubicación / cambio entre html5 y modo hashbang / reescritura de enlaces

179

Tenía la impresión de que Angular reescribiría las URL que aparecen en los atributos href de las etiquetas de anclaje dentro de las plantillas, de modo que funcionen ya sea en modo html5 o en modo hashbang. La documentación para el servicio de ubicación parece decir que HTML Link Rewriting se ocupa de la situación de hashbang. Por lo tanto, esperaría que cuando no esté en modo HTML5, se insertarán hashes, y en modo HTML5, no.

Sin embargo, parece que no se está reescribiendo. El siguiente ejemplo no me permite simplemente cambiar el modo. Todos los enlaces en la aplicación tendrían que reescribirse a mano (o derivarse de una variable en tiempo de ejecución. ¿Debo reescribir manualmente todas las URL dependiendo del modo?

No veo ninguna reescritura de URL del lado del cliente en Angular 1.0.6, 1.1.4 o 1.1.3. Parece que todos los valores href deben anteponerse con # / para el modo hashbang y / para el modo html5.

¿Hay alguna configuración necesaria para reescribir? ¿Estoy leyendo mal los documentos? ¿Haciendo algo más tonto?

Aquí hay un pequeño ejemplo:

<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="https://stackoverflow.com/about"/>about</a>'
                });
                $routeProvider.when('/about', {
                    template: 'this is about. go to <a href="https://stackoverflow.com/"/>home</a'
                });
            }
        ])
            .run();
    </script>
</body>

Anexo: al releer mi pregunta, veo que utilicé el término "reescribir" sin una gran claridad sobre quién y cuándo quería reescribir. La pregunta es acerca de cómo hacer que Angular reescriba las URL cuando renderiza rutas y cómo hacer que interprete rutas en el código JS de manera uniforme en los dos modos. No se trata de cómo hacer que un servidor web realice una reescritura de solicitudes compatible con HTML5.

laurelnaiad
fuente
1
Aquí está la solución para Angular 1.6 .
Mistalis

Respuestas:

361

La documentación no es muy clara sobre el enrutamiento AngularJS. Habla sobre Hashbang y modo HTML5. De hecho, el enrutamiento AngularJS opera en tres modos:

  • Modo hashbang
  • Modo HTML5
  • Hashbang en modo HTML5

Para cada modo hay una clase de LocationUrl respectiva (LocationHashbangUrl, LocationUrl y LocationHashbangInHTML5Url).

Para simular la reescritura de URL, debe establecer html5mode en verdadero y decorar la clase $ sniffer de la siguiente manera:

$provide.decorator('$sniffer', function($delegate) {
  $delegate.history = false;
  return $delegate;
});

Ahora explicaré esto con más detalle:

Modo hashbang

Configuración:

$routeProvider
  .when('/path', {
    templateUrl: 'path.html',
});
$locationProvider
  .html5Mode(false)
  .hashPrefix('!');

Este es el caso cuando necesita usar URL con hashes en sus archivos HTML, como en

<a href="index.html#!/path">link</a>

En el navegador debe usar el siguiente enlace: http://www.example.com/base/index.html#!/base/path

Como puede ver en modo Hashbang puro, todos los enlaces en los archivos HTML deben comenzar con la base como "index.html #!".

Modo HTML5

Configuración:

$routeProvider
  .when('/path', {
    templateUrl: 'path.html',
  });
$locationProvider
  .html5Mode(true);

Debe establecer la base en el archivo HTML

<html>
  <head>
    <base href="/">
  </head>
</html>

En este modo, puede usar enlaces sin el # en archivos HTML

<a href="/path">link</a>

Enlace en el navegador:

http://www.example.com/base/path

Hashbang en modo HTML5

Este modo se activa cuando realmente utilizamos el modo HTML5 pero en un navegador incompatible. Podemos simular este modo en un navegador compatible decorando el servicio $ sniffer y configurando el historial como falso.

Configuración:

$provide.decorator('$sniffer', function($delegate) {
  $delegate.history = false;
  return $delegate;
});
$routeProvider
  .when('/path', {
    templateUrl: 'path.html',
  });
$locationProvider
  .html5Mode(true)
  .hashPrefix('!');

Establecer la base en el archivo HTML:

<html>
  <head>
    <base href="/">
  </head>
</html>

En este caso, los enlaces también se pueden escribir sin el hash en el archivo HTML

<a href="/path">link</a>

Enlace en el navegador:

http://www.example.com/index.html#!/base/path
Júpiter
fuente
Gracias por la explicación detallada, @jupiter. ¡Veré si puedo omitir la explosión y mantener el hash y engañar a Angular para que no requiera dos conjuntos de URL dependiendo del modo!
laurelnaiad
1
Creo que no he entendido bien tu problema. ¿Por qué no usas solo URL sin un hash? Funcionarán en navegadores que admitan la API de historial y navegadores que no admitan la API de historial. AngularJS pondrá la versión # en la barra de ubicación cuando haga clic en ellos en los navegadores que no admitan la API de historial, ya que AngularJS intercepta los clics en los enlaces.
Júpiter
Estoy trabajando en un marco que debería admitir ambos modos. Los autores de aplicaciones deberían poder elegir uno u otro sin preocuparse de si hay o no hashes en sus plantillas y / o cambios en la interpretación de las rutas relativas. Espero que su truco ayude a hacer realidad que "todo lo que debe hacer es cambiar el modo" (incluso si la solución práctica es "configurar el modo en html5 y luego mentir sobre las capacidades del navegador").
laurelnaiad
1
Estoy obteniendo $providees indefinido?
Petrus Theron
1
@pate: debe inyectar $ provide en su función de configuración cuando configure el decorador.
laurelnaiad
8

Para futuros lectores, si está utilizando Angular 1.6 , también debe cambiar el hashPrefix:

appModule.config(['$locationProvider', function($locationProvider) {
    $locationProvider.html5Mode(true);
    $locationProvider.hashPrefix('');
}]);

No olvides establecer la base en tu HTML <head>:

<head>
    <base href="/">
    ...
</head>

Más información sobre el registro de cambios here.

Mistalis
fuente
3
gracias @Mistalis. Su respuesta funciona bien. pero se produce un problema cuando actualizo la página después del enrutamiento, se produce un error de página no encontrada. por favor ayúdame ..
Ashish Mehta
@AshishMehta Hola Ashish. Le sugiero que lea esta respuesta , puede resolver su problema. ¡Adiós! :)
Mistalis
0

Esto me llevó un tiempo comprender, así es como lo puse a trabajar: enrutamiento ASP angular WebAPI sin el # para SEO

  1. agregar a Index.html - base href = "/">
  2. Añadir $ locationProvider.html5Mode (verdadero); a app.config

  3. Necesitaba que se ignorara cierto controlador (que estaba en el controlador de inicio) para cargar imágenes, así que agregué esa regla a RouteConfig

         routes.MapRoute(
            name: "Default2",
            url: "Home/{*.}",
            defaults: new { controller = "Home", action = "SaveImage" }
        );
  4. En Global.asax, agregue lo siguiente: asegúrese de ignorar las rutas de carga de la API y la imagen para que funcionen normalmente, de lo contrario redirigirán todo lo demás.

     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);
    }
  5. Asegúrese de usar $ location.url ('/ XXX') y no window.location ... para redirigir

  6. Hacer referencia a los archivos CSS con ruta absoluta

y no

<link href="app/content/bootstrapwc.css" rel="stylesheet" />

Nota final: hacerlo de esta manera me dio el control total y no tuve que hacer nada en la configuración web.

Espero que esto ayude, ya que me llevó un tiempo entenderlo.

tfa
fuente
0

Quería poder acceder a mi aplicación con el modo HTML5 y un token fijo y luego cambiar al método hashbang (para mantener el token para que el usuario pueda actualizar su página).

URL para acceder a mi aplicación:

http://myapp.com/amazing_url?token=super_token

Luego, cuando el usuario carga la página:

http://myapp.com/amazing_url?token=super_token#/amazing_url

Luego, cuando el usuario navega:

http://myapp.com/amazing_url?token=super_token#/another_url

Con esto mantengo el token en la URL y mantengo el estado cuando el usuario está navegando. Perdí un poco de visibilidad de la URL, pero no hay una manera perfecta de hacerlo.

Por lo tanto, no habilite el modo HTML5 y luego agregue este controlador:

.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' })
Luc Boissaye
fuente