Skip to content

Simplify Redirects (Alias URLs?) #185

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

Closed
timkindberg opened this issue Jun 14, 2013 · 87 comments
Closed

Simplify Redirects (Alias URLs?) #185

timkindberg opened this issue Jun 14, 2013 · 87 comments

Comments

@timkindberg
Copy link
Contributor

See @ksperling comment in #174 (comment):

I like the idea of aliases, but I'd be okay with just adding a new redirect method to $stateProvider. Like this:
$stateProvider.redirect("/fancypants", stateNameOrURL)

If we did allow urlAlias then I'm not too worried about the various parent/child alias combinations. If that's what the user wants, they should get it.

This also brings up the point of too many providers, see #184

@laurelnaiad
Copy link

on simple redirects without concern for parameters...
...Personally I think a redirect that doesn't support parameters isn't enough to satisfy the "friendly URL" thing, and my experience is that customers/clients/managers will end up asking for them -- better to support it in angular apps than to have people doing crazy things in Apache (or worse) to make it happen.

on whether to reduce the number of providers/services to one...
...I absolutely wholeheartedly think one service (with its provider) is the way to go.

on the complexity of supporting parameterized aliases...
I can't and won't try to speak for @ksperling, but this comment made me think about why it might be complicated and/or non-performant to support aliases within state definitions. I'm going to go out on a limb and try to start the process of getting into the nitty gritty.

Right now, there is some interplay within $state(Provider) and $urlRouter(Provider) -- witness this code in the $stateProvider:

// Register the state in the global state list and with $urlRouter if necessary.
if (!state['abstract'] && url) {
  $urlRouterProvider.when(url, ['$match', function ($match) {
    $state.transitionTo(state, $match, false);
  }]);
}

So $state leverages the functionality of $urlRouter to handle url-instigated transitions. $urlRouter ends up with a "when" rule that has a function which calls $state.transitionTo().

And here's how $urlRouterProvider assembles its rules:

this.rule =
    function (rule) {
      if (!isFunction(rule)) throw new Error("'rule' must be a function");
      rules.push(rule);
      return this;
    };

So $urlRouter is bearing an array of rules that it examines one at a time when the $location changes.

$state doesn't really care about the order in which states are defined, because in and of itself everything has specific name and to get to a state you specify the name in the transitionTo() function.

$urlRouter, on the other hand, tests things one at a time to find a match. If I understand it correctly, this comment in the source mentions the possibility of down the road snipping the decision process once a particular pattern is disqualified.

// TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree

That comment begins to introduce the problem (at least I think of it as a problem) of using an array to store the rules -- namely that you have no way of skipping rules that certainly won't match because you don't known how many indices in the array to advance to get to the next rule which has a possibility of matching.

When I began to float the idea of editing states ex post facto, one of the complexities cited was (to paraphrase) "how do you manage the rules"... and given that $stateProvider farms out the url handling to $urlRouter and that its rules are in an array (but not a "disarray" ;) ), this was a completely valid challenge. I'm not going down the path of suggesting that ui-router support editing in this comment, so please bear with me... this is about aliases.

I think I cracked a few nuts that relate to aliases/otherwise, and into merging the functionality of $urlRouter and $state in one service provider in $detour, and I'd be happy to help with the retirement of $urlRouter and the addition of aliases and the otherwise() function support into $state if such changes are deemed useful...

...and I defer to the ui-router team as to whether that's the way to go with $state.

I started to go into greater detail about how $detour works and realized I was getting ahead of myself. I guess my point is that aliases aren't that hard if you merge $urlRouter with $state, but I acknowledge that doing that merging is real work.

@ksperling
Copy link
Contributor

The reason for keeping $urlRouter and $state separate is that

  • it is perfectly OK to trigger things via $urlRouter that don't relate to state
  • it is also OK to use $state without urls at all

I don't think the complexities of alias support are unmanageable, or really related to $state and $urlRouter being separate. Essentially what will have to happen looks something like this in pseudo code:

