Angular 2+ - 动态设置基本 href

174
我们有一个企业应用程序,客户端使用Angular 2。每个客户都有自己独特的URL,例如:https://our.app.com/customer-onehttps://our.app.com/customer-two。目前,我们能够使用document.location动态设置<base href...>。因此,当用户访问https://our.app.com/customer-two时,<base href...>会被设置为/customer-two......完美!
问题是,如果用户在例如https://our.app.com/customer-two/another-page处,并刷新页面或尝试直接访问该网址,则<base href...>会被设置为/customer-two/another-page,并且资源无法找到。
我们尝试了以下方法,但均无效:
<!doctype html>
<html>

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

...

很遗憾,我们已经没有更多的想法了。任何帮助都将是惊人的。


1
你尝试过使用APP_BASE_HREF吗?我不太确定它该如何实现,但它似乎是一个很有用的东西... - Jarod Moser
当然,这绝对取决于您当前使用的路由器版本。您是否在使用路由器版本3.0.0-alpha.x? - Jarod Moser
13
Angular 7 - 2018年11月。这仍然是一个问题。目前的答案只提供了部分解决方案。在Angular模块内进行操作已经太晚了。因为当您提供index HTML时,它需要知道从哪里拉取脚本。在我看来,只有两种方法-使用asp\php\whatever来使用动态basehref内容提供index.html。或者在index.html文件上直接使用本机JavaScript在DOM内容到达Angular之前更改DOM内容。这两种方法都是常见问题的不良解决方案。可惜。 - Stavm
1
有人可以回答我的问题吗?https://dev59.com/RFQJ5IYBdhLWcg3wfl6M - Karan
Angular 12 - 2021年9月,最新的Angular版本有什么新的解决方案吗? - ael
显示剩余4条评论
16个回答

214

这里标记的答案没有解决我的问题,可能是因为使用了不同的 Angular 版本。我通过以下终端/ shell 中的 angular cli 命令实现了所需的结果:

ng build --base-href /myUrl/

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

这将仅在构建版本中更改<base href="/"><base href="/myUrl/">,非常适合我们在开发和生产环境之间进行环境更改的需求。最好的部分是无需改变任何代码。


总结一下,在你的index.html文件中将基础路径设置为:<base href="/">,然后使用 angular cli 运行 ng build --bh ./ 将其转换为相对路径,或者用你需要的东西替换其中的./


更新:

正如上面的示例展示了如何通过命令行做到这一点,下面是如何将其添加到您的 angular.json 配置文件中。

这将用于所有本地开发的 ng serving

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

这是针对配置构建(例如生产环境)的特定配置:

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

官方angular-cli文档介绍如何使用。


72
这个解决方案需要为每个客户建立一个版本,这可能无法解决原帖作者的问题。 - Maxime Morin
1
根据我的经验,到目前为止使用“./”适用于所有位置。因此,我认为您不必为每个客户端构建。@MaximeMorin - Zze
14
我对 ./ 的使用经验与 @Zze 很不一样。它能够正常工作,直到用户导航到某个路由并按下浏览器的刷新按钮或键时。此时,我们的重写程序处理调用,提供索引页面,但浏览器假定 ./ 是当前路由而不是 Web 应用程序的根目录。我们通过一个动态 (.aspx、.php、.jsp 等) 索引页来解决 OP 的问题,该页会设置基础 href。 - Maxime Morin
4
在构建过程中使用 --prod 不会改变您的 base href 值,这里不需要讨论它。--prod 告诉 angular-cli 使用 environments 文件夹中的 environment.prod.ts 文件而不是默认文件 environment.ts - Mattew Eon
2
Angular 7.1:Unknown option: '--bh'--base-href 可以使用。 - Mir-Ismaili
显示剩余4条评论

74

这是我们最终做出的决定。

将以下内容添加到index.html中。它应该是<head>部分中的第一件事。

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

然后在app.module.ts文件中,将{ provide: APP_BASE_HREF, useValue: window['_app_base'] || '/' }添加到提供程序列表中,就像这样:

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

1
错误 TS7017:元素隐式具有“any”类型,因为类型“Window”没有索引签名。useValue: (window as any) ['_app_base'] || '/' 可以帮助解决问题。 - user1816491
1
在下面的答案中返回翻译文本:将以下与编程有关的内容从英语翻译为中文。 - Krishnan
1
@Amit,请看下面我给出的答案,这是在最新的ng2中简单解决的方法。 - Zze
12
你是如何解决其他文件的问题的?我的浏览器试图加载 http://localhost:8080/main.bundle.js,但是它无法加载因为我的 contextPath 不同。(它应该获取 http://localhost:8080/hello/main.bundle.js。) - Sasa
1
这在Angular 10中有效。但请确保从index.html中删除<base href="/">行,并删除在angular.json文件中设置base href的设置。 - Sameera K
显示剩余3条评论

43

根据你 (@sharpmachine) 的回答,我能进一步地对其进行重构,这样我们就不必在 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 { }

而且,./shared/common-functions.util.ts 包含:

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

