Angular 2/4/5: establece el href base dinámicamente

131

Tenemos una aplicación empresarial que usa Angular 2 para el cliente. Cada uno de nuestros clientes tiene su propia URL única, por ejemplo, https://our.app.com/customer-oney https://our.app.com/customer-two. Actualmente podemos configurar <base href...>dinámicamente usando document.location. Así que el usuario toca https://our.app.com/customer-twoy <base href...>se prepara para /customer-two... ¡perfecto!

El problema es que si el usuario está, por ejemplo, en https://our.app.com/customer-two/another-pagey actualiza la página o intenta acceder a esa URL directamente, <base href...>se configura en /customer-two/another-pagey no se pueden encontrar los recursos.

Hemos intentado lo siguiente en vano:

<!doctype html>
<html>

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

...

Desafortunadamente, no tenemos ideas. Cualquier ayuda sería asombrosa.

afilada
fuente
1
¿Ha intentado algo con APP_BASE_HREF ? No estoy muy seguro de cómo se implementaría, pero parece ser algo que sería útil ...
Jarod Moser
Por supuesto, definitivamente depende de la versión del enrutador que esté utilizando actualmente. ¿Tiene la versión 3.0.0-alpha.x del enrutador?
Jarod Moser
Estoy usando "@ angular / router": "3.0.0-alpha.7"
sharpmachine
5
Angular 7 - noviembre de 2018. Esto sigue siendo un problema. las respuestas actuales proporcionan una solución parcial. hacer cosas dentro del módulo angular es demasiado tarde. a medida que se sirve su índice HTML, debe saber de dónde extraer los scripts. Como yo lo veo, solo hay 2 formas: usando asp \ php \ cualquiera para servir index.html con contenido dinámico basehref. o usando javascript nativo directamente en el archivo index.html para cambiar el contenido DOM antes de que llegue a angular. ambos son soluciones deficientes para un problema común. triste.
Stavm
1
¿Alguien puede responder a mi consulta también? stackoverflow.com/questions/53757931/…
Karan

Respuestas:

166

La respuesta marcada aquí no resolvió mi problema, posiblemente diferentes versiones angulares. Pude lograr el resultado deseado con el siguiente angular clicomando en terminal / shell:

ng build --base-href /myUrl/

ng build --bh /myUrl/ o ng build --prod --bh /myUrl/

Esto cambia el <base href="https://stackoverflow.com/">a solo <base href="https://stackoverflow.com/myUrl/">en la versión construida, que fue perfecta para nuestro cambio de entorno entre desarrollo y producción. La mejor parte fue que ninguna base de código requiere cambiar usando este método.


Para resumir, deje su index.htmlhref base como: <base href="https://stackoverflow.com/">luego ejecute ng build --bh ./con angular cli para convertirlo en una ruta relativa, o reemplace el ./con lo que necesite.


Actualizar:

Como el ejemplo anterior muestra cómo hacerlo desde la línea de comando, aquí se explica cómo agregarlo a su archivo de configuración angular.json.

Esto se utilizará para todos ng servingpara el desarrollo local.

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

Esta es la configuración específica para compilaciones de configuración como prod:

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

La documentación oficial angular-cli referente al uso.

Zze
fuente
35
Esta solución requiere una compilación por cliente, lo que probablemente no resuelva el problema del OP.
Maxime Morin
1
@MaximeMorin en mi experiencia hasta ahora haciendo esto, ./funciona para todas las ubicaciones. Entonces, en realidad, no creo que tengas que construir por cliente.
Zze
9
@Zze Mi experiencia con ./ha sido muy diferente. Funciona hasta que el usuario navega a una ruta y presiona el botón o las teclas de actualización del navegador. En ese momento, nuestras reescrituras manejan la llamada, sirven la página de índice, pero el navegador asume que esa ./es la ruta actual, no la carpeta raíz de la aplicación web. Resolvimos el problema del OP con un índice dinámico (.aspx, .php, .jsp, etc.) que establece el href base.
Maxime Morin
2
El uso --proddurante la compilación no cambiará su base hrefvalor, no debe hablar de ello aquí. --prodle dice angular-clique use el environment.prod.tsarchivo en lugar del archivo predeterminado environment.tsen su environmentscarpeta
Mattew Eon
1
@MattewEon Solo estaba demostrando que era compatible con la --prodbandera, ya que estaban hablando de implementación en el OP.
Zze
57