determine effective url set for state:
  for each pattern in [state.url + state.urlAliases]
    if (pattern is absolute) add it to state.effectiveUrls
    else for each parentUrl in parent.effectiveUrls
      combine pattern with parentUrl and add it to state.effectiveUrls
  end
verify that all effectiveUrls have the same set of parameters

I do wonder why you'd use lots of aliases in pratice though -- if you get asked to implement 'fancy' URLs, wouldn't you just change the primary URLs of the state to be fancy instead of adding aliases?

@laurelnaiad
Copy link

"fancy" may be a misnomer... I'd call them "marketing" urls...usually simpler and smaller than the ones that make the app actually work. It's just a way to do a redirect when you get right down to it. The url does matter for relative paths in the html (as I suspected might be what's tripping up the bootstrap modal in #180), so you may not want to rearrange your hierarchy to support the marketing urls.

@ksperling
Copy link
Contributor

Well... once @nateabele finishes the sref directive, you shouldn't really need to hard-code state URLs in HTML anymore. The state definition would then be the only place containing the actual URL pattern, so you'd be free to change it according to marketing whims ;-)

@laurelnaiad
Copy link

I don't understand how @nateabele's changes are going to have an impact on the flyer the marketing team hands out at conferences or puts in a TV ad. There may be one or two links from the outside world into your app if you're lucky!

I'm not sure it's time to retire url's altogether... when you have relative paths in your html to things that aren't states, the url hierarchy matters. Redirects haven't lost their usefulness...

If the $urlRouter when and otherwise functionality goes into $state as aliases and otherwise, then you don't have two orthogonal providers, which I think @ksperling is talking about here...

It think with the '' -> '/' fix and maybe alias URLs there shouldn't be much reason to use $urlRouterProvider directly in most cases.

...though I could be mistaken.

@ksperling
Copy link
Contributor

If there's only 1 or 2 external URLs into the average app, I'm not quite sure why aliases are important then?

In either case, I think $state should only handle URLs that either directly or indirectly (alias / redirect) refer to a state. Lumping everything together in one monolithic service is one of the main design mistakes of the existing $route service in my mind.

@timkindberg
Copy link
Contributor Author

$stateProvider.redirect(stateNameOrUrl, stateNameOrUrl);

@timkindberg
Copy link
Contributor Author

I don't really think alias is needed, because I think it promotes the use of redirects a bit too much. It indirectly tells the dev that every state could/should have some alias urls. Instead if we use a method like $stateProvider.redirect(stateNameOrUrl, stateNameOrUrl); then they'll only set up the redirects they really need.

@laurelnaiad
Copy link

Maybe by using the term redirect I'm making things confusing. When I've said redirect I was referring to the aliad feature which would be within state definitions. I wasn't indicating that I perceive the need to a separate redirect feature to go between states.

@laurelnaiad
Copy link

But you need to redirect from an existing state -- which defeats the
purpose. The purpose is to give alias urls to a state -- and nobody's
gonna die if someone decides to use the feature "too much" -- it would be
questionable, but it's their choice.

@ksperling
Copy link
Contributor

Hm, one easy way to side-step the whole "combination of parent aliases" problem would be to require alias URLs to be absolute (which in most reasonable use-cases they should be anyway). So all $state needs to do is validate that the alias has all the parameters the state needs and set up the redirect.

@nateabele
Copy link
Contributor

I'm 👎 on this. Each state should have one and only one canonical URL. If you want to have short URLs, create an empty state with only onEnter, and use transitionTo() to redirect. The way to use a good routing system is that, once you define your routes (or states, in this case), you only ever refer to those in app code, not directly to URLs.

@laurelnaiad
Copy link

My $0.02...

@nateabele, I'm glad you're keeping the topic alive, even if we seem to be on opposite sides of the thinking... :)

Is it the feature's value the question (does it complicate the ui, is it going to be used, does it fit in the mission, how many lines of code and bugs saved in client apps, etc.)? Or the feature's cost? Or some combination of both?

I ask because you're indicating that the user should go through some hoops to do this -- nothing too complicated, but work all the same -- and the alternative is that the library goes through those hoops.

