Загрузчик angular2 с данными вызова ajax
Я хочу загрузить приложение с данными, полученными из службы. Я делаю что-то вроде
let dependencies = [
//... a load of dependencies
MyService
];
let injector = Injector.resolveAndCreate(dependencies);
let service: MyService = injector.get(MyService);
service.getData() // returns observable
.toPromise()
.then((d) => {
// use data to append to dependencies
bootstrap(App, dependencies)
});
Это работает нормально, но мне не нравится использовать массив зависимостей дважды, есть ли более чистый способ сделать это? Можно ли добавить вещи в инжектор приложений после начальной загрузки? Также я заметил, что функция bootstrap возвращает обещание. Могу ли я использовать это обещание для предотвращения начальной загрузки приложения до тех пор, пока не завершится мой ajax-запрос?
Конечно для Injector
Я мог бы использовать только те зависимости, которые требуются MyService
но это делает его очень хрупким, как вы можете себе представить.
2 ответа
Проблема здесь в том, что Angular2 не дает вам доступа к ссылке на приложение и его инжектору до начальной загрузки основного компонента. См. Эту строку в исходном коде: https://github.com/angular/angular/blob/master/modules/angular2/platform/browser.ts#L110.
Подход может заключаться в том, чтобы реализовать собственный загрузчик вместо использования по умолчанию. Нечто подобное разделяет создание приложения и начальную загрузку компонента приложения. Таким образом, вы сможете загрузить что-то между двумя задачами.
Вот пример реализации:
function customBoostrap(appComponentType, customProviders) {
reflector.reflectionCapabilities = new ReflectionCapabilities();
let appProviders =
isPresent(customProviders) ? [BROWSER_APP_PROVIDERS, customProviders] : BROWSER_APP_PROVIDERS;
var app = platform(BROWSER_PROVIDERS).application(appProviders);
var service = app.injector.get(CompaniesService);
return service.getCompanies().flatMap((companies) => {
var companiesProvider = new Provider('companies', { useValue: data });
return app.bootstrap(appComponentType, [ companiesProvider ]);
}).toPromise();
}
и используйте это так:
customBoostrap(AppComponent, [
HTTP_PROVIDERS,
CompaniesService
]);
Компании будут автоматически доступны для внедрения в компоненте, например:
@Component({
(...)
})
export class AppComponent {
constructor(@Inject('companies') companies) {
console.log(companies);
}
}
Смотрите этот соответствующий plunkr: https://plnkr.co/edit/RbBrQ7KOMoFVNU2ZG5jM?p=preview.
В настоящее время это немного странно, но такой подход может быть предложен как запрос функции...
редактировать
После просмотра документа для ApplicationRef
класс, я увидел, что есть более простое решение;-)
var app = platform(BROWSER_PROVIDERS)
.application([BROWSER_APP_PROVIDERS, appProviders]);
service.getCompanies().flatMap((companies) => {
var companiesProvider = new Provider('companies', { useValue: data });
return app.bootstrap(appComponentType, [ companiesProvider ]);
}).toPromise();
Вот соответствующий план: https://plnkr.co/edit/ooMNzEw2ptWrumwAX5zP?p=preview.
@ Тьерри (как обычно) хорошо ответил на суть вашего вопроса, но я думаю, что это стоит отметить отдельно:
Можно ли добавить вещи в инжектор приложений после начальной загрузки?
Да, объявив их в providers
или же viewProviders
на декораторы компонентов, которые их требуют. например:
//main.ts
bootstrap(MyComponent) //no dependencies declared
//my.service.ts
@Injectable class MyService { public getMessage = () => "foobar" }
//my.component.ts
@Component({
selector: 'foo',
providers: [MyService]
template: `<div>{{mySvc.getMessage()}}</div>` //displays foobar
})
class MyComponent {
constructor(private mySvc: MyService){ }
}
Обратите внимание, что providers
может использоваться как в директивах, так и в компонентах (это опция DirectiveMetadata
из которого ComponentMetadata
расширяется), в то время как viewProviders
доступно только для компонентов по понятным причинам, учитывая разницу между ними.
ИМХО, лучше внедрять зависимости таким образом, где это возможно, вместо того, чтобы делать это bootstrap
, поскольку он позволяет вам ограничить область доступности данной зависимости той частью приложения (то есть поддеревом компонента), где вы хотите, чтобы она была доступна. Это также способствует прогрессивной загрузке и позволяет избежать запаха SoC при настройке множества несвязанных инъекций в одном файле начальной загрузки.
Вы также можете использовать APP_INITIALIZER
токен инъекции, и вы также можете использовать его для одновременного вызова нескольких асинхронных ресурсов:
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule } from "@angular/common/http";
import { AppLoadService } from './app-load.service';
export function init_app(appLoadService: AppLoadService) {
return () => appLoadService.initializeApp();
}
export function get_settings(appLoadService: AppLoadService) {
return () => appLoadService.getSettings();
}
@NgModule({
imports: [HttpClientModule],
providers: [
AppLoadService,
{ provide: APP_INITIALIZER, useFactory: init_app, deps: [AppLoadService], multi: true },
{ provide: APP_INITIALIZER, useFactory: get_settings, deps: [AppLoadService], multi: true }
]
})
export class AppLoadModule { }
Источник: Angular 4 Tutorial - Запуск кода во время инициализации приложения
Еще одна интересная статья: подключитесь к процессу инициализации Angular