Esto es lo que terminamos haciendo.

Agregue esto a index.html. Debería ser lo primero en la <head>sección

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

Luego, en el app.module.tsarchivo, agregue { provide: APP_BASE_HREF, useValue: window['_app_base'] || '/' }a la lista de proveedores, así:

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 { }
afilada
fuente
Error TS7017: El elemento tiene implícitamente un tipo 'cualquier' porque el tipo 'Ventana' no tiene firma de índice. useValue: (window as any) ['_app_base'] || '/'ayuda
Erkan Buelbuel
Realmente no funciona para el último ng2, ¿hay alguna solución? (carga la ruta de los scripts incorrectos, luego la ruta de los scripts buenos, pero no en el móvil)
Amit
1
Agregándolo como una respuesta a continuación, ya que la longitud del contenido es demasiado larga para un comentario.
Krishnan
1
@Amit Por favor, vea mi respuesta a continuación para una solución fácil en el ng2 más nuevo.
Zze
3
¿Cómo resolvió el problema con otros archivos? Mi navegador está intentando cargar localhost: 8080 / main.bundle.js , pero no puede porque mi contextPath es diferente. (Debería obtener localhost: 8080 / hello / main.bundle.js. )
Sasa
33

Según su respuesta (@sharpmachine), pude refactorizarlo un poco más, para que no tengamos que piratear una función en 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 { }

Y ./shared/common-functions.util.tscontiene:

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
fuente
1
He implementado mi aplicación en la subcarpeta. Mi URL ahora se ve así: http://localhost:8080/subfolder/myapp/#/subfolder/myroute¿Así es la tuya? ¿Sabes cómo deshacerte de la subcarpeta después del # para que pueda ser http://localhost:8080/subfolder/myapp/#/myroute?
techguy2000
Oh. Creo que la base href solo es necesaria para LocationStrategy. En mi caso, estoy usando HashLocationStrategy. Así que ni siquiera necesito configurarlo. ¿Escribiste un código especial para manejar la actualización con LocationStrategy?
techguy2000
1
Sí, base hrefsolo es necesario para LocationStrategyAFAIK. En cuanto a escribir código especial para manejar la actualización con LocationStrategy, no estoy seguro de lo que quiere decir exactamente. ¿Estás preguntando qué pasa si mi "href base" cambia durante una actividad en mi aplicación? Entonces, sí, tuve que hacer algo sucio como window.location.href = '/' + newBaseHref;dónde se requería cambiarlo. No estoy muy orgulloso de eso. Además, para dar una actualización, me alejé de estas soluciones de pirateo en mi aplicación, ya que se estaba convirtiendo en un problema de diseño para mí. Los parámetros del enrutador funcionan perfectamente bien, así que me quedé con ellos.
Krishnan
1
¿Cómo te appRoutingProviderves, Krishnan? Veo que la respuesta aceptada se usó igual; tal vez solo hayas copiado el suyo providers, pero si no, ¿puedes darme una muestra o describir su propósito? La documentación solo importsmódulos de enrutamiento , no utiliza ningún proveedor relacionado con el enrutamiento (por lo que puedo ver)
The Red Pea
@Krishnan Esto es lo que tengo que hacer con nginx para que funcione:location /subfolder/myapp/ { try_files $uri /subfolder/myapp/index.html; } location /subfolder2/myapp/ { try_files $uri /subfolder2/myapp/index.html; }
techguy2000
24

Utilizo el directorio de trabajo actual ./cuando construyo varias aplicaciones desde el mismo dominio:

<base href="./">

