Global Variables and Constants
There are quite a few ways that you can create and manage both global variables and constants within the Angular framework. Three methods that we will go over in this article are through the environment file, an auxiliary constants file, and by using a service.
Environment File
Angular comes right out of the box ready to run already using an environment file specific to the build/serve configuration being used, typically a local and production version. You can find these files in the environments
folder. You can add and remove any property from the environment constant that you would like, however, the production property is used in your main.ts
file to enable production mode so it is highly recommended to leave that one as it is.
If you just want to jump straight into setting a global variable, just add in the environment file as it’s own constant, or even add it as a property of the environment object, whichever makes the most sense for your situation.
src > environments > environments.ts
export const environment = { production: false, myCustomProperty: 'original value' }; export const myNewConstant = { key: 'value' };
Once the app is built and served, all constants are available as an in-memory object and it can be dynamically set in on the fly. No need to worry about writing to or reading from the file, just call your constant or the environment variable from any module, component, service, or other desired location.
src > app > app.component.ts
import { Component } from '@angular/core'; import { environment, myNewConstant } from '../environments/environment'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { title = 'Sample Application'; constructor() { this.title = environment.myCustomProperty; environment.myCustomProperty = 'new value'; console.log(this.title); console.log(environment.myCustomProperty); console.log(myNewConstant.key); } } // RESULTS // console.log(this.title) => 'original value' // console.log(environment.myCustomProperty) => 'new value' // console.log(myNewConstant.key) => 'value'
While this method is great for adding things that pertain directly to the specific environment, cluttering your environment file with other constants only results in code spaghetti later on. If you need to provide a separate constant, we strongly recommend creating it in a separate file.
New Constants File
For extraneous constants, so as to not overload your environment files with messy exports, it is as easy as creating a new file and exporting your constants there. You can add the file pretty much anywhere you want, so long as it is in the src folder, but creating some type of convention would help keep things straight. To create that convention, I created a folder that will contain all of my constant files. In the constant file, you just do the same as you saw in the environment file: export const. You then also use it the same as you would the environment constant.
src > constants > my-constant.ts
export const myCustomConstant = { myCustomProperty: 'original value' };
src > app > app.component.ts
import { Component } from '@angular/core'; import { myCustomConstant } from '../constants/my-constant'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { title = 'Sample Application'; constructor() { console.log(myCustomConstant.myCustomProperty); myCustomConstant.myCustomProperty = 'new value'; console.log(myCustomConstant.myCustomProperty); } } // RESULTS // console.log(myCustomConstant.myCustomProperty) => 'original value' // console.log(myCustomConstant.myCustomProperty) => 'new value'
Key Note About Importing Objects
Because the object is being imported, regardless of it being either a const or var, we cannot overwrite the value of the object itself, only the values of the properties within the object. This is a hard restriction and overwriting the whole object itself cannot be done in this manner. One way that we can mitigate this issue, as well as give us a lot more benefits to managing our state is by instead using a service.
Global Service
Introducing a service is a bit more work than the first two methods, mostly because it involves messing with the module and providers, but it comes with a plethora of advantages. Angular services provide the application with the ability to quickly get, set, and notify other components of any state and variable changes through the use of Subjects, and more specifically in our case, a BehaviorSubject
.
1. The first thing we need to do is to create the new service and it will be placed in a services folder at the app level.
src > app > services > global.service.ts
import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class GlobalService { public customVariable = new BehaviorSubject<any>({ customProperty: 'original value' }); }
2. Next, we import and use the service in a component.
src > app > app.component.ts
import { Component } from '@angular/core'; import { GlobalService } from './services/global.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { title = 'Sample Application'; constructor(public globalService: GlobalService) { // Subscribe and listen for any changes this.globalService.customVariable.subscribe({ next: newValue => console.log('Update Detected:', newValue) }); // Silently update the property on the object after 2.5s setTimeout(() => { this.globalService.customVariable.value.customProperty = 'silent update'; console.log(this.globalService.customVariable.value); }, 2500); // Broadcast change to the source object and all properties after 5s setTimeout(() => this.globalService.customVariable.next({ customProperty: 'broadcasted update' }), 5000); } } // RESULTS // Immediately => Update Detected: {customProperty: 'original value'} // After 2.5s total => {customProperty: 'silent update'} // Notice above that the subscription did not pick up on the value change! // After 5s total => Update Detected: {customProperty: 'broadcasted update'}
Using common RxJS methods such as subscribe and next, we are able to easily control the state of information in our application. Components and even other services can listen for updates in the state and react accordingly. If you have ever used EventEmitters
, think of this as an analogous option, but instead of having to update the reference variable, call .emit()
and pass the variable as a value, the value itself is actually stored within the observable and is automatically broadcasted for you. While providing an automatic broadcast, you still retain the ability to silently manipulate the value of the observable and all subscribers will be none the wiser until you so choose to update them. This is a very helpful feature when multiple changes occur in quick succession and you don’t want to broadcast multiple updates. (Although you could use a debounce…but that’s for another time!)
For more information on the Service/BehaviorSubject design pattern, take a look at our article titled I’m not ready for NgRx.
State Managed
Hopefully seeing the power and usability of Angular services for simple state management, you are now armed with the ability to go forth and further optimize your application! No more importing dozens of objects from files all over your application, or having to rely on constantly running loops every couple of seconds to check for updates.
Now this is just a start for simple state management, great for one or two little things, but for a truly robust state management solution, I recommend looking into implementing an NgRx store.
Additional Thoughts
Something to consider after reading this article would be to figure out why exactly you are looking to create a global variable in the first place. Angular, and JavaScript in general, provide a multitude of ways of accomplishing this task but some are much better options than others depending upon your specific use case. Which method you should choose to use is, as per usual, heavily dependent upon your environment and what you are trying to accomplish. Be sure to be smart about your design choice and consider any alternate options, weighing the pros and cons of each.