Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Runtime Environment Settings #7506

Closed
bailejl opened this issue Aug 25, 2017 · 14 comments
Closed

Runtime Environment Settings #7506

bailejl opened this issue Aug 25, 2017 · 14 comments

Comments

@bailejl
Copy link

bailejl commented Aug 25, 2017

Bug Report or Feature Request (mark with an x)

- [ ] bug report -> please search issues before submitting
- [x] feature request

Versions.

@angular/cli: 1.3.2
node: 7.8.0
os: darwin x64

Repro steps.

Not a failure.

The log given by the failure.

Not a failure

Desired functionality.

With modern DevOps and infrastructure automation, it is easy to create a new environment on the fly. To better support this new world, it would be preferred to have one build package and provide the configuration at the point of deployment. This is a more flexible model, which fits a more dynamic environment.

Imagine you are trying to reproduce an issue, but you need to have a copy of production to duplicate the issue, as it only shows up in production. With modern infrastructure tools, it is easy to create a replica of production in the cloud and all the other services on the Internet. With the current design the following steps are required to create a new environment:

  1. Build new environment services
  2. Create a new evironment.{env}.ts file from the new settings of step 1
  3. Create a build to incorporate the environment file from step 2
  4. Deploy new build to environment

In the process above, you risk creating a build that differs from the one in production, which could cause issues and loss of time. The new environment services are first, since you may get tokens and other settings from these services, which need to go into the environment file. This design requires knowledge of all your environments to be known and static, which is fine in a small project, but it is inflexible in this modern era.

By using a runtime environment settings, we reduce the need to create a new environment file and build, which may lead to issues. Here is an example flow for runtime environment settings:

  1. Build environment services
  2. Create a new environment settings file from the new settings of step 1
  3. Deploy a copy of production's build package along with the new environment settings file into the new environment

In this example, the new environment has an exact copy of the build package, not a new build. Runtime environment settings, allow for easy creation of new environments to meet current project needs.

Mention any other details that might be useful.

Nothing else to add.

@clydin
Copy link
Member

clydin commented Aug 25, 2017

This can be achieved currently without any modifications to the CLI. One method would be to create an application configuration service (e.g., AppConfig) that can be injected throughout the application as necessary. It would use the built-in CLI environment functionality for build time settings. But it would also, upon application startup, fetch a configuration file (JSON or otherwise) that can be deployed with your application by adding the file as an asset. This configuration file would contain any settings which would need to be run-time configurable. The aforementioned service can abstract away the underlying mechanics of retrieving and normalizing the settings; and in addition, can optionally use the build time settings as defaults and defer to the run-time settings only if present. This allows for simplified deployment if desired and also run-time flexibility if needed.

An additional CLI feature that could be useful in this scenario (and potentially others), would be to expand the environment option to allow the addition of assets per environment. Allowing, for instance, a production environment to add a different set of files to the output than a development or staging environment. However, with the defaulting concept as explained above, this not necessarily a requirement but simply opens up an additional implementation option for the desired use case.

@dopry
Copy link

dopry commented Aug 25, 2017

my 2c, I swear by 12factors Config in my apps, and I totally like having run-time control from environment variables. With a client side app you don't actually have control of the runtime environments. In practice, I've found run time setting definition difficult in for ng apps, specifically the startup delay when your app has to go look up configuration. Usually, I set environment at build time and package unique dev, qa, and prod assets. As a developer I try to ensure the only things that charge are configuration settings and include/exclude dev instrumentation.

@jongunter
Copy link

jongunter commented Aug 30, 2017

Agree with this 1000%. In AngularJS apps, I've usually deployed them on PHP/Python/ASP.net MVC server with just one "backend" controller that served up a JS file populating a global environmentSettings constant (those actual settings would be stored in a JSON/XML file read by the controller). I'm still trying to figure out the exact steps I'd take to do this in an Angular CLI project (as it's more complex than just including <script> tags). Also, it'd be nice to find a way to set environment variables at runtime without having any sort of back-end (just edit a JSON file), so SPA's could be deployed on an extremely lightweight HTTP server.

@tytskyi
Copy link

tytskyi commented Aug 30, 2017

@jongunter

(as it's more complex than just including <script> tags)

It is not: turn off files hashing (do not turn off for "media") (use hashing on PHP side), add scripts to index.php in the same order they are in file dist/index.html

Print your "config" in the script tag before first script tag.

<script>
    APP_CONFIG = {};
</script>

Create provider token and provide your config via factory.

export const APP_CONFIG_TOKEN = new InjectionToken<string>('APP_CONFIG_TOKEN');

export function appConfigFactory() {
    return (window as any).APP_CONFIG;
}

//...
{provide: APP_CONFIG_TOKEN, useFactory: appConfigFactory}

@devoto13
Copy link
Contributor

devoto13 commented Aug 30, 2017

Also you can use Webpack's dynamic imports to achieve that. Manually put settings.json with runtime settings next to your index.html after application is compiled. Than you can load this file from your application:

import('settings.json').then((settings) => /* store settings in some common place, like a service /* );`

You can call it from APP_INITIALIZER or on demand. The advantage of this approach is that you don't have to edit index.html and can load settings from any endpoint (like from API for example).

FWIW there are many relatively simple ways to achieve runtime environment settings.

@jongunter
Copy link

Great stuff, @devoto13 and @tytskyi . I'll give it a try. Thanks for the suggestions!

@clydin
Copy link
Member

clydin commented Aug 30, 2017

My answer above allows for runtime settings without the need for a backend service, modifying index.html, or using webpack specific features. The key is to use an angular service to request the JSON file either on startup or on first use via the angular http service. In my projects I also add additional logic to the service to incorporate the CLI environment options, provide option defaults and helper functions, etc.

@jongunter
Copy link

@clydin Would you mind giving more details on how you implement that? I'm coming fresh from an AngularJS world and I don't know Angular 4 specifics as well as I probably should.

I understand how you would request a JSON file via Http. However, there seems to be an annoying race condition that I need to have these settings available before I can use them (for example, before I make an HTTP request to our API, or redirect to a specific URL for authentication). I can make the settings an Observable and chain it into every service method (via switchMap) that returns an observable, but that seems awfully repetitive and possibly error-prone. If I were to use a route resolve at my root route for fetching this data, would that solve this problem?

@devoto13
Copy link
Contributor

devoto13 commented Aug 30, 2017

Google for examples of using APP_INITIALIZER. It allows to execute some logic and return Promise. Angular application will not be initialized before this Promise is resolved. Like this one.

@jongunter
Copy link

@devoto13 I got this working on my app and it worked great until I brought in ngrx/effects. Seems as though all of my effects classes (and their dependencies) are eagerly instantiated before the APP_INITIALIZER promise resolves, meaning that the environment settings are not available to some of my services and effects.

I'll play around some more...maybe this an issue I should file with the ngrx team.

@jongunter
Copy link

Regarding ngrx/effects running before APP_INITIALIZER, there's an issue open here (I don't think it's solved in v4 yet): https://github.com/ngrx/effects/issues/152

@Brocco Brocco self-assigned this Sep 1, 2017
@Brocco
Copy link
Contributor

Brocco commented Sep 1, 2017

I think the workaround sounds quite reasonable/usable, closing this issue/feature request.

@Brocco Brocco closed this as completed Sep 1, 2017
@filipesilva
Copy link
Contributor

Heya all, just want to mention that this issue was essentially a duplicate of #3855, and there's some more discussion there.

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 8, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants