Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

angular2 bootstrap with data from ajax call(s)

I want to bootstrap my application with data I am retrieving from a service. I am doing something along the lines of

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)
    });

This works fine, but I do not like using the dependency array twice, is there a cleaner way of doing this? Can I add things to the application injector after bootstrap? Also I notice that the bootstrap function returns a promise, can I use this promise to prevent bootstrap of the application until after my ajax request finishes?

Of course for the Injector I could use only those dependencies required by MyService but this makes it very brittle as you can imagine.

like image 501
Umair Avatar asked Apr 01 '16 20:04

Umair


2 Answers

The problem here is that Angular2 doesn't give you access to the application reference and its injector before bootstrapping the main component on it. See this line in the source code: https://github.com/angular/angular/blob/master/modules/angular2/platform/browser.ts#L110.

An approach could be to implement a custom bootstrap instead of using the default one. Something like that that splits the application creation and the boostrapping on the application component on it. This way you will be able to load something between the two tasks.

Here is a sample implementation:

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();
}

and use it this way:

customBoostrap(AppComponent, [
  HTTP_PROVIDERS,
  CompaniesService
]);

Companies will be automatically available for injection within the component for example:

@Component({
  (...)
})
export class AppComponent {
  constructor(@Inject('companies') companies) {
    console.log(companies);
  }
}

See this corresponding plunkr: https://plnkr.co/edit/RbBrQ7KOMoFVNU2ZG5jM?p=preview.

At this time, it's a bit hacky but such approach could proposed as a feature request...

Edit

After having a look at the doc for the ApplicationRef class, I saw that there is a simpler solution ;-)

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();

Here is the corresponding plunkr: https://plnkr.co/edit/ooMNzEw2ptWrumwAX5zP?p=preview.

like image 172
Thierry Templier Avatar answered Sep 23 '22 16:09

Thierry Templier


@Thierry (as usual) has answered the heart of your question well, but I think this is worth noting separately:

Can I add things to the application injector after bootstrap?

Yes, by declaring them in providers or viewProviders on the decorators of the components that require them. e.g:

//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){ }
}

Note that providers can be used on directives as well as components (it's an option on DirectiveMetadata, from which ComponentMetadata extends), while viewProviders is only available on components for reasons that are clear given the difference between them.

IMHO, it is a best practice to inject dependencies this way wherever possible instead of doing it bootstrap, as it allows you to limit the scope of availability of a given dependency to the part of the application (i.e. component sub-tree) where you want it to be available. It's also conducive to progressive loading and avoids the SoC smell of configuring myriad unrelated injectables in a single bootstrap file.

like image 33
drew moore Avatar answered Sep 23 '22 16:09

drew moore