Angulaire 2/4/5 - Régler la base href dynamiquement

131

Nous avons une application d'entreprise qui utilise Angular 2 pour le client. Chacun de nos clients a sa propre URL unique, ex: https://our.app.com/customer-oneet https://our.app.com/customer-two. Actuellement, nous sommes en mesure de définir <base href...>dynamiquement l'utilisation document.location. Alors l'utilisateur frappe https://our.app.com/customer-twoet <base href...>se prépare à /customer-two... parfait!

Le problème est que si l'utilisateur est par exemple sur https://our.app.com/customer-two/another-pageet qu'il actualise la page ou essaie d'accéder directement à cette URL, le <base href...>paramètre est défini sur /customer-two/another-pageet les ressources sont introuvables.

Nous avons essayé ce qui suit en vain:

<!doctype html>
<html>

<head>
  <script type='text/javascript'>
    var base = document.location.pathname.split('/')[1];
    document.write('<base href="https://stackoverflow.com/' + base + '" />');
  </script>

...

Malheureusement, nous sommes à court d'idées. Toute aide serait incroyable.

sharpmachine
la source
1
Avez-vous tenté quelque chose avec APP_BASE_HREF ? Je ne sais pas trop comment cela serait mis en œuvre, mais cela semble être quelque chose qui serait utile ...
Jarod Moser
Bien sûr, cela dépend également de la version du routeur que vous utilisez actuellement. Êtes-vous sur la version 3.0.0-alpha.x du routeur?
Jarod Moser
J'utilise "@ angular / router": "3.0.0-alpha.7"
sharpmachine
5
Angular 7 - novembre 2018. c'est toujours un problème. les réponses actuelles fournissent une solution partielle. faire des choses à l'intérieur du module angulaire est trop tard. au fur et à mesure que votre index HTML est servi, il doit savoir d'où extraire les scripts. il n'y a que 2 façons dont je le vois - en utilisant asp \ php \ n'importe quoi pour servir index.html avec un contenu basehref dynamique. ou en utilisant du javascript natif directement sur le fichier index.html pour changer le contenu DOM avant qu'il ne devienne angulaire. les deux sont de mauvaises solutions à un problème commun. triste.
Stavm
1
Quelqu'un peut-il s'il vous plaît répondre à ma question également. stackoverflow.com/questions/53757931/…
Karan

Réponses:

166

La réponse marquée ici n'a pas résolu mon problème, peut-être différentes versions angulaires. J'ai pu obtenir le résultat souhaité avec la angular clicommande suivante dans le terminal / shell:

ng build --base-href /myUrl/

ng build --bh /myUrl/ ou ng build --prod --bh /myUrl/

Cela change la <base href="https://stackoverflow.com/">à <base href="https://stackoverflow.com/myUrl/">la version intégrée que ce qui était parfait pour notre changement dans l' environnement entre le développement et la production. La meilleure partie était qu'aucune base de code ne nécessite d'être modifiée à l'aide de cette méthode.


Pour résumer, laissez votre index.htmlhref de base comme: <base href="https://stackoverflow.com/">puis exécutez ng build --bh ./avec angular cli pour en faire un chemin relatif, ou remplacez le ./par ce dont vous avez besoin.


Mettre à jour:

Comme l'exemple ci-dessus montre comment le faire à partir de la ligne de commande, voici comment l'ajouter à votre fichier de configuration angular.json.

Cela sera utilisé pour tous ng servingpour le développement local

