$ location / basculement entre html5 et mode hashbang / réécriture de lien

179

J'avais l'impression qu'Angular réécrirait les URL qui apparaissent dans les attributs href des balises d'ancrage dans les tempaltes, de manière à ce qu'elles fonctionnent en mode html5 ou en mode hashbang. La documentation du service de localisation semble indiquer que la réécriture de lien HTML prend en charge la situation de hachage. Je m'attendrais donc à ce que, lorsqu'ils ne sont pas en mode HTML5, des hachages soient insérés, et en mode HTML5, ils ne le seraient pas.

Cependant, il semble qu'aucune réécriture n'ait lieu. L'exemple suivant ne me permet pas de simplement changer de mode. Tous les liens de l'application doivent être réécrits à la main (ou dérivés d'une variable au moment de l'exécution. Suis-je obligé de réécrire manuellement toutes les URL en fonction du mode?

Je ne vois aucune réécriture d'url côté client en cours dans Angular 1.0.6, 1.1.4 ou 1.1.3. Il semble que toutes les valeurs href doivent être précédées de # / pour le mode hashbang et / pour le mode html5.

Une configuration est-elle nécessaire pour provoquer la réécriture? Est-ce que je lis mal les documents? Faire autre chose de stupide?

Voici un petit exemple:

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

Addendum: en relisant ma question, je vois que j'ai utilisé le terme «réécriture» sans beaucoup de clarté sur qui et quand je voulais faire la réécriture. La question est de savoir comment obtenir Angular pour réécrire les URL lors du rendu des chemins et comment lui faire interpréter les chemins dans le code JS de manière uniforme dans les deux modes. Il ne s'agit pas de savoir comment amener un serveur Web à effectuer une réécriture compatible HTML5 des requêtes.

Laurelnaiad
la source
1
Voici la solution pour Angular 1.6 .
Mistalis

Réponses:

361

La documentation n'est pas très claire sur le routage AngularJS. Il parle du mode Hashbang et HTML5. En fait, le routage AngularJS fonctionne selon trois modes:

  • Mode Hashbang
  • Mode HTML5
  • Hashbang en mode HTML5

Pour chaque mode, il existe une classe LocationUrl respective (LocationHashbangUrl, LocationUrl et LocationHashbangInHTML5Url).

Afin de simuler la réécriture d'URL, vous devez en fait définir html5mode sur true et décorer la classe $ sniffer comme suit:

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

Je vais maintenant expliquer cela plus en détail:

Mode Hashbang

Configuration:

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

C'est le cas lorsque vous devez utiliser des URL avec des hachages dans vos fichiers HTML comme dans

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

Dans le navigateur, vous devez utiliser le lien suivant: http://www.example.com/base/index.html#!/base/path

Comme vous pouvez le voir en mode Hashbang pur, tous les liens dans les fichiers HTML doivent commencer par la base telle que "index.html #!".

Mode HTML5

Configuration:

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

Vous devez définir la base dans un fichier HTML

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

Dans ce mode, vous pouvez utiliser des liens sans le # dans les fichiers HTML

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

Lien dans le navigateur:

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

Hashbang en mode HTML5

Ce mode est activé lorsque nous utilisons réellement le mode HTML5 mais dans un navigateur incompatible. Nous pouvons simuler ce mode dans un navigateur compatible en décorant le service $ sniffer et en définissant l'historique sur false.

Configuration:

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

Définissez la base dans un fichier HTML:

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

Dans ce cas, les liens peuvent également être écrits sans le hachage dans le fichier HTML

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

Lien dans le navigateur:

http://www.example.com/index.html#!/base/path
Jupiter
la source
Merci pour l'explication détaillée, @jupiter. Je vais voir si je peux sauter le coup et garder le hachage et tromper Angular en ne nécessitant pas deux ensembles d'URL en fonction du mode!
laurelnaiad
1
Je pense que je n'ai pas bien compris votre problème. Pourquoi n'utilisez-vous pas uniquement des URL sans hachage? Ils fonctionneront dans les navigateurs prenant en charge l'API d'historique et les navigateurs ne prenant pas en charge l'API d'historique. AngularJS mettra la version # dans la barre d'adresse lorsque vous cliquez dessus dans les navigateurs ne prenant pas en charge l'API d'historique car AngularJS intercepte les clics sur les liens.
Jupiter
Je travaille sur un framework qui devrait supporter les deux modes. Les auteurs d'applications devraient pouvoir choisir l'un ou l'autre sans se soucier de savoir s'il y a ou non des hachages dans leurs modèles et / ou des changements dans l'interprétation des chemins relatifs. J'espère que votre astuce aidera à faire en sorte que "tout ce que vous faites est de changer de mode" (même si la solution pratique est de "régler le mode sur html5 et ensuite mentir de façon angulaire sur les capacités du navigateur").
laurelnaiad
1
Je reçois $provideest indéfini?
Petrus Theron
1
@pate - vous devez injecter $ provide dans votre fonction de configuration lorsque vous configurez le décorateur.
laurelnaiad
8

Pour les futurs lecteurs, si vous utilisez Angular 1.6 , vous devez également modifier hashPrefix:

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

N'oubliez pas de définir la base dans votre HTML <head>:

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

Plus d'informations sur le changelog here.

Mistalis
la source
3
merci @Mistalis. votre réponse fonctionne bien. mais problème lorsque j'actualise la page après le routage, des erreurs de page non trouvée se produisent. s'il vous plaît aidez-moi ..
Ashish Mehta
@AshishMehta Bonjour Ashish. Je vous suggère de lire cette réponse , cela peut résoudre votre problème. Au revoir! :)
Mistalis
0

Cela m'a pris un certain temps à comprendre, c'est ainsi que je l'ai fait fonctionner - Routage ASP WebAPI angulaire sans le # pour le référencement

  1. ajouter à Index.html - base href = "/">
  2. Ajoutez $ locationProvider.html5Mode (true); à app.config

  3. J'avais besoin d'un certain contrôleur (qui était dans le contrôleur domestique) à ignorer pour le téléchargement d'images, j'ai donc ajouté cette règle à RouteConfig

         routes.MapRoute(
            name: "Default2",
            url: "Home/{*.}",
            defaults: new { controller = "Home", action = "SaveImage" }
        );
  4. Dans Global.asax, ajoutez ce qui suit - en vous assurant d'ignorer les chemins de téléchargement des API et des images, laissez-les fonctionner normalement, sinon redirigez tout le reste.

     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. Assurez-vous d'utiliser $ location.url ('/ XXX') et non window.location ... pour rediriger

  6. Référencez les fichiers CSS avec un chemin absolu

et pas

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

Note finale - cela m'a donné un contrôle total et je n'ai rien eu à faire sur la configuration Web.

J'espère que cela aide car cela m'a pris un certain temps à comprendre.

tfa
la source
0

Je voulais pouvoir accéder à mon application avec le mode HTML5 et un token fixe puis passer à la méthode hashbang (pour conserver le token afin que l'utilisateur puisse actualiser sa page).

URL pour accéder à mon application:

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

Ensuite, lorsque l'utilisateur charge la page:

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

Ensuite, lorsque l'utilisateur navigue:

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

Avec cela, je garde le jeton dans l'URL et conserve l'état lorsque l'utilisateur navigue. J'ai perdu un peu de visibilité de l'URL, mais il n'y a pas de moyen parfait de le faire.

Alors n'activez pas le mode HTML5, puis ajoutez ce contrôleur:

.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
la source