Как передать параметры рендеринга из бэкэнда в метод начальной загрузки angular2

Есть ли способ передать аргументы, представленные на бэкэнде, в метод начальной загрузки angular2? Я хочу установить заголовок http для всех запросов, используя BaseRequestOptions со значением, предоставленным из бэкэнда. мой main.ts файл выглядит так:

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

bootstrap(AppComponent);

Я нашел, как передать эти аргументы корневому компоненту ( /questions/36784852/uglovyie-2-vhodnyih-parametra-v-kornevoj-direktive/36784861#36784861), но он мне нужен, когда я запускаю 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 ответа

Решение

Update2

Пример плунжера

обновить 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

Ты можешь использовать APP_INITIALIZER которая будет выполнять функцию при инициализации приложения и будет задерживать то, что оно предоставляет, если функция возвращает обещание. Это означает, что приложение может инициализироваться без особых задержек, и вы также можете использовать существующие сервисы и функции инфраструктуры.

Например, предположим, что у вас есть мультитенантное решение, в котором информация сайта основана на доменном имени, с которого он обслуживается. Это может быть [имя].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})]);

В финальной версии 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 }
    ],
    ...
});

Эти строки будут вызывать метод load() из класса BackendRequestClass до запуска вашего приложения.

Убедитесь, что вы установили "HttpModule" в разделе "импорт", если хотите делать http-вызовы к бэкэнду, используя встроенную библиотеку angular2.

б) Создайте класс и назовите файл "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
    }
}

Дайте мне знать, если это решит вашу проблему.

Вместо того, чтобы ваша точка входа вызывала самозагрузку, вы можете создать и экспортировать функцию, которая выполняет эту работу:

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

Вы также можете разместить эту функцию на глобальном объекте, в зависимости от вашей настройки (webpack/SystemJS). Это также является AOT-совместимым.

Это имеет дополнительное преимущество для отсрочки начальной загрузки, когда это имеет смысл. Например, когда вы извлекаете эти пользовательские данные в виде вызова AJAX после того, как пользователь заполняет форму. Просто вызовите экспортированную функцию начальной загрузки с этими данными.

Единственный способ сделать это - указать эти значения при определении поставщиков:

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>

Посмотрите этот вопрос: Передайте постоянные значения в Angular из _layout.cshtml.

Другие вопросы по тегам