"architect": {
    "build": {
      "builder": "@angular-devkit/build-angular:browser",
      "options": {
        "baseHref": "/testurl/",

Ceci est la configuration spécifique pour les versions de configuration telles que prod:

"configurations": {
   "Prod": {
     "fileReplacements": [
        {
          "replace": src/environments/environment.ts",
          "with": src/environments/environment.prod.ts"
        }
      ],
      "baseHref": "./productionurl/",

La documentation officielle relative angular-cli à l'utilisation.

Zze
la source
35
Cette solution nécessite une compilation par client, ce qui ne résout probablement pas le problème de l'OP.
Maxime Morin
1
@MaximeMorin d'après mon expérience jusqu'à présent, cela ./fonctionne pour tous les emplacements. Donc en fait, je ne pense pas que vous deviez construire par client.
Zze
9
@Zze Mon expérience avec ./a été très différente. Cela fonctionne jusqu'à ce que l'utilisateur accède à un itinéraire et appuie sur le bouton ou les touches d'actualisation du navigateur. À ce stade, nos réécritures gèrent l'appel, servent la page d'index, mais le navigateur suppose qu'il ./s'agit de l'itinéraire actuel et non du dossier racine de l'application Web. Nous avons résolu le problème de l'OP avec un index dynamique (.aspx, .php, .jsp, etc.) qui définit le href de base.
Maxime Morin
2
L'utilisation --prodpendant la construction ne changera pas votre base hrefvaleur, vous ne devriez pas en parler ici. --proddit angular-clid'utiliser le environment.prod.tsfichier au lieu du fichier par défaut environment.tsdans votre environmentsdossier
Mattew Eon
1
@MattewEon Il s'agissait simplement de démontrer qu'il était compatible avec le --proddrapeau puisqu'ils parlaient de déploiement dans l'OP.
Zze
57

Voici ce que nous avons fini par faire.

Ajoutez ceci à index.html. Ce devrait être la première chose de la <head>section

<base href="https://stackoverflow.com/">
<script>
  (function() {
    window['_app_base'] = '/' + window.location.pathname.split('/')[1];
  })();
</script>

Puis dans le app.module.tsfichier, ajoutez { provide: APP_BASE_HREF, useValue: window['_app_base'] || '/' }à la liste des fournisseurs, comme ceci:

import { NgModule, enableProdMode, provide } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { APP_BASE_HREF, Location } from '@angular/common';


import { AppComponent, routing, appRoutingProviders, environment } from './';

if (environment.production) {
  enableProdMode();
}

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    HttpModule,
    routing],
  bootstrap: [AppComponent],
  providers: [
    appRoutingProviders,
    { provide: APP_BASE_HREF, useValue: window['_app_base'] || '/' },
  ]
})
export class AppModule { }
sharpmachine
la source
Erreur TS7017: L'élément a implicitement un type «any» car le type «Window» n'a pas de signature d'index. useValue: (window as any) ['_app_base'] || '/'aide
Erkan Buelbuel
Ne fonctionne pas vraiment pour le dernier ng2, existe-t-il une solution? (charge le mauvais chemin des scripts, puis le bon chemin des scripts, mais pas sur mobile)
Amit
1
L'ajouter comme réponse ci-dessous, car la longueur du contenu est trop longue pour un commentaire.
Krishnan
1
@Amit Veuillez voir ma réponse ci-dessous pour une solution facile dans le dernier ng2.
Zze
3
Comment avez-vous résolu le problème avec d'autres fichiers. Mon navigateur essaie de charger localhost: 8080 / main.bundle.js , mais il ne peut pas car mon contextPath est différent? (Il devrait récupérer localhost: 8080 / hello / main.bundle.js. )
Sasa
33

Sur la base de votre réponse (@sharpmachine), j'ai pu la refactoriser un peu plus, afin que nous n'ayons pas à pirater une fonction index.html.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { APP_BASE_HREF, Location } from '@angular/common';

import { AppComponent } from './';
import { getBaseLocation } from './shared/common-functions.util';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    HttpModule,
  ],
  bootstrap: [AppComponent],
  providers: [
    appRoutingProviders,
    {
        provide: APP_BASE_HREF,
        useFactory: getBaseLocation
    },
  ]
})
export class AppModule { }

Et, ./shared/common-functions.util.tscontient:

export function getBaseLocation() {
    let paths: string[] = location.pathname.split('/').splice(1, 1);
    let basePath: string = (paths && paths[0]) || 'my-account'; // Default: my-account
    return '/' + basePath;
}
Krishnan
la source
1
J'ai mon application déployée dans le sous-dossier. Mon URL ressemble maintenant à ceci: http://localhost:8080/subfolder/myapp/#/subfolder/myrouteest-ce à quoi ressemble la vôtre? Savez-vous comment vous débarrasser du sous-dossier après le # pour qu'il puisse l'être http://localhost:8080/subfolder/myapp/#/myroute?
techguy2000
Oh. Je pense que la base href n'est requise que pour LocationStrategy. Dans mon cas, j'utilise HashLocationStrategy. Donc je n'ai même pas besoin de le régler. Avez-vous écrit un code spécial pour gérer l'actualisation avec LocationStrategy?
techguy2000
1
Oui, base hrefn'est requis que pour LocationStrategyAFAIK. En ce qui concerne l'écriture de code spécial pour gérer l'actualisation avec LocationStrategy, vous ne savez pas ce que vous voulez dire exactement. Demandez-vous, que se passe-t-il si mon "href de base" change pendant une activité dans mon application? Ensuite, oui, j'ai dû faire quelque chose de sale comme window.location.href = '/' + newBaseHref;là où il fallait le changer. Je n'en suis pas trop fier. Aussi, pour faire une mise à jour, je me suis éloigné de ces solutions de piratage dans mon application car cela devenait un problème de conception pour moi. Les paramètres du routeur fonctionnent parfaitement bien, je me suis donc tenu à cela.
Krishnan
1
À quoi as-tu l' appRoutingProviderair, Krishnan? Je vois que la réponse acceptée est la même; peut-être que vous venez de copier le sien providers, mais sinon, pouvez-vous me donner un échantillon ou décrire son objectif? La documentation ne fait que importsdes modules de routage , elle n'utilise aucun fournisseur lié au routage (pour autant que je sache)
The Red Pea
@Krishnan Voici ce que j'ai à faire avec nginx pour le faire fonctionner:location /subfolder/myapp/ { try_files $uri /subfolder/myapp/index.html; } location /subfolder2/myapp/ { try_files $uri /subfolder2/myapp/index.html; }
techguy2000
24

J'utilise le répertoire de travail actuel ./lors de la création de plusieurs applications sur le même domaine:

<base href="./">

Sur une note latérale, j'utilise .htaccesspour aider à mon routage lors du rechargement de la page:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.html [L]
Daerik
la source
2
Merci beaucoup pour le .htaccessfichier
Shahriar Siraj Snigdho
8
Cette réponse est incorrecte, elle ne fonctionne pas pour les routes telles que / your-app / a / b / c. Si vous actualisez votre page à partir d'une telle route, Angular recherchera le runtime, les polyfills et les fichiers JS principaux et le fichier CSS de styles sous / your-app / a / b /. Ils ne sont évidemment pas à cet endroit, mais sous / your-app /
toongeorges
2
@toongeorges Cette réponse a été écrite il y a près de deux ans à l'aide d'un fichier de configuration qui est détecté et exécuté par le logiciel Apache Web Server pour gérer le routage lors du rechargement de la page. Dire que cette solution est incorrecte et ne fonctionne pas est trompeur, mais ce que vous voulez dire, c'est que vous ne pouvez pas comprendre comment la faire fonctionner avec votre configuration.
Daerik
J'ai oublié la règle de réécriture Apache et je n'en sais pas assez pour dire que cela ne fonctionnerait pas sur Apache, donc je me trompe peut-être et votre réponse est peut-être correcte. Cependant, dans tous les cas, je préfère une solution qui ne vous oblige pas à modifier la configuration du serveur car plusieurs réponses ont été données ici, car cela rend l'application portable sur différents serveurs.
toongeorges
2
Non, @toongeorges a raison, changer en <base href="./">est faux et va se foutre avec des itinéraires comme en /app/a/b/ceffet, je viens de le tester. Préférez définir vous-même le href de base s'il s'agit uniquement d'une application Web ou jetez un œil à la manière angulaire de changer le href de base lors du déploiement (il y a beaucoup de réponses ici mentionnant cela).
giovannipds
15

Simplification de la réponse existante par @sharpmachine .

import { APP_BASE_HREF } from '@angular/common';
import { NgModule } from '@angular/core';