I don't see it as painting too far out of the lines to hope that you could deep link extra externally-shared links to your states.

Why not do this right in the library, rather than try to document use of onEnter() and transitionTo() for that use case and then have people using a bunch of deviants of the sample? If done in the library, it's really not much more complicated than the "otherwise" redirect which should be in $state if $urlRouter isn't retired. If everyone's supposed to use $urlRouterProvider to deal in urls, then aren't a lot of people going to end up using both and wonder how they're related?

If it's how the thing would be implemented -- of course all links within the app should be using hierarchical state names rather than URLs... those are the true CNs.... even the "url" isn't as canonical as the state name and its parameters. The "url" is is the second-level canon... and the one that ends up being current when you go to a state... but aliases are proven to be valuable all over the place (DNS, OOP, yadah, yadah). I see no reason why aliases in the sense we're talking about should be treated as workarounds or relegated to some deeper level of the tech stack, such as the web server, proxy, or load distributor.

Seems to me supporting aliases lets you stop publicizing $urlRouter and have no need to keep it as part of the public api... which might help a little with adoption.

@ksperling
Copy link
Contributor

@nateabele I agree that states should have one true canonical URL. However being able to easily set up a few redirects doesn't contradict that basic premise.

And redirects should happen on the URL level; I strongly disagree with using 'dummy' / transition states that do another transition in onEnter.

@stu-salsbury I don't quite understand what you dislike about $urlRouter being part of the public API. It's true that it's a lower-level API, but it's there for when people want to do lower-level things. In fact I think that blurring the distinction between state handling and routing by shoving things like .otherwise onto $state makes it harder for people to understand that conceptually a state and a route (aka urlRouter rule) are different things.

@nateabele
Copy link
Contributor

In fact I think that blurring the distinction between state handling and routing by shoving things like .otherwise onto $state makes it harder for people to understand that conceptually a state and a route (aka urlRouter rule) are different things.

This.

@laurelnaiad
Copy link

Cool with me! I think states and routing are deeply enough entwined to manage them together as one service from an application's perspective.

To me it feels like making two things out of one.

Since detour is meant to be a way to let you define both urls and states with one api, perhaps I'm biased. I wouldn't want to require people to use an extra service and provider just to support otherwise, and it's a hop skip and jump from there to aliases,

However, for ui-router, moving aliases/otherwise to $state is just an idea for how to make it easier for app developers -- if it's clearer for them if separate providers and services are exposed -- so that everyone understands their different functions -- then it's certainly worth the bytes to expose both separately. Usability is key.

No worries!

@timkindberg
Copy link
Contributor Author

I'm more on @stu-salsbury side of this debate. It feels like states and routes belong together enough that adding sugar to stateProvider doesn't feel odd to me. I do think it will reduce confusion for newer users as I am still a new user and it makes sense to me. However I won't cry too much if I have to bust out urlRouter to do my URL based functions.

@laurelnaiad
Copy link

Not to pile on because I've said enough, but I'm still feeling like something isn't clicking. The concept of two providers and services just seems ludicrous to me. I must not be getting it and curiosity is driving me forward.

If $state relies on $urlRouter to do its url handling, then how separate are they? $state can't stand on its own without $urlRouter, and yet $urlRouter really isn't useful other than to do "otherwise" redirects to a "404-ish" state and to handle when redirects that could be better managed with aliases... and yet they should be seen as separate things? Every state with a url is leaning on $urlRouter. $state configures a $urlRouter "when" rule to get itself squared away with urls, and the $urlRouter relies on $state to clean up the urls when transitionTo is called, and it's hanging onto transitionTo functions in its when handlers.... they couldn't be more tightly linked without a circular dependency which is avoided due to the eventing mechanism of route changes.

Advantages to combining:

  • smaller footprint, less ui-router code to manage
  • cleaner api/easier to use and document

Advantages to keeping them separate:

  • users will better understand that states don't map one-to-one with urls by making them configure two providers?

I must be missing something. As @timkindberg says, I won't cry if they're kept separate. If I don't understand the logic, no hard feelings either way.

