Загрузчик 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

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