AngularJS: Use constant service Instead of Magic Strings

Depending on your programming background, this tip might look a little strange. As I’ve mentioned before, I spent the first 15 years of my career as a java developer. One of the first things I was taught was to avoid “magic strings”. The term “magic string’ is really just another term for a string literal. To relate this to Angular, below is an couple of example of a magic string/string literal:

// UI Router config in app.js
$stateProvider.state('locationDetails', {..rest of config obj...};

And navigating to the state above in another file…

// in locationsListingCtrl.js
$state.go('locationDetails');

Both of the examples above feature a hard-coded string literal. In the first case, we are defining a state called ‘locationDetails’. Any piece of code that needs to navigate to this state will also use a magic string:

$state.go('locationDetails');

The problem is that ‘locationDetails’ is now scattered across multiple files. A simple misspelling can cause your code to be incorrect. Or if you decide ‘locationDetails’ is too long and you want to rename it ‘locDetails’..now you have to search the project for all instances of ‘locationDetails’ and replace them with ‘locDetails’. Sure, your text editor can help you with this, but it doesn’t change the fact that your code is more fragile than it needs to be.

So how can you fix this? Angular’s constant service to the rescue. I’ve updated the angularBPSeed project to add the following module:

.constant( 'STATE', {
    'MAIN': 'main',
    'LOCATION_DETAILS': 'locationDetails'
})

I’ve defined a constant service called STATE. As the name indicates, I will use the STATE service to define constants needed by UI Router. A nice thing about the constant service is that it is available in the config service. That will allow us to use constants when configuring out UI router navigation. For example:

.config(['$stateProvider', '$urlRouterProvider', 'cfpLoadingBarProvider', 'STATE',
    function ($stateProvider, $urlRouterProvider, cfpLoadingBarProvider, STATE) {
        ...omitting some code.....
        $stateProvider.state(STATE.LOCATION_DETAILS, {...rest of config...});
        ...rest of the config module below...

Two things were changed from the previous version of the config module. First, you can see that STATE is now passed in as a dependency. Second, instead of

$stateProvider.state('locationDetails', {...rest of config...});

We now have

$stateProvider.state(STATE.LOCATION_DETAILS, {...rest of config...});

The “magic string’ has now been replaced by an object. At runtime STATE.LOCATION_DETAILS becomes ‘locationDetails’, because ‘locationDetails’ is the value we defined for the LOCATION_DETAILS key in the STATE constant service.

This also means when we need to manually navigate to the location details state, instead of

$state.go('locationDetails');

we do this

$state.go(STATE.LOCATION_DETAILS);

(note that you will need to add STATE as a dependency to any angular module that accesses the STATE constant service we defined above)

So how does this help us in the real world? First, you are no longer using “magic strings”. Second, if you later decide that the location details state should be “locDetails” instead of “locationDetails”, you only need to make ONE change to your code:

.constant( 'STATE', {
    'MAIN': 'main',
    'LOCATION_DETAILS': 'locDetails'
})

You just change the value for the LOCATION_DETAILS key in the constant service. Throughout the rest of your app, any references to STATE.LOCATION_DETAILS will now resolve to “locDetails” instead of “locationDetails”.

This change makes your code less brittle, while removing the bad practice of “magic strings”.

As I mentioned, I’ve updated the angularBPSeed project to use remove magic string usage from the UI Router code. Check out the following files for more details: /src/app/app.js, /src/app/components/locationDetails/locationDetailsCtrl.js, /src/app/components/locationsListing/locationsListingCtrl.js

I also updated the Angular Best Practices Styleguide to add the following:

When possible, avoid using “magic strings” by using Angular’s constant service.

 

11 thoughts on “AngularJS: Use constant service Instead of Magic Strings

    • One option off the top of my head:
      –Add a new field to your scope, and set it equal to your constant service. Ex:
      self.STATE = STATE; // in your controller

      then in your template you can access the service through the scope reference. I haven’t tried this in code yet, but I think it should work.

      EDIT:
      Or, you could insulate the template from the service a little more by doing something like this:
      self.locationDetailsState = STATE.LOCATION_DETAILS; // in your controller

      then in your template you can reference locationDetailsState. On further reflection, this approach is likely a little better.

      Like

  1. I can understand you want to use some constants, but this doesn’t really speed up development at all. If you change locationdetails entirely to something like appointmentdetails, your constant is also not really correct anymore. I doubt you will be switching route-name much but if you do, it might be a bigger change than just shorten it.

    We’ve been using constants to differenciate between development and production. For stuff like auto-login, debug-mode, different API-address and setting timeout-values.

    Like

    • I agree that it doesn’t do much to speed up development very much, but that honestly isn’t a reason I use constants. I think the benefits are:
      -reduce the number of magic strings/string literals in the codebase
      -let WebStorm autocomplete my constant names to help prevent runtime errors due to typos
      -makes my code feel cleaner

      Maybe its my java background, but seeing string literals littered throughout my code gives me the heebies. And something about it just feels lazy.

      I didn’t mean to imply that UI Router was the primary use case for constants, as ANY time you use a string literal in your code, a constant is an option. I just used UI Router in my example, because I used UI Router in my seed project.

      Like

  2. Hmmm, since we’re talking about javascript, how about

    window.applicationConfigObject = {};
    window.applicationConfigObject.myConstant = ‘whatever’;

    This stuff is available without the need of DI throughout your code, and if you do it cleverly, e.g. to have an app initializer module that sets these constants, then your tests might have a different application initializer which might give different values to those constants.

    Like

    • I personally don’t like the idea of storing ANYTHING in the global scope. In fact, if you are going to use a global scope, I would thing you would just use the $rootScope instead of the window (or $window). But I wouldn’t do either. I would either use the constant service, or a service service. Service’s can be used to maintain app state, like a cache. I just don’t see a need to use any sort of global object for constants.

      Like

      • Jeff, thanks a lot for giving me your views about it. I think it’s not that I missed a counter argument, rather it’s a kind of personal taste. After all, the public final static String WHATEVER is something one stores in the “global scope” (in the sense that it is available everywhere, in case of need). Yet we just use it in Java, and with those static imports, this global scope smell is even more strong (although I still have to inject them explicitly).

        I don’t feel it less secure, no performance or other issue arises, the code is maintainable – so if indeed no other counter argument exist, I will use it for storing app-wide constants, and will initialize them in one central file (which then might be replaced during testing). It’s not that different than a centrally located config xml or property file.

        Your solution serves the same goal, and answers the problem well. It’s just a bit more verbose – I have to inject the constant service wherever I need the constants. That’s perhaps closer to the java static import way, I allow.

        Like

        • Ivan…thanks for the dialog. Even though we have differing opinions, I enjoy talking them out 🙂

          I still think there are 2 main differences between “public static final String” constants in Java, and using window (globals) in JavaScript:
          1) I don’t consider public static final Strings “global”, since you still have to import that class, right? Or did this change in Java 7/8? In other words, you still have to declare that dependency. Using window in JavaScript just works, so you don’t necessarily know a module is using window unless you search the code. I’m just not a fan of “hidden” (non-explicit) dependencies…but I know that is a personal choice.
          2) Angular constant values cannot be changed. But window.someGlobal can be changed (unless you are using ES5 to make the object non-writable).
          3) Unlike Java, JavaScript doesn’t have a namespace (thought you can work around that with IIFEs)…in other words, your window.someGlobal object isn’t just visible to your app, it is visible to every script your app uses. And your window.someGlobal could be defined in another script on the page, or modified by another script on the page. You could do something like window.myNamespace.someGlobal but it doesn’t feel clean to me.

          I guess for me, with JavaScript being more dynamic (and more fragile, imo)…it is worth it to me to work a little harder to make my code safe (from side-effects, from potential conflicts with other code, etc).

          Like

          • Hi Jeff,

            I can accept that. However, the 1) is not quite so, because imports are just shortcuts for the fully qualified names, and the window.whatever.variable is also just a fully qualified name. 2) is a good reason, although it might be circumvented (with getObj), yet the Angular way is better. 3) is more or less the same – I don’t mind if my constants are accessible globally, am going to use the window.wrapper.constant pattern, and since I make them immutable, therefore I don’t have to worry about the unwanted modifications.

            But of course, I might make an object immutable, but not a primitive, so the Angular constants have their merit. So I guess your solution is safer.

            Thanks a lot for taking the time!

            Like

  3. Great post and it seems fine in your example. However, our app has many nested states. How would you do nested routes within this?

    So for:

    $stateProvider.state(‘app.location.details.item.subitem’, {…rest of config…});

    How would you code this:

    .constant( ‘STATE’, {
    ‘MAIN’: ‘main’,
    ‘LOCATION_DETAILS’: ‘locDetails’
    })

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s