Is it at all possible that $urlRouter is separate today because of an early impulse to support something that resembles $routeProvider? If $routeProvider goes away, what good is left? This paragraph is sponsored by those who weren't around when this project first took flight...

Either way, ui-router is a heckuvah great solution to routing and state management. Don't let my pushback convince you I'm not grateful for it.

@timkindberg
Copy link
Contributor Author

Yeah I'm on board with @stu-salsbury. This certainly isn't the most important thing we need, and I do see the concern of merging them too much that devs become confused or more importantly, don't learn to fully understand the power of each provider in its own right.

So I think if we create an otherwise/redirect method that redirects to a state as opposed to a url, then its a totally legal addition to $state in the same vein as having a url property in the state config. So how about this?

$stateProvider.redirect(url, stateName);

Now this method is totally in "state" world. There could be no confusion here as to this being a url specific method. It redirects from a url to a state.

@nateabele
Copy link
Contributor

$state can't stand on its own without $urlRouter

But it can. You can wire up a whole state-driven application without ever using routing.

This has nothing to do with footprint or API surface area. This has to do with architecture design decisions that are conceptually right vs. those that are conceptually wrong. For some background, I suggest you read up on the Single Responsibility Principle.

@timkindberg
Copy link
Contributor Author

@nateabele, curious if you think my idea for $stateProvider.redirect(url, stateName) would fall outside of the state responsibility.

@nateabele
Copy link
Contributor

@timkindberg It would. You're asking $stateProvider to 'know' about URLs in a way that it shouldn't. The state machine is an abstraction on top of URLs and routing. You establish a relationship between a state and its route at the outset of setting up the system. After that, it's an implementation detail you no longer concern yourself with, because it's beneath the layer of abstraction at which you're working. You don't use your high-level abstraction reach down into lower layers and tinker with it.

@timkindberg
Copy link
Contributor Author

So setting up a url is ok in the config, but dealing with them afterward in a method is not ok? If we really were splitting things out into true SRP objects, wouldn't we leave URL out of state completely and then have an additional service called something like URLStateConnector which simply connected urls to states?

@laurelnaiad
Copy link

$stateProvider does not stand on its own. Every state with a url is creating a $urlRouterProvider when rule.

You concern yourself with url's because they are the addresses to your whole system that the outside world can use to find and keep track of things.

@laurelnaiad
Copy link

$stateProvider.redirect(url, stateName);

This doesn't address parameters and is unwieldy because it lives away from the state to which it applies. I was asked why I have something against urlRouter... it's because aliases in the state keep all of the code that relates to addressing that state in one place (with the exception of an otherwise/fallback state, which is inherently its own animal).

@laurelnaiad
Copy link

I'd say SRP is fine and all, but at present each state's UrlMatcher gets lost in an array in a different service. It's called ui-router for a reason. I fold.

@ksperling
Copy link
Contributor

$urlRouter is also there for people that want to have URLs that do other things that don't map to $state. Or somebody that doesn't like how $state works write their own version and still re-use $urlRouter, $resolve (about to commit that one in the next few days), $view (TBD), ...

It's perfectly possible to use $state without $uR either if you don't reflect state in the URL, or you could write your own $location processing that ends up calling $state.transitionTo(). Lumping everything into one services makes all these things impossible. SRP isn't only about the use cases we can think of now, it's also about making it easier to reuse pieces of code in other use cases and contexts that we haven't thought of.

That said, having a state.urlAliases property that is an array of absolute URLs and instructs $state to set up a $uRP.redirect to the primary URL of that state is simple and should cater for 95% of the simple cases where people don't understand why they need to use $uR separately. It also won't couple $state and $uR significantly tighter than they are now; it's the same kind of optional interaction as configured by state.url.

@timkindberg
Copy link
Contributor Author

Fine by me, as long as one of the aliases can be "" or "/".

Sent from my iPad

On Jun 18, 2013, at 6:00 PM, Karsten Sperling [email protected] wrote:

$urlRouter is also there for people that want to have URLs that do other things that don't map to $state. Or somebody that doesn't like how $state works write their own version and still re-use $urlRouter, $resolve (about to commit that one in the next few days), $view (TBD), ...

