Как передать параметры рендеринга из бэкэнда в метод начальной загрузки 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.