2

I have a link generator service that is able to generate links to specific content types (users' details page, content items' details pages etc).

This service is really easy to use and has synchronous functions:

links.content(contentInstance); // /items/123
links.user(userInstance); // /users/234

I now have to introduce separate routing for logged in user to change from /users/id to /users/me.

The only change I'd need to add to my link generator service is to check whether userInstance.id == loggedInUser.id and return a different route URL. This is not a problem as long as my logged-in user's info would be synchronously available. but it's not...

I have a userService.getMyInfo() that returns a promise. The first time it's called it actually makes a server request but subsequent calls return a resolved promise with already cached data.

So how should I implement my user link URL generation in my link generator service?

Edit

Ok. So to see better what I have at the moment and where I'm having the problem. I'm pretty aware that async will stay async and that it can't be converted to synchronous (and it shouldn't be).

This is some more of my code, that will make it easier to understand.

linkGenerator.user

angular
    .module("App.Globals")
    .factory("linkGenerator", function(userService) {
        ...
        user: function(userInstance) {
            // I should be calling userService.getMyInfo() here
            return "/users/{0}/{1}".format(userInstance.id, userInstance.name);
        },
        ...
    });

userService.getMyInfo

angular
    .module("App.Globals")
    .service("userService", function($q, cacheService, userResource) {
        ...
        getMyInfo: function() {
            if (cacheService.exists("USER_KEY"))
                // return resolved promise
                return $q.when(cacheService.get("USER_KEY"));

            // get data
            return userResource
                .getCurrentUser()
                .$promise
                .then(function(userData) {
                    // cache it
                    cacheService.set("USER_KEY", userData);
                });
        },
        ...
    });

Controller

angular
    .module("App.Content")
    .controller("ItemDetailsController", function(linkGenerator, ...) {
        ...
        this.model = { ... };
        this.helpers = {
            ...
            links: linkGenerator,
            ...
        };
        ...
    });

View

View uses ItemDetailsController as context notation.

...
<a ng-href="{{::context.helpers.links(item.author)}}"
   ng-bind="::item.author.name">
</a>
...

Notes

As you can see my view generates links to item authors. The problem is that my linkGenerator (as you can see from the code may not have the information yet whether it should generate one of the correct links to user details view.

I know I can't (and don't want to) change my async code to synchronous, but what would be the best way to make this thing work as expected?

One possible solution

For the time being I've come up with a solution that does the trick, but I don't really like it, as I have to supply my logged in user's ID to linkGenerator.user(userInstance, loggedInUserId) function. Then I set up my routing so that I add resolve to my route where I call userService.getMyInfo() which means that my controller is not being instantiated until all promises are resolved. Something along this line:

routeProvider
    .when("...", {
        templateUrl: "path/to/my/details/template",
        controller: "ItemDetailsController".
        controllerAs: "context",
        resolve: {
            myInfo: function(userService) {
                return userService.getMyInfo();
            }
        }
    })
    ...

Then I also add an additional helper to my controller

this.helpers = {
    ...
    links: linkGenerator,
    me: myInfo.id,
    ...
};

And then I also change link generator's function by adding the additional parameter that I then supply in the view.

linkGenerator.user = function(userInstance, loggedInUserId) {
    if (userInstance.id === loggedInUserId)
        return "users/me";
    return "users/{0}/{1}".format(userInstance.id, userInstance.name);
}

and in the view

<a ng-href="{{::context.helpers.links.user(item.author, context.helpers.me)}}"...

And I don't to always supply logged in user's ID. I want my service to take care of this data on its own.

Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
  • Could you provide more of your code please? It is much easier to create an answer that demonstrates by code than it is to describe a solution in prose. e.g. The `userService.getMyInfo` method at least. – sdgluck Sep 18 '15 at 13:58
  • @sdgluck: I've now added all the important bits of code that will make it easier to understand the problem. And to stress out I want to make it work so `linkGenerator` will get loged-in user's ID so I don't have to provide it each time. – Robert Koritnik Sep 18 '15 at 19:46

1 Answers1

1

There is no way to make anything in JavaScript that is asynchronous at some point synchronous again. This is a ground rule of how concurrency works - no blocking for waiting for stuff is allowed.

Instead, you can make your new method return a promise and use the regular tools for waiting for it to resolve.

links.me = function(){
     var info = userService.getMyInfo();
     return info.then(info => { // or function(info){ if old browser
          // generate link here
          return `/users/${info.id}`; // or regular string concat if old browser
     });
}

Which you'd have to use asynchronously as:

links.me().then(function(link){
    // use link here
});
Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • I've heavily edited my question to make it more clear how my code is structured and what I'm after... I would like `linkGenerator` to get logged-in user's ID by itself without me explicitly providing it every time I would want a user link generated. – Robert Koritnik Sep 18 '15 at 19:48