typescript - bootstrap angular2 con datos de llamadas ajax
(3)
Quiero iniciar mi aplicación con los datos que estoy recuperando de un servicio. Estoy haciendo algo en la línea de
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)
});
Esto funciona bien, pero no me gusta usar la matriz de dependencias dos veces, ¿hay alguna forma más limpia de hacerlo? ¿Puedo agregar cosas al inyector de la aplicación después de bootstrap? También noto que la función bootstrap devuelve una promesa, ¿puedo usar esta promesa para evitar el bootstrap de la aplicación hasta que finalice mi solicitud de ajax?
Por supuesto, para el
Injector
, podría usar solo las dependencias requeridas por
MyService
pero esto lo hace muy frágil, como se puede imaginar.
@Thierry (como siempre) ha respondido bien el corazón de su pregunta, pero creo que vale la pena señalarlo por separado:
¿Puedo agregar cosas al inyector de la aplicación después de bootstrap?
Sí, declarándolos en
providers
o
viewProviders
en los decoradores de los componentes que los requieren.
p.ej:
//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){ }
}
Tenga en cuenta que los
providers
se pueden usar tanto en directivas como en componentes (es una opción en
viewProviders
, desde la que se extiende
ComponentMetadata
), mientras que
viewProviders
solo está disponible en componentes por razones que son claras dada
la diferencia entre ellos
.
En mi humilde opinión, es una buena práctica inyectar dependencias de esta manera siempre que sea posible en lugar de hacerlo de forma
bootstrap
, ya que le permite limitar el alcance de disponibilidad de una dependencia dada a la parte de la aplicación (es decir, sub-árbol de componentes) donde desee Que esté disponible.
También es propicio para la carga progresiva y evita el olor a SoC de configurar innumerables inyectables no relacionados en un solo archivo de arranque.
El problema aquí es que Angular2 no le da acceso a la referencia de la aplicación y su inyector antes de iniciar el componente principal. Vea esta línea en el código fuente: https://github.com/angular/angular/blob/master/modules/angular2/platform/browser.ts#L110 .
Un enfoque podría ser implementar un bootstrap personalizado en lugar de usar el predeterminado. Algo así que divide la creación de la aplicación y el boostrapping en el componente de la aplicación. De esta manera, podrá cargar algo entre las dos tareas.
Aquí hay una implementación de muestra:
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();
}
y úsalo de esta manera:
customBoostrap(AppComponent, [
HTTP_PROVIDERS,
CompaniesService
]);
Las empresas estarán disponibles automáticamente para inyección dentro del componente, por ejemplo:
@Component({
(...)
})
export class AppComponent {
constructor(@Inject(''companies'') companies) {
console.log(companies);
}
}
Consulte este plunkr correspondiente: https://plnkr.co/edit/RbBrQ7KOMoFVNU2ZG5jM?p=preview .
En este momento, es un poco extraño, pero este enfoque podría proponerse como una solicitud de función ...
Editar
Después de echar un vistazo al documento de la clase
ApplicationRef
, vi que hay una solución más simple ;-)
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();
Aquí está el plunkr correspondiente: https://plnkr.co/edit/ooMNzEw2ptWrumwAX5zP?p=preview .
También puede usar el
APP_INITIALIZER
inyección
APP_INITIALIZER
y también puede hacer que llame a múltiples recursos asíncronos en paralelo:
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 { }
Fuente: Tutorial de Angular 4 - Ejecutar código durante la inicialización de la aplicación
Otro artículo interesante: enganche en el proceso de inicialización de Angular