如何将后端渲染的参数传递给angular2 bootstrap方法

IT技术 javascript angular
2021-01-30 09:49:50

有没有办法将后端呈现的参数传递给 angular2 bootstrap 方法?我想使用BaseRequestOptions和后端提供的值为所有请求设置 http 标头我的main.ts文件看起来像这样:

import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from "./app.component.ts";

bootstrap(AppComponent);

我找到了如何将此参数传递给根组件(https://stackoverflow.com/a/35553650/3455681),但是当我触发bootstrap方法时我需要它......有任何想法吗?

编辑:

webpack.config.js 内容:

module.exports = {
  entry: {
    app: "./Scripts/app/main.ts"
  },

  output: {
    filename: "./Scripts/build/[name].js"
  },

  resolve: {
    extensions: ["", ".ts", ".js"]
  },

  module: {
    loaders: [
      {
        test: /\.ts$/,
        loader: 'ts-loader'
      }
    ]
  }
};
4个回答

更新2

Plunker 示例

更新AoT

为了与 AoT 合作,工厂关闭需要移出

function loadContext(context: ContextService) {
  return () => context.load();
}

@NgModule({
  ...
  providers: [ ..., ContextService, { provide: APP_INITIALIZER, useFactory: loadContext, deps: [ContextService], multi: true } ],

另见https://github.com/angular/angular/issues/11262

更新RC.6 和 2.0.0 最终示例

function configServiceFactory (config: ConfigService) {
  return () => config.load();
}

@NgModule({
    declarations: [AppComponent],
    imports: [BrowserModule,
        routes,
        FormsModule,
        HttpModule],
    providers: [AuthService,
        Title,
        appRoutingProviders,
        ConfigService,
        { provide: APP_INITIALIZER,
          useFactory: configServiceFactory
          deps: [ConfigService], 
          multi: true }
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

如果不需要等待初始化完成,也可以使用`class AppModule {}的构造函数:

class AppModule {
  constructor(/*inject required dependencies */) {...} 
}

提示(循环依赖)

例如,注入路由器会导致循环依赖。要解决此问题,请注入Injector并获取依赖项

this.myDep = injector.get(MyDependency);

而不是MyDependency直接注入,如:

@Injectable()
export class ConfigService {
  private router:Router;
  constructor(/*private router:Router*/ injector:Injector) {
    setTimeout(() => this.router = injector.get(Router));
  }
}

更新

这应该在 RC.5 中工作相同,但将提供者添加到providers: [...]根module而不是bootstrap(...)

(尚未测试自己)。

更新

这里解释了一种完全在 Angular 内部完成的有趣方法https://github.com/angular/angular/issues/9047#issuecomment-224075188

您可以使用APP_INITIALIZERwhich 将在应用程序初始化时执行函数,并在函数返回Promise时延迟它提供的内容。这意味着应用程序可以在没有太多延迟的情况下进行初始化,您还可以使用现有的服务和框架功能。

例如,假设您有一个多租户解决方案,其中站点信息依赖于为其提供服务的域名。这可以是 [name].letterpress.com 或与完整主机名匹配的自定义域。我们可以通过使用APP_INITIALIZER.

在引导程序中:

{provide: APP_INITIALIZER, useFactory: (sites:SitesService) => () => sites.load(), deps:[SitesService, HTTP_PROVIDERS], multi: true}),

站点.service.ts:

@Injectable()
export class SitesService {
  public current:Site;

  constructor(private http:Http, private config:Config) { }

  load():Promise<Site> {
    var url:string;
    var pos = location.hostname.lastIndexOf(this.config.rootDomain);
    var url = (pos === -1)
      ? this.config.apiEndpoint + '/sites?host=' + location.hostname
      : this.config.apiEndpoint + '/sites/' + location.hostname.substr(0, pos);
    var promise = this.http.get(url).map(res => res.json()).toPromise();
    promise.then(site => this.current = site);
    return promise;
  }

注意:config只是一个自定义配置类。rootDomain'.letterpress.com'用于此示例并允许诸如 aptaincodeman.letterpress.com.

任何组件和其他服务现在都可以Site注入它们并使用该.current属性,属性将是一个具体的填充对象,无需等待应用程序中的任何Promise。

这种方法似乎减少了启动延迟,如果您在等待大型 Angular 包加载,然后在引导程序开始之前等待另一个 http 请求,则启动延迟非常明显。

原来的

您可以使用 Angulars 依赖注入来传递它:

var headers = ... // get the headers from the server

bootstrap(AppComponent, [{provide: 'headers', useValue: headers})]);
class SomeComponentOrService {
   constructor(@Inject('headers') private headers) {}
}

BaseRequestOptions直接提供准备好

class MyRequestOptions extends BaseRequestOptions {
  constructor (private headers) {
    super();
  }
} 

var values = ... // get the headers from the server
var headers = new MyRequestOptions(values);

bootstrap(AppComponent, [{provide: BaseRequestOptions, useValue: headers})]);
优秀的解决方案,只是对像我这样的未来谷歌员工的一些说明:1)请注意,load()必须返回 a Promise,而不是Observable. .toPromise()如果您像我一样在服务中使用 Observables,请使用此处函数。2) 您可能想知道如何将值sites.load()检索到您的服务、组件等中。请注意,SitesService将其分配给this.current. 所以你只需要注入SitesService你的组件并检索它的current属性
2021-03-28 09:49:50
@GünterZöchbauer 谢谢,我按照你的建议尝试但得到同样的错误。但也许我也没有跟随。你能看一下我的问题吗:stackoverflow.com/questions/42998892/...
2021-04-05 09:49:50
使用 AoT,您需要移动() => sites.load()到一个函数(在类和装饰器之外),然后在提供程序中将其替换为该函数名称
2021-04-06 09:49:50
所以你想从 HTML 中读取 。您可以在服务器上添加一个脚本标记,将它们分配给某个全局变量<script> function() { window.headers = someJson; }()</script>不确定语法,我自己并没有太多使用 JS。这样你就不必解析了。
2021-04-08 09:49:50
好的解决方案但是,当我重新构建我的项目时,我收到错误:“错误中的错误遇到静态解析符号值。不支持函数调用。考虑使用对导出函数的引用替换函数或 lambda(位置 24:46 在原始 .ts 文件),解析符号 AppModule 在 .../src/app/app.module.ts”。我相信它指向 useFactory 中的 lambda 表达式。您如何将上述 lambda 转换为导出的函数?该函数是否只是充当包装器?
2021-04-08 09:49:50

在 Angular2 最终版本中,可以使用 APP_INITIALIZER 提供程序来实现您想要的。

我写了一个完整的例子:https : //gist.github.com/fernandohu/122e88c3bcd210bbe41c608c36306db9

要点示例是从 JSON 文件读取,但可以轻松更改为从 REST 端点读取。

你需要的,基本上是:

a) 在您现有的module文件中设置 APP_INITIALIZER:

import { APP_INITIALIZER } from '@angular/core';
import { BackendRequestClass } from './backend.request';
import { HttpModule } from '@angular/http';

...

@NgModule({
    imports: [
        ...
        HttpModule
    ],
    ...
    providers: [
        ...
        ...
        BackendRequestClass,
        { provide: APP_INITIALIZER, useFactory: (config: BackendRequestClass) => () => config.load(), deps: [BackendRequestClass], multi: true }
    ],
    ...
});

这些行将在您的应用程序启动之前从 BackendRequestClass 类调用 load() 方法。

如果要使用 angular2 内置库对后端进行 http 调用,请确保在“导入”部分设置“HttpModule”。

b) 创建一个类并将文件命名为“backend.request.ts”:

import { Inject, Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx';

@Injectable()
export class BackendRequestClass {

    private result: Object = null;

    constructor(private http: Http) {

    }

    public getResult() {
        return this.result;
    }

    public load() {
        return new Promise((resolve, reject) => {
            this.http.get('http://address/of/your/backend/endpoint').map( res => res.json() ).catch((error: any):any => {
                reject(false);
                return Observable.throw(error.json().error || 'Server error');
            }).subscribe( (callResult) => {
                this.result = callResult;
                resolve(true);
            });

        });
    }
}

c) 要读取后端调用的内容,您只需将 BackendRequestClass 注入您选择的任何类并调用 getResult()。例子:

import { BackendRequestClass } from './backend.request';

export class AnyClass {
    constructor(private backendRequest: BackendRequestClass) {
        // note that BackendRequestClass is injected into a private property of AnyClass
    }

    anyMethod() {
        this.backendRequest.getResult(); // This should return the data you want
    }
}

如果这能解决您的问题,请告诉我。

尝试在 angular4 中执行相同的步骤,但它不起作用,没有错误,没有数据显示在视图中
2021-03-17 09:49:50
在 Angular 2.3.0 中,我收到一个错误:“未处理的 Promise 拒绝:appInits[i] 不是函数;区域:<root>;任务:Promise.then;值:TypeError:appInits[i] 不是一个函数function(...) TypeError: appInits[i] 不是在 MyModuleInjector 的new ApplicationInitStatus 函数(在 <anonymous> ( localhost:8080/js/vendor.js:89:2 ), <anonymous>:3751:49 处求值)。 createInternal (/MyModule/module.ngfactory.js:454:36) -- 似乎 load 返回的 promise 也不能由 useFactory 函数返回。
2021-03-18 09:49:50
@IanT8 请注意,工厂函数不返回函数,它返回一个返回Promise的函数。这就是我的 appInits[i] 错误的原因。
2021-03-31 09:49:50
我得到了完全相同的错误。通过添加额外() =>的 useFactory 解决
2021-04-01 09:49:50

您可以创建并导出一个完成工作的函数,而不是让您的入口点调用 bootstrap 本身:

export function doBootstrap(data: any) {
    platformBrowserDynamic([{provide: Params, useValue: new Params(data)}])
        .bootstrapModule(AppModule)
        .catch(err => console.error(err));
}

你也可以把这个函数放在全局对象上,这取决于你的设置(webpack/SystemJS)。它也与 AOT 兼容。

这有一个额外的好处,可以延迟引导,当它有意义时。例如,当您在用户填写表单后通过 AJAX 调用检索此用户数据时。只需使用此数据调用导出的引导程序函数即可。

@Ajey 在任何可注入元素上注入参数
2021-03-17 09:49:50
那么如何在 AppModule 中访问这个传递的“数据”呢?
2021-03-23 09:49:50
就我而言,这是更好的选择。我想通过页面上的另一个事件手动启动应用程序的加载,这很好用
2021-04-01 09:49:50

唯一的方法是在定义提供者时提供这些值:

bootstrap(AppComponent, [
  provide(RequestOptions, { useFactory: () => {
    return new CustomRequestOptions(/* parameters here */);
  });
]);

然后你可以在你的CustomRequestOptions类中使用这些参数

export class AppRequestOptions extends BaseRequestOptions {
  constructor(parameters) {
    this.parameters = parameters;
  }
}

如果您从 AJAX 请求中获取这些参数,则需要以这种方式异步引导:

var appProviders = [ HTTP_PROVIDERS ]

var app = platform(BROWSER_PROVIDERS)
  .application([BROWSER_APP_PROVIDERS, appProviders]);

var http = app.injector.get(Http);
http.get('http://.../some path').flatMap((parameters) => {
  return app.bootstrap(appComponentType, [
    provide(RequestOptions, { useFactory: () => {
      return new CustomRequestOptions(/* parameters here */);
    }})
  ]);
}).toPromise();

看到这个问题:

编辑

由于您的数据在 HTML 中,因此您可以使用以下内容。

您可以导入一个函数并使用参数调用它。

这是引导您的应用程序的主module的示例:

import {bootstrap} from '...';
import {provide} from '...';
import {AppComponent} from '...';

export function main(params) {
  bootstrap(AppComponent, [
    provide(RequestOptions, { useFactory: () => {
      return new CustomRequestOptions(params);
    });
  ]);
}

然后你可以像这样从你的 HTML 主页导入它:

<script>
  var params = {"token": "@User.Token", "xxx": "@User.Yyy"};
  System.import('app/main').then((module) => {
    module.main(params);
  });
</script>

看到这个问题:Pass Constant Values to Angular from _layout.cshtml

有没有办法在不使用 SystemJS 的情况下运行它(我使用的是 webpack,入口点在 webpack.config 文件中定义)
2021-03-14 09:49:50
我不是 webpack 专家,但我可以尝试...你能添加 webpack.config 文件的内容吗?谢谢!
2021-03-17 09:49:50
但是如何将这些参数渲染到typescript文件中?或者我应该将此引导程序方法运行到页面上的内联脚本中吗?但是在使用 es6 导入时该怎么做呢?
2021-03-18 09:49:50
你所说的渲染究竟是什么意思?您是否从服务器生成主 HTML 文件/JS 文件?您是否执行 AJAX 请求来获取这些参数?
2021-03-23 09:49:50
我从服务器生成我的视图。我想我会像这样在后端呈现所有必要的参数:{"token": "@User.Token", "xxx": "@User.Yyy"}所以在呈现的 HTML 中,我将有{"token": "123abc456def", "xxx": "yyy"}. 我想以某种方式将这个渲染的 JSON 传递到 bootstrap 方法中,我在 .js 文件中有这个方法。
2021-03-23 09:49:50