It's perfectly possible to use $state without $uR either if you don't reflect state in the URL, or you could write your own $location processing that ends up calling $state.transitionTo(). Lumping everything into one services makes all these things impossible. SRP isn't only about the use cases we can think of now, it's also about making it easier to reuse pieces of code in other use cases and contexts that we haven't thought of.

That said, having a state.urlAliases property that is an array of absolute URLs and instructs $state to set up a $uRP.redirect to the primary URL of that state is simple and should cater for 95% of the simple cases where people don't understand why they need to use $uR separately. It also won't couple $state and $uR significantly tighter than they are now; it's the same kind of optional interaction as configured by state.url.


Reply to this email directly or view it on GitHub.

@laurelnaiad
Copy link

@timkindberg: gosh I hope you're kidding :) Long story -- not meant for github -- but I needed that.

I agree $urlRouter provider's functionality is worth having access to, and I'd be psyched if aliases/otherwise (and @timkindberg's "/"<->"" feature (j/k: I imagine we all do it)) were accessible to the majority of apps that are just going to use the otherwise feature and never concern themselves with aliases.

@vizo
Copy link

vizo commented Dec 7, 2014

@hugsbrugs i updated it to work with new version.. give me some days, i think i will put it on github, but i don't have time to write tutorials :D http://naslovnice.si it's working example (but it's still in development, so maybe when u check it, it will not work ;)

@hugsbrugs
Copy link

Thank you very much @vizo for your work and your link !

I can't get it working at the time writing : no errors but routes does not load content ... I will investigate further before asking you questions !

@gerisztein
Copy link

I've had a big problem with nested states. For example, app.locale.[state].[substate] were unavailable. We've fixed it by changing the following line in @vizo's code:
this.params = extend(this.params, urlMatcher.params);

For this one:
this.params = Object.getOwnPropertyNames(this.params).length === 0 ? urlMatcher.params : extend(this.params, urlMatcher.params);

As soon as I finish my routes config file, I'll post it in here.

@vizo
Copy link

vizo commented Dec 11, 2014

Hmm what is difference?
even if this.params is just empty object, extend should work ... for me it worked ...
i now changed code to
this.params = extend({}, urlMatcher.params, this.params);

so it creates totally new object ... tell me if it works for you too?

@vizo
Copy link

vizo commented Dec 11, 2014

I finally created https://github.com/vizo/angular-ui-router-i18n . So please let's move there for issues and anything else related.

Thanks.

@fernandofleury
Copy link

The problem we faced with the simple extend is the urlMatcher.params prototype, this.params would come with an empty one. Missing functions such as $$equals

@gerisztein
Copy link

In some cases I get a "undefined function" when I call $state.go by using this new line or even that old. It only works properly when I use that way I said before. Actually I'm trying to figure out the reason why.

@vizo
Copy link

vizo commented Dec 15, 2014

Now i see it, i wasn't using latest ui-router ... UrlMatcher is becoming larger and larger and it's becoming very hard to maintain. It would be so much easier if ui-router would support alias urls for same state.

@mkoczorowski
Copy link

Hi guys,
I have a weird problem


$urlMatcher.rootLocale 'en'
$states.state 'app.locale',
            url: $urlMatcher.compile
                en: '/'
                fr: '/:locale'
            views:
                '@': '/modules/dashboard/views/dashboard.html'

No view is being loaded when I visit '/' route


$states.state 'app.locale.home',
            url: $urlMatcher.compile
                en: '/home'
                fr: '/:locale/dom'
            views:
                           '@': '/modules/dashboard/views/dashboard.html'
  

Visiting '/home/ in this case results redirecting to '/'.

It looks like the '/' route doest really work. Does anyone of you encounter this problem before ?

@mkoczorowski
Copy link

anyone ?

@gerisztein
Copy link

I have the same problem. I always go to $urlRouterProvider.otherwise when I try to not use the :locale prefix. But, if I change the state using $state.go, it works properly. I'm kinda stuck in this issue also @mkoczorowski.

@mkoczorowski
Copy link

Yeah, so close.. Let us know if you managed to figure this on out @gerisztein

@gerisztein
Copy link

So do you, @mkoczorowski
I'm trying a different approach today, let's see what happen. If I make any progress I'll let you know.

@mwarren2
Copy link

mwarren2 commented Feb 2, 2015

@vizo I agree with you that multilingual apps are complicated with ui-router as it stands. I totally agree with you that 'It would be so much easier if ui-router would support alias urls for same state'. Your solution is possible (thankyou for your work), but it comes with several 'musts' before you can start to use it. I suppose that there are good reasons why there must only be one URL per state, but actually a cow is a cow whether you call it 'cow' in english or 'vache' in french. And the URL '/cow' and the URL '/vache' is the same URL for those who have to deal with SEO in multilingual apps, they both use exactly the same state.

@mwarren2
Copy link

mwarren2 commented Feb 3, 2015

I tried your solution for hours, no go in my case. ui-sref worked nicely, but I couldn't get the view to show 'TypeError: state.ownParams.$$equals is not a function' kept appearing. Presumably some mistake of mine. I'll have to go back to my original, complicated solution for now.

@gerisztein
Copy link

Yeah. I have the same achievement using ui-sref but when I try to access the page through the URL it is redirected to the otherwise state. I don't really know what to do, I've tried many different approaches but...
Actually, I think I'll give up using this service and adopt another solution. It was pretty useful for me until I had to set the root / without the locale variable.

@mwarren2
Copy link

mwarren2 commented Feb 3, 2015

Hi Lucas,
Yeah, we're working with stuff that's a bit too close to the cutting
edge, I guess...

Il 03/02/15 17:45, Lucas Gerisztein ha scritto:

Yeah. I have the same achievement using ui-sref but when I try to
access the page through the URL it is redirected to the otherwise
state. I don't really know what to do, I've tried many different
approaches but...
Actually, I think I'll give up using this service and adopt another
solution. It was pretty useful for me until I had to set the root /
without the locale variable.


Reply to this email directly or view it on GitHub
#185 (comment).

@mwarren2
Copy link

mwarren2 commented Feb 3, 2015

I had another brilliant idea (hack I suppose). See the following code:

    $stateProvider.state('it-chiantiClassico', {
        url: '/it/chianti/vino/chiantiClassico',
        templateUrl: 'modules/core/views/chiantiClassico.client.view.html'
    });
    $stateProvider.state('en-chiantiClassico', {
        url: '/chianti/wine/chiantiClassico',
        templateUrl: 'modules/core/views/chiantiClassico.client.view.html'
    });

Since at the moment I have to create a state for every language URL I could put the language in front of the state name, such as 'it-chiantiClassico' and 'en-chiantiClassico' and then change the ui-sref on the fly when I click to change languages.

ui-sref="{{currentLang}}-chiantiClassico"

But ui-sref doesn't listen for changes so that can't work.

@gerisztein
Copy link

Actually this is exactly the approach I'm working on.
I generate all the states dynamically with the language prefix also.

Tomorrow I'm going to work on a custom directive to emulate ui-sref behavior.

Se riesco a finirlo te lo faccio sapere ;)

@mwarren2
Copy link

mwarren2 commented Feb 4, 2015

Thanks Lucas for your reply.

I suppose another idea along the same lines would be a state url with params:
url: ':slashPlusLang/:someHyphenatedPhraseInLangForSeo/'

This would need only one url per state, so it might fit in with ui-router just as it is, but slashPlusLang is not particularly elegant, although it might to be the only way if there is a root lang which doesn't appear,
and of course ui-sref would need a $watch on it for when the language changes.

@vizo
Copy link

vizo commented Feb 4, 2015

Thanks for all your thoughts!

I was also trying a lot of other approaches before i came to this, but it seems that all others are more complicated on the end. You will figure it out when your app gets bigger.

@mkoczorowski
Copy link

@vizo any idea about the bug we are descibing above ? We would appreciate your help

@mwarren2
Copy link

mwarren2 commented Feb 8, 2015

I've reached a simple solution to the multi-language problem which is suitable for my needs.
It uses a nested state solution with the locales at the root.

It is not completely DRY: templateURLs are still repeated, but nested states are still possible.

Ok, my site is not going to be complicated so I haven't tried this with more esoteric combinations, but it looks like I have stayed within normal ui-router semantics.
So to those that are interested I include my routing below.

There is an abstract state for each locale at the root, and in my example there are two locales, 'en' and 'it'.
I also found that I had to provide a template containing just a ui-view for each abstract state, otherwise nothing appeared.

The root abstract states have urls like '/it' - or, in the case of english, a blank string '' - because on my site 'en' locale has no url prefix.

Now in the urls I can put what I like for SEO, (see the chiantiClassico states)

    $stateProvider.state('en', {
        url: '',
        abstract: true,
        template: '<ui-view/>'
    });
    $stateProvider.state('it', {
        url: '/it',
        abstract: true,
        template: '<ui-view/>'
    });

    $stateProvider.state('en.home', {
        url: '/',
        templateUrl: 'modules/core/views/home.client.view.html',
    });
    $stateProvider.state('it.home', {
        url: '/',
        templateUrl: 'modules/core/views/home.client.view.html',
    });

    $stateProvider.state('en.chiantiClassico', {
        url: '/chianti/wine/chiantiClassico',
        templateUrl: 'modules/core/views/chiantiClassico.client.view.html',
    });
    $stateProvider.state('it.chiantiClassico', {
        url: '/chianti/vino/chiantiClassico',
        templateUrl: 'modules/core/views/chiantiClassico.client.view.html',
    });

Now, there's the problem of ui-sref. On a multi-lingual site it's basically unusable because it won't respond to an change of the language - e.g. from 'en.home' to 'it.home'.

So, to solve the href problem I considered a couple of solutions.

  1. Calling a function in ng-href from the view that called $state.href(). This would be tidy, but it has the usual $scope.apply() problem of being called multiple times.
  2. Creating an adhoc 'urlsInLanguage' map with state names as keys and urls as values, filtered for the current locale from all states in $state.get() each time the language changes, which is not particularly often. Then I do all the look-ups in my header from the map using ng-href.
    data-ng-href="#!{{urlsInLanguage.chiantiClassico}}".

Code for making the map when the language changes;

        $scope.currentLang = $scope.currentLang || 'en';
        $scope.urlsInLanguage = {};
        var states = $state.get();

        //filter for all states that have currentLang in them
        states.filter(function(currentState){
            if(currentState.name.substring(0,2) === $scope.currentLang){
                return currentState;
            }
            return null;
        }).map(function(thisState){ 
            //put each url value with stateName key into our object map
            //the key should be just the basic stateName, we don't want the locale on the front
            $scope.urlsInLanguage[thisState.name.substring(3)] = $state.get($scope.currentLang).url + thisState.url;
        });

@gerisztein
Copy link

Hi! I'm using another method as I already have lodash running on my project I've decided to create the states dynamically using an object to set the templateurl, controllers and other settings for each state and the forIn function. I've reduced 200 lines in my code and I think it's kinda easy to maintain.

I already have some configurations for the routes for each state and an object dynamically generated that shows me which locales are available to use. In addition to this, I have something like this:

the object:

var states = {
  'home': {
    'controller': 'HomeCtrl',
    'templateUrl': 'home.html',
    'params': {}
  }
}

the function

_.forIn(states, function(prop, state) {
  _.forIn(available, function(props, locale) {
    $stateProvider.state(stateName, {
      data: {
        analytics: analytics,
        title: title
      },
      params: prop.params,
      resolve: resolve,
      url: url,
      controller: prop.controller,
      templateUrl: prop.templateUrl,
    });
  });
});

It solved my problem. It's not the prettiest solution but works like a charm.

@eddiemonge
Copy link
Contributor

@fxck
Copy link

fxck commented Jan 27, 2016

any updates on this @vizo @gerisztein @mwarren2 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests