TL;DR: Nothing was removed

Angular 15 simply no longer generates environment files as part of the default project scaffolding. You can still create them on your own and wire up file replacement per build target just like older versions did automatically.


The confusion comes from a false assumption

Recent chatter around the missing src/environments/environment.ts and src/environments/environment.prod.ts in fresh projects created with ng-cli at version 15 stems from a widespread belief that these were hardcoded paths baked into the Angular framework itself. That's not the case. They were simply a convenient default, referenced nowhere in the framework internals, and could have been swapped for any other names or locations without any problem.

What those files were actually for

When a new project was generated, the only place those files were referenced was inside main.ts, which contained bootstrapping code similar to the following snippet:

...
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}
...

That code used the environment.production property to decide whether to enable ProdMode at startup. That call disabled a flag — known internally as isDevMode, despite what one might assume — that Angular's core relies on to activate debugging behavior. The most obvious effect of this flag being on is the familiar console message:

Angular is running in development mode.
Call enableProdMode() to enable production mode.

With the flag active, the framework is more verbose about warnings and errors, and it also goes out of its way to check for conditions that aren't errors by themselves but could cause problems down the line. One well-known example is NG0100: Expression has changed after it was checked, which guards the unidirectional flow of data binding — a rule that can be broken in ways that don't fail immediately but might cause issues later.

In the beginning, there was no other way to flip that switch except by invoking the function from a file that was parsed at startup.

How file replacement actually worked

Many developers took it on faith that Angular somehow knew whether to load environment.ts or environment.prod.ts depending on the build target — without ever looking into the mechanism. The reality was far less magical: it was just a feature of the Angular builder. While processing the configuration for a chosen target, the builder would look for a fileReplacements array and swap files according to the mappings described there. A typical default configuration for a production build looked like this a few versions back:

"configurations": {
...
  "production": {
    "fileReplacements": [
      {
        "replace": "src/environments/environment.ts",
        "with": "src/environments/environment.prod.ts"
      }
    ],
...

There was nothing stopping you from adding new replacements or changing the existing one. However, if you changed the default mapping, you'd also need to update main.ts so it looked for the environment.production property in the right file.

What actually changed in Angular 15

The latest major release takes a different route to control the production flag. Instead of parsing any environment file, it lets the optimization builder option — which is already enabled for the production build target — set the global NgDevMode to false.

That single change removed the original reason for having environment files at all. As a result, the CLI stopped generating them, and dropped the default fileReplacements entries from build target configuration.

Why people are upset

Given everything above, this adjustment sounds like an internal detail that shouldn't bother most Angular apps. But because these files were generated automatically and swapped by build target from the start, many developers got used to storing configuration values in them — mostly API endpoints and auth provider settings that needed to differ between development and production builds.

When new projects stopped including those files, people who had never looked under the hood had no idea where to put that data — not realizing how simple it is to recreate the old setup by hand.

The quick fix

After enough pushback, the Angular team decided to bring back something close to the previous behavior, available on demand starting with the 15.1 release.

From that version onward, generating a project with environment files in place is just one command away:

ng generate environments

As explained in the docs, this creates both src/environments/environment.ts and src/environments/environment.development.ts, and wires up the latter as a replacement for the former in the development build target:

"development": {
...
  "fileReplacements": [
    {
      "replace": "src/environments/environment.ts",
      "with": "src/environments/environment.development.ts"
    }
  ]

Why not keep them by default

Storing environment-specific values in those files wasn't as clean a solution as it seemed. Although their name suggests they're the right place for endpoints, domains, and ports, such values aren't always tied to the build target — they often depend more on the deployment context. That's the reason many teams prefer to keep such data out of the build entirely, evaluating it at runtime instead. This can be done by injecting tokens during deployment, reading environment variables from the hosting platform or Docker setup, having a minimal server-side process expose them via an API, or even shipping them as a config file placed into assets by the pipeline — a strategy described in this excellent write-up by @frederikprijck.


To summarize: Angular hasn't changed how environment variables are handled. What happened is that a formerly necessary convenience feature is no longer required — which naturally pushed developers to question what was always a somewhat imperfect practice. The old approach still works and is still available, should you want it.