@NgModule({
    providers: [
        {
            provide: APP_BASE_HREF,
            useValue: '/' + (window.location.pathname.split('/')[1] || '')
        }
    ]
})

export class AppModule { }

Vous n'êtes pas obligé de spécifier une balise de base dans votre index.html, si vous fournissez une valeur pour le jeton opaque APP_BASE_HREF.

Karanvir Kang
la source
10

Cela fonctionne bien pour moi dans un environnement de production

<base href="https://stackoverflow.com/" id="baseHref">
<script>
  (function() {
    document.getElementById('baseHref').href = '/' + window.location.pathname.split('/')[1] + "/";
  })();
</script>
Avikar
la source
2
Cette solution a une grande limitation avec la configuration de plusieurs VD dans IIS. Cela provoque le chargement en double des morceaux.
Karan
9

Dans angular4, vous pouvez également configurer baseHref dans les applications .angular-cli.json.

dans .angular-cli.json

{   ....
 "apps": [
        {
            "name": "myapp1"
            "baseHref" : "MY_APP_BASE_HREF_1"
         },
        {
            "name": "myapp2"
            "baseHref" : "MY_APP_BASE_HREF_2"
         },
    ],
}

cela mettra à jour le href de base dans index.html en MY_APP_BASE_HREF_1

ng build --app myapp1
Vince
la source
4
Cela nécessite également de créer une version pour chaque application.
Patrick Hillert
6

Si vous utilisez Angular CLI 6, vous pouvez utiliser les options deployUrl / baseHref dans angular.json (projets> xxx> architect> build> configurations> production). De cette façon, vous pouvez facilement spécifier la baseUrl par projet.

Vérifiez que vos paramètres sont corrects en consultant l'index.html de l'application créée.

Katlu
la source
7
cela nécessite une construction différente pour chaque environnement, avec toutes ses implications.
Stavm
3

Add-ons: Si vous voulez par exemple: / users comme base d'application pour le routeur et / public comme base pour l'utilisation des actifs

$ ng build -prod --base-href /users --deploy-url /public
3rdi
la source
1

Eu un problème similaire, en fait, j'ai fini par sonder l'existence d'un fichier dans mon site Web.

probePath serait l'URL relative du fichier que vous souhaitez vérifier (sorte de marqueur si vous êtes maintenant au bon emplacement), par exemple 'assets / images / thisImageAlwaysExists.png'

<script type='text/javascript'>
  function configExists(url) {
    var req = new XMLHttpRequest();
    req.open('GET', url, false); 
    req.send();
    return req.status==200;
  }

  var probePath = '...(some file that must exist)...';
  var origin = document.location.origin;
  var pathSegments = document.location.pathname.split('/');

  var basePath = '/'
  var configFound = false;
  for (var i = 0; i < pathSegments.length; i++) {
    var segment = pathSegments[i];
    if (segment.length > 0) {
      basePath = basePath + segment + '/';
    }
    var fullPath = origin + basePath + probePath;
    configFound = configExists(fullPath);
    if (configFound) {
      break;
    }
  }

  document.write("<base href='" + (configFound ? basePath : '/') + "' />");
</script>
Ronald Korze
la source
0

Dans package.json, définissez l'indicateur --base-href sur le chemin relatif:

"script": {
    "build": "ng build --base-href ./"
}
Jarek Wichrowski
la source
-1

Franchement, votre cas fonctionne bien pour moi!

J'utilise Angular 5.

<script type='text/javascript'>
    var base = window.location.href.substring(0, window.location.href.toLowerCase().indexOf('index.aspx'))
    document.write('<base href="' + base + '" />');
</script>
Aviw
la source
Cette solution a-t-elle fonctionné pour vous? Je fais face au problème. Pouvez-vous également répondre à mon message. stackoverflow.com/questions/53757931/…
Karan
1
Veuillez éviter d'utiliser document.write (): développeurs.google.com
web/updates/2016/08/…
-6

J'ai juste changé:

  <base href="/">

pour ça:

  <base href="/something.html/">

n'oubliez pas de terminer par /

Huy - Logarit
la source