1
我已经将myapp部署到子文件夹中。现在我的URL看起来像这样: http://localhost:8080/subfolder/myapp/#/subfolder/myroute 你的URL是不是也是这样的?你知道如何去掉#后面的子文件夹,使它变成http://localhost:8080/subfolder/myapp/#/myroute吗? - techguy2000
哦,我认为href base只在LocationStrategy中是必需的。在我的情况下,我正在使用HashLocationStrategy。所以我甚至不需要设置它。你是否编写了特殊代码来处理LocationStrategy的刷新? - techguy2000
1
是的,据我所知,base href 仅适用于 LocationStrategy。至于编写特殊代码以处理使用 LocationStrategy 进行刷新,我不确定您的意思。您是在问,如果我的应用程序中的“base href”在活动期间发生更改怎么办?那么,是的,我不得不做一些肮脏的事情,比如 window.location.href = '/' + newBaseHref;,在必要时进行更改。我对此并不感到自豪。另外,为了更新,我已经摆脱了这些 hack 解决方案,因为它对我来说成为了一个设计问题。路由器参数完全可以正常工作,所以我坚持使用它。 - Krishnan
1
Krishnan,你的 appRoutingProvider是什么样子的?我看到被接受的答案使用了相同的方法;也许你只是复制了他的providers,但如果不是这样的话,你能给我一个样例或描述它的目的吗?文档只**导入**路由模块,它没有使用任何与路由相关的提供者(据我所见) - Nate Anderson
不错的动态解决方案,但我在通过 http://my.app.com/app1(末尾没有 /)调用应用程序时遇到了困难。使用硬编码的 base-href 并调用 http://my.app.com/app1 会自动更改 URL 为 http://my.app.com/app1/ - xforfun
显示剩余4条评论

25

我在构建多个应用程序的相同域时,使用当前工作目录./

<base href="./">

顺便提一下,我使用.htaccess文件来帮助在页面重新加载时进行路由:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.html [L]

2
非常感谢您提供.htaccess文件。 - Shahriar Siraj Snigdho
13
这个回答是错误的,它不能适用于像/your-app/a/b/c这样的路由。如果你从这样的路由刷新页面,Angular将在/your-app/a/b/目录下查找运行时、polyfills和主JS文件以及样式CSS文件。很明显它们不在那个位置,而是在/your-app/目录下。 - toongeorges
2
@toongeorges 这个答案是在近两年前编写的,使用了一个配置文件,该文件被Apache Web服务器软件检测并执行以处理页面重新加载时的路由。你说这个解决方案不正确且无法工作是误导性的,但你的意思是你无法弄清楚如何将其与你的配置一起使用。 - Daerik
我忽略了Apache重写规则,并且不太了解它,无法确定这是否适用于Apache,因此我的答案可能是错误的,您提供的答案可能是正确的。尽管如此,我更喜欢不需要修改服务器配置的解决方案,因为在此已经给出多个答案,这将使应用程序可以在不同的服务器上移植。 - toongeorges
1
使用./会破坏到具有多级深度的地址的路由,例如http://localhost:4200/foo/bar - bvdb

20

根据这个回答,对其进行简化,作者是@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 { }

如果您提供APP_BASE_HREF不透明令牌的值,则无需在index.html中指定基本标记。


这非常类似(因此受到支持)于官方文档 https://angular.io/api/common/APP_BASE_HREF - Franklin Piat

14

在生产环境中,这对我来说运作良好。

<base href="/" id="baseHref">
<script>
  (function() {
    document.getElementById('baseHref').href = '/' + window.location.pathname.split('/')[1] + "/";
  })();
</script>

2
这个解决方案在IIS中设置多个VD时存在一个很大的限制,它会导致块的重复加载。 - Karan
使用硬编码的base-href并调用http://my.app.com/app1会将URL更改为http://my.app.com/app1/(末尾有/)。 这个解决方案对于http://my.app.com/app1也适用,因为它会更改为http://my.app.com/app1/ - 否则资源无法加载。 - xforfun

8
在Angular4中,您也可以在.angular-cli.json的apps字段中配置baseHref属性。
在.angular-cli.json文件中。
{   ....
 "apps": [
        {
            "name": "myapp1"
            "baseHref" : "MY_APP_BASE_HREF_1"
         },
        {
            "name": "myapp2"
            "baseHref" : "MY_APP_BASE_HREF_2"
         },
    ],
}

这将更新index.html中的base href为MY_APP_BASE_HREF_1。

ng build --app myapp1

11
需要为每个应用程序进行构建。 - Patrick Hillert

5
如果您正在使用Angular CLI 6,您可以在angular.json(projects> xxx> architect> build> configurations>production)中使用deployUrl/baseHref选项。这样,您可以轻松地为每个项目指定baseUrl。
通过查看构建应用程序的index.html,检查您的设置是否正确。

12
这需要针对每个环境进行不同的构建,其所包含的所有含义都要考虑在内。 - Stavm

5

我曾遇到类似的问题,最终发现是因为某个文件不存在导致的。

probePath是你想检查的文件的相对URL(如果你已经在正确的位置,这是一种标记),例如 '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>

这个解决方案是我部署场景中唯一真正有效的:完全动态的基础路径和PathLocationStrategy。所有其他解决方案在子路径上进行硬刷新时都无法工作。 - aPokeIntheEye

3

插件:如果你想将应用程序基础路径设置为/users,静态资源基础路径设置为/public,可以进行如下配置。

$ ng build -prod --base-href /users --deploy-url /public

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接