【问题标题】:How to pass parameters rendered from backend to angular2 bootstrap method如何将从后端渲染的参数传递给angular2引导方法
【发布时间】:2016-10-03 08:28:28
【问题描述】:

有没有办法将后端呈现的参数传递给 angular2 引导方法?我想使用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'
      }
    ]
  }
};

【问题讨论】:

    标签: javascript angular


    【解决方案1】:

    更新2

    Plunker example

    更新 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: [...] 而不是 bootstrap(...)

    (尚未测试自己)。

    更新

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

    您可以使用APP_INITIALIZER,它会在 应用程序被初始化并在函数返回时延迟它提供的内容 一个承诺。这意味着应用程序可以在没有完全初始化的情况下进行 很多延迟,你也可以使用现有的服务和框架 功能。

    例如,假设您有一个多租户解决方案,其中 站点信息依赖于提供它的域名。这个可以 是 [name].letterpress.com 或在 完整的主机名。我们可以通过以下方式隐藏一个承诺背后的事实 使用APP_INITIALIZER

    在引导程序中:

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

    sites.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 属性,这将是一个具体的 填充对象,无需等待应用程序中的任何承诺。

    这种方法似乎减少了启动延迟,否则 如果您正在等待大型 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})]);
    

    【讨论】:

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

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

    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 中,您可以使用以下内容。

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

    以下是引导您的应用程序的主模块示例:

    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

    【讨论】:

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

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

    我写了一个带有完整示例的 Gist:https://gist.github.com/fernandohu/122e88c3bcd210bbe41c608c36306db9

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

    你需要的,基本上是:

    a) 在您现有的模块文件中设置 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 调用,请确保在“imports”部分设置“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
        }
    }
    

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

    【讨论】:

    • 在 Angular 2.3.0 中,我收到一个错误:“未处理的承诺拒绝:appInits[i] 不是函数;区域:;任务:Promise.then;值:TypeError : appInits[i] is not a function(...) TypeError: appInits[i] is not a function at new ApplicationInitStatus (eval at (localhost:8080/js/vendor.js:89:2), :3751:49) at MyModuleInjector.createInternal ( /MyModule/module.ngfactory.js:454:36) -- load 返回的 promise 似乎也不能由 useFactory 函数返回。
    • @IanT8 请注意,工厂函数不会返回函数,它会返回返回承诺的函数。这就是我的 appInits[i] 错误的原因。
    • 我得到了完全相同的错误。通过为 useFactory 添加额外的() =&gt; 解决
    • 尝试在 angular4 中执行相同的步骤,但它不起作用,没有错误,没有数据显示在视图上
    【解决方案4】:

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

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

    您也可以将此函数放在全局对象上,具体取决于您的设置(webpack/SystemJS)。它也与 AOT 兼容。

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

    【讨论】:

    • 那么如何在 AppModule 中访问这个传递的“数据”?
    • @Ajey 在任何可注入元素上注入参数
    • 就我而言,这是更好的选择。我想通过页面上的另一个事件手动启动应用程序的加载,效果很好
    猜你喜欢
    • 2012-03-15
    • 2011-11-11
    • 1970-01-01
    • 2020-02-20
    • 1970-01-01
    • 1970-01-01
    • 2018-11-18
    • 2015-06-02
    • 1970-01-01
    相关资源
    最近更新 更多