$ location / alternando entre o modo html5 e o modo hashbang / reescrita de link

179

Fiquei com a impressão de que o Angular reescreveria URLs que aparecem nos atributos href de tags âncora em tempaltes, para que funcionassem no modo html5 ou no hashbang. A documentação do serviço de localização parece indicar que a Reescrita de Link HTML cuida da situação do hashbang. Assim, eu esperaria que, quando não estivesse no modo HTML5, os hashes fossem inseridos e, no modo HTML5, não.

No entanto, parece que nenhuma reescrita está ocorrendo. O exemplo a seguir não me permite apenas alterar o modo. Todos os links no aplicativo precisariam ser reescritos manualmente (ou derivados de uma variável em tempo de execução. É necessário reescrever manualmente todos os URLs, dependendo do modo?

Não vejo nenhuma reescrita de URL do lado do cliente no Angular 1.0.6, 1.1.4 ou 1.1.3. Parece que todos os valores href precisam ser anexados com # / para o modo hashbang e / para o modo html5.

Existe alguma configuração necessária para causar a reescrita? Estou interpretando mal os documentos? Fazendo algo mais bobo?

Aqui está um pequeno exemplo:

<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>

Adendo: ao reler minha pergunta, vejo que usei o termo "reescrever" sem muita clareza sobre quem e quando eu queria reescrever. A questão é sobre como fazer com que o Angular reescreva os URLs quando renderizar caminhos e como interpretar os caminhos no código JS de maneira uniforme nos dois modos. É não sobre como fazer com que um servidor web para fazer HTML5 compatível com reescrita de pedidos.

laurelnaiad
fonte
1
Aqui está a solução para o Angular 1.6 .
Mistalis 7/03

Respostas:

361

A documentação não é muito clara sobre o roteamento AngularJS. Ele fala sobre os modos Hashbang e HTML5. De fato, o roteamento AngularJS opera em três modos:

  • Modo Hashbang
  • Modo HTML5
  • Hashbang no modo HTML5

Para cada modo, existe uma respectiva classe LocationUrl (LocationHashbangUrl, LocationUrl e LocationHashbangInHTML5Url).

Para simular a reescrita de URL, você deve realmente definir o html5mode como true e decorar a classe $ sniffer da seguinte maneira:

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

Agora vou explicar isso em mais detalhes:

Modo Hashbang

Configuração:

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

Este é o caso quando você precisa usar URLs com hashes em seus arquivos HTML, como em

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

No navegador, você deve usar o seguinte link: http://www.example.com/base/index.html#!/base/path

Como você pode ver no modo Hashbang puro, todos os links nos arquivos HTML devem começar com a base, como "index.html #!".

Modo HTML5

Configuração:

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

Você deve definir a base no arquivo HTML

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

Nesse modo, você pode usar links sem o # nos arquivos HTML

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

Link no navegador:

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

Hashbang no modo HTML5

Este modo é ativado quando realmente usamos o modo HTML5, mas em um navegador incompatível. Podemos simular esse modo em um navegador compatível decorando o serviço $ sniffer e configurando o histórico para false.

Configuração:

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

Defina a base no arquivo HTML:

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

Nesse caso, os links também podem ser gravados sem o hash no arquivo HTML

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

Link no navegador:

http://www.example.com/index.html#!/base/path
Júpiter
fonte
Obrigado pela explicação detalhada, @jupiter. Vou ver se consigo pular o estrondo e manter o hash e o truque Angular para não exigir dois conjuntos de URLs, dependendo do modo!
Laurelnaiad
1
Acho que não entendi direito o seu problema. Por que você não usa apenas URLs sem hash? Eles funcionarão em navegadores que suportam a API do histórico e em navegadores que não suportam a API do histórico. O AngularJS colocará a versão # na barra de localização quando você clicar neles em navegadores que não suportam a API do histórico, pois o AngularJS intercepta cliques em links.
Júpiter
Estou trabalhando em uma estrutura que deve suportar os dois modos. Os autores do aplicativo devem poder escolher um ou outro sem se preocupar se há ou não hashes em seus modelos e / ou alterações na interpretação de caminhos relativos. Espero que seu truque ajude a tornar verdade que "tudo o que você faz é mudar o modo" (mesmo que a solução prática seja "defina o modo como html5 e depois minta sobre os recursos do navegador").
Laurelnaiad 26/05
1
Estou entendendo $provide é indefinido?
Petrus Theron
1
@pate - você precisa injetar $ allow na sua função de configuração quando estiver configurando o decorador.
precisa saber é o seguinte
8

Para futuros leitores, se você estiver usando o Angular 1.6 , também precisará alterar o hashPrefix:

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

Não se esqueça de definir a base no seu HTML <head>:

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

Mais informações sobre o changelog here.

Mistalis
fonte
3
obrigado @Mistalis. sua resposta está funcionando bem. mas, quando eu atualizo a página após o roteamento, ocorre um erro de página não encontrada. por favor me ajude ..
Ashish Mehta 13/10
@AshishMehta Hello Ashish. Sugiro que você leia esta resposta , isso pode resolver seu problema. Tchau! :)
Mistalis 13/10
0

Demorei um pouco para descobrir, e foi assim que eu consegui funcionar - Angular WebAPI ASP Routing sem o # para SEO

  1. adicione ao Index.html - base href = "/">
  2. Adicione $ locationProvider.html5Mode (true); para app.config

  3. Eu precisava que um determinado controlador (que estava no controlador doméstico) fosse ignorado para fazer upload de imagens, então adicionei essa regra ao RouteConfig

         routes.MapRoute(
            name: "Default2",
            url: "Home/{*.}",
            defaults: new { controller = "Home", action = "SaveImage" }
        );
  4. No Global.asax, adicione o seguinte - certificando-se de ignorar a API e os caminhos de upload de imagens, permitam que eles funcionem normalmente, caso contrário, redirecione tudo o resto.

     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. Certifique-se de usar $ location.url ('/ XXX') e não window.location ... para redirecionar

  6. Faça referência aos arquivos CSS com caminho absoluto

e não

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

Nota final - fazer dessa maneira me deu controle total e eu não precisei fazer nada na configuração da web.

Espero que isso ajude, pois isso me levou um tempo para descobrir.

tfa
fonte
0

Queria poder acessar meu aplicativo com o modo HTML5 e um token fixo e depois mudar para o método hashbang (para manter o token para que o usuário possa atualizar sua página).

URL para acessar meu aplicativo:

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

Então, quando o usuário carregar a página:

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

Então, quando o usuário navega:

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

Com isso, mantenho o token na URL e mantenho o estado quando o usuário está navegando. Perdi um pouco de visibilidade do URL, mas não há uma maneira perfeita de fazê-lo.

Portanto, não ative o modo HTML5 e adicione 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
fonte