En una nota al margen, utilizo .htaccesspara ayudar con mi enrutamiento en la recarga de la página:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.html [L]
Daerik
fuente
2
Muchas gracias por el .htaccessarchivo
Shahriar Siraj Snigdho
8
Esta respuesta es incorrecta, no funciona para rutas como / your-app / a / b / c. Si actualiza su página desde dicha ruta, Angular buscará el tiempo de ejecución, polyfills y archivos JS principales y archivos CSS de estilos en / your-app / a / b /. Obviamente, no están en esa ubicación, sino en / your-app /
toongeorges
2
@toongeorges Esta respuesta se escribió hace casi dos años usando un archivo de configuración que es detectado y ejecutado por el software Apache Web Server para manejar el enrutamiento en la recarga de la página. Es engañoso que diga que esta solución es incorrecta y no funciona, pero lo que quiere decir es que no pudo averiguar cómo hacer que funcione con su configuración.
Daerik
Pasé por alto la regla de reescritura de Apache y no sé lo suficiente sobre ella como para decir que esto no funcionaría en Apache, por lo que puedo estar equivocado y su respuesta puede ser correcta. Aunque, en cualquier caso, prefiero una solución que no requiera que modifique la configuración del servidor, ya que aquí se han dado múltiples respuestas, porque esto hace que la aplicación sea portátil en diferentes servidores.
toongeorges
2
No, @toongeorges tiene razón, cambiar a <base href="./">está mal y arruinará las rutas como /app/a/b/c, de hecho, lo acabo de probar. Prefiera configurar el href base usted mismo si se trata solo de una aplicación web o eche un vistazo a la forma angular de cambiar el href base en la implementación (aquí hay muchas respuestas que mencionan esto).
giovannipds
15

Simplificación de la respuesta existente por @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 { }

No tiene que especificar una etiqueta base en su index.html, si está proporcionando valor para el token opaco APP_BASE_HREF.

Karanvir Kang
fuente
10

Esto funciona bien para mí en el entorno de producción.

<base href="https://stackoverflow.com/" id="baseHref">
<script>
  (function() {
    document.getElementById('baseHref').href = '/' + window.location.pathname.split('/')[1] + "/";
  })();
</script>
Avikar
fuente
2
Esta solución tiene una gran limitación con la configuración de múltiples VD en IIS. Provoca la carga duplicada de fragmentos.
Karan
9

En angular4, también puede configurar baseHref en aplicaciones .angular-cli.json.

en .angular-cli.json

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

esto actualizará el href base en index.html a MY_APP_BASE_HREF_1

ng build --app myapp1
Vince
fuente
4
Esto también debe crear una compilación para cada aplicación.
Patrick Hillert
6

Si está utilizando Angular CLI 6, puede usar las opciones deployUrl / baseHref en angular.json (proyectos> xxx> arquitecto> compilación> configuraciones> producción). De esta manera, puede especificar fácilmente la baseUrl por proyecto.

Compruebe que su configuración sea correcta mirando el index.html de la aplicación construida.

Katlu
fuente
7
esto requiere una construcción diferente para cada entorno, con todas sus implicaciones.
Stavm
3

Complementos: si desea, por ejemplo: / users como base de la aplicación para enrutador y / public como base para el uso de activos

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

Tuve un problema similar, de hecho terminé probando la existencia de algún archivo dentro de mi web.

probePath sería la URL relativa al archivo que desea verificar (una especie de marcador si ahora se encuentra en la ubicación correcta), por ejemplo, '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
fuente
0

En package.json, establezca el indicador --base-href en la ruta relativa:

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

Hablando francamente, ¡tu caso me funciona bien!

Estoy usando 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
fuente
¿Funcionó esta solución para usted? Me enfrento al problema. ¿Puedes responder también a mi publicación? stackoverflow.com/questions/53757931/…
Karan
1
Evite el uso de document.write (): developers.google.com/web/updates/2016/08/…
Patrick Hillert
-6

Acabo de cambiar:

  <base href="/">

a esto:

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

no olvides terminar con /

Huy - Logarit
fuente