2

I want to manually bootstrap an Angular 4 app (created with CLI). In main.ts I am doing this:

const injector = ReflectiveInjector.resolveAndCreate([
  Http,
  BrowserXhr,
  {provide: RequestOptions, useClass: BaseRequestOptions},
  {provide: ResponseOptions, useClass: BaseResponseOptions},
  {provide: ConnectionBackend, useClass: XHRBackend},
  {provide: XSRFStrategy, useFactory: () => new CookieXSRFStrategy()},
]);
const http = injector.get(Http);

http.get('assets/configs/configuration.json')
  .map((res: Response) => {
    return res.json();
  }).subscribe((config: Configuration) => {
  configuration = config;
  console.log(JSON.stringify(configuration));
  platformBrowserDynamic().bootstrapModule(AppModule);
});

I seem to get a valid Http instance but when I use it (http.get) I get this error:

Uncaught TypeError: Cannot read property 'getCookie' of null
    at CookieXSRFStrategy.webpackJsonp.../../../http/@angular/http.es5.js.CookieXSRFStrategy.configureRequest (vendor.bundle.js:141626)

My http object looks like this: enter image description here

hholtij
  • 2,906
  • 6
  • 27
  • 42
  • You can do that with `http` provided by Angular. See [my answer](https://stackoverflow.com/a/45205562/2545680). I'm also curios where you use `ConfigurationService`, can you elaborate on that? – Max Koretskyi Jul 20 '17 at 05:03
  • Try this solution, it worked for me https://stackoverflow.com/questions/42461852/inject-a-service-manually – Andres Rincon Jun 29 '20 at 00:14

4 Answers4

6

You can use HttpClient service before the Angular starts using ReflectiveInjector like this:

import { ReflectiveInjector } from '@angular/core';
import { HttpClient, HttpClientModule } from '@angular/common/http';
const injector = ReflectiveInjector.resolveAndCreate(getAnnotations(HttpClientModule)[0].providers);

const http = injector.get(HttpClient);
http.get('/posts/1').subscribe((r) => {
  ConfigurationService.configuration = <Configuration>JSON.parse(config);
  platformBrowserDynamic().bootstrapModule(AppModule);
});

This line:

getAnnotations(HttpClientModule).providers

references all providers that are registered on the HttpClientModule so you don't have to specify them manually. This answer explains the getAnnotations function in great details.

The approach I've shown is "sort of" the similar to what you're doing when importing HttpClientModule into the AppModule:

@NgModule({
    imports: [HttpClientModule, ...],
})
export class AppModule {}

See this plunker for details.

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • Thanks, Maximus. This looks like the kind of code that might change with the next version. I think I'll stick with my solution for now. However, your submission answers my question and I'll mark it as accepted (although I haven't tried it). – hholtij Jul 20 '17 at 05:21
  • you're welcome, you can accept or upvote my answer if it helped. Also can you show how you use `ConfigurationService` – Max Koretskyi Jul 20 '17 at 05:22
  • @hholtij, thanks. There are only two pieces here that might be affected by the update `httpFactory` and `_createDefaultCookieXSRFStrategy` functions. It's theoretically possible to try to find a way to not use them, at least `_createDefaultCookieXSRFStrategy`. Everything else is publicly supported and if it changes then you'll have to update all code that uses `http` inside the Angular app as well. Also I'm still interested in `ConfigurationService`. Do you then use it as a provider somewhere? – Max Koretskyi Jul 20 '17 at 05:26
  • ConfigurationService used to be the service that read the config file. We are now using the NSwagStudio tool to auto-generate api services from the swagger definitions. It works very well but necessitates reading the config file before bootstrapping the app as it contains the api urls. Now, ConfigurationService is basically just a class with a static property. Might change that as well. – hholtij Jul 20 '17 at 05:34
  • I just tried your solution, Maximus. It doesn't work for me. I get the same error: Uncaught TypeError: Cannot read property 'getCookie' of null at CookieXSRFStrategy.webpackJsonp.../../../http/@angular/http.es5.js.CookieXSRFStrategy.configureRequest – hholtij Jul 20 '17 at 22:03
  • @hholtij, yeah, the previous `http` implementation depended on the some DOM related stuff which wasn't available before the Angular starts. The new `HttpClient` available in the `4.3.0` seems to not have that dependency and works fine for me. Just tested it and updated the answer. Check it – Max Koretskyi Jul 21 '17 at 06:27
  • I am using the latest 4.3.1 and there is no 'decorators' property on HttpClientModule. Your code does not work with 4.3.1. – hholtij Jul 24 '17 at 00:38
  • [this plunker](https://plnkr.co/edit/29XWrJd959gNrAzIM76N?p=preview) shows that everything is working fine on `4.3.1` – Max Koretskyi Jul 24 '17 at 06:46
  • @AngularInDepth.com `ReflectiveInjector` is being deprecated. How would you do this with `StaticInjector`? – c1moore Dec 04 '17 at 19:52
  • @c1moore, same way `new StaticInjector(getAnnotations(HttpClientModule)[0].providers)` – Max Koretskyi Dec 04 '17 at 19:55
  • @AngularInDepth.com I tried that with `Injector.create()`, which is what I think you meant by `new StaticInjector`, but it doesn't work (`Error: StaticInjectorError[HttpClient]: Function/Class not supported`). I can't find anything about `StaticInjector` in the actual documentation. – c1moore Dec 04 '17 at 20:09
  • @c1moore, why are you trying this? – Max Koretskyi Dec 04 '17 at 20:16
  • @AngularInDepth.com This might be a conversation better suited for [chat](https://chat.stackoverflow.com/rooms/160461/inject-httpclient-with-staticinjector). – c1moore Dec 04 '17 at 20:28
  • @c1moore, I don't have time now, maybe post a new question – Max Koretskyi Dec 04 '17 at 20:31
  • The answer was actually quite simple. There really was no need to use `HttpClient` at all. I eventually just created a wrapper around it since all async operations are caught by Angular (via Zones). – c1moore Mar 19 '18 at 22:33
  • @AngularInDepth.com I am using the same code with static injector it is saying `StaticInjectorError[HttpClient]: Function/Class not supported` same as @c1moore ? and @c1moore did you figure that stuff out ? – fastAsTortoise May 01 '18 at 14:08
  • @fastAsTortoise Yes, I just used `XMLHttpRequest` directly. Angular Zones will catch the async request and everything will work automagically. I did create a simple wrapper around `XMLHttpRequest`, though, that implements the same interface as `HttpClient`. Let me know if that makes sense. – c1moore May 01 '18 at 21:41
5

As another approach you can use the native browser fetch api. So you do not have to deal with angular http, etc

That is how I am doing that:

fetch(configUrl, { method: 'get' })
.then((response) => {
  response.json()
    .then((data: any) => {
      if (environment.production) {
        enableProdMode();
      };
      platformBrowserDynamic([{ provide: AppSettings, useValue: new AppSettings(data.config) }]).bootstrapModule(AppModule);
    });
});

But bare in mind that fetch didn't get much love in old browsers so you need to polyfil that with whatwg-fetch like npm install whatwg-fetch --save then import 'whatwg-fetch' in polyfills.ts in order if you want to support old browsers.

UPDATE: Yeah you can use XMLHttpRequest but you are getting same browsers support with that as fetch just a modern replacement for XMLHttpRequest.

angularrocks.com
  • 26,767
  • 13
  • 87
  • 104
2

Possibly the original answer worked before, but in Angular 5 I was not able to use it, there was no definition for the function getAnnotations. This is what did work for me however:

import { ReflectiveInjector } from '@angular/core';
import {
  HttpClient,
  XhrFactory,
  HttpHandler,
  HttpXhrBackend,
  HttpBackend
} from '@angular/common/http';

// https://github.com/angular/angular/blob/master/packages/common/http/src/xhr.ts#L45
export class BrowserXhr implements XhrFactory {
  build(): any { return <any>(new XMLHttpRequest()); }
}

const injector = ReflectiveInjector.resolveAndCreate([
  HttpClient,
  HttpXhrBackend,
  { provide: HttpBackend, useExisting: HttpXhrBackend },
  { provide: HttpHandler, useExisting: HttpBackend },
  { provide: XhrFactory, useClass: BrowserXhr},
]);

const http: HttpClient = injector.get(HttpClient);

http.get('/url').subscribe((response) => {
  console.log(response);
});
Jens Bodal
  • 1,707
  • 1
  • 22
  • 32
1

Thanks, Kuncevic. I have come up with this solution, which works fine:

function httpGetAsync(theUrl, callback) {
  const xmlHttp = new XMLHttpRequest();

  xmlHttp.onreadystatechange = function() {
    if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
      callback(xmlHttp.responseText);
    }
  }

  xmlHttp.open('GET', theUrl, true); // true for asynchronous
  xmlHttp.send(null);
}

httpGetAsync('assets/configs/configuration.json', (config: string) => {
  ConfigurationService.configuration = <Configuration>JSON.parse(config);
  platformBrowserDynamic().bootstrapModule(AppModule);
});
hholtij
  • 2,906
  • 6
  • 27
  • 42