Skip to content
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

ui-router and reusable directives #95

Closed
stasgrom opened this issue Apr 19, 2013 · 11 comments
Closed

ui-router and reusable directives #95

stasgrom opened this issue Apr 19, 2013 · 11 comments

Comments

@stasgrom
Copy link

Hi,

we have several reusable components in our application, which we implemented as directives. We can plant such a component in some place in the application, and associate a state (and subsequently a specific URL) with it, so it becomes visible as soon as the URL is entered (or some user action is performed, leading to transition to the state).

The problem I'm having is the following: we want this directive to add new states to the system, relative to the parent view it is displayed in. But I am having trouble doing it. The issue is I seem to need a way to add new states/routes dynamically, when the directive becomes active (i.e. its controller is called). So I thought I could provide the directive a name of the parent state as a parameter and it could bind new states to this parent state, however:

  1. The $stateProvider is not available for dependency injection in the controller. I kind of bypass this problem by providing a service that returns the $stateProvider, and inject this service, but:
  2. Adding new states to the $stateProvider doesn't seem to work at this stage. It seems like the new states are not properly registered.

So my question is: are there any good practices on using the ui-router with reusable directives that want to provide their own states? Are there any forward looking thoughts on providing a way to dynamically register new states during the run of the application and not only during its initialization?

Thanks.

@jeme
Copy link
Contributor

jeme commented Apr 19, 2013

I don't quite understand the use case here.

Letting directives them self add those child states I fear tings would become to fragile, and in turn then to complex to understand and debug what is wrong when it doesn't work... Especially when you begin to talk about URL bound states, because those states wouldn't be defined unless you had visited the parent at some point, in which case the whole thing becomes a bit mood...

So a big part of the core concept would have to be done very differently than what it is now.

In any case, to me it seems that defining all possible states up front would be the most healthy approach. But if we where to be able to add states dynamically, directives would be the last place to add them from, because of the above caveat.

@stasgrom
Copy link
Author

Hi @jeme,

I'm not sure I understand the caveat. You write: " Especially when you begin to talk about URL bound states, because those states wouldn't be defined unless you had visited the parent at some point".

But even when I define the states up-front, in a way that you say is the correct one, I define the hierarchy of states which can be quite a deep, without first visiting any parent state. It's only later in time I actually activate one of the states. Maybe I didn't quite understand your point.

I get a feeling that if I want to benefit from URL-bound states, then I should design my application using views and not directives, and define all the states up-front. If I want to design some reusable internal view that adds URL-bound states to the application, then I should design some internal API the purpose of which is to give this view the opportunity to add the states during the initialization of the program, and not its actually running. Is this an accurate statement?

Thanks.

@stasgrom
Copy link
Author

BTW the main motivation here is "reusable" components (whether directives or views). These components can be used in different places in an application, and they internally have some complexity and navigation capability that merits usage of URL-bound state.

For example a view that displays aspects of some application item, separated in several categories so that the user can switch between those categories using tabs. We essentially want to associate a separate URL with each tab so that the URL ill be self contained, and I would be able to use history in the browser as well as send the URL to somebody else and be sure he will open the application in the same view.

@jeme
Copy link
Contributor

jeme commented Apr 19, 2013

@stasgrom ill see if I cant clarify a bit...

Say you have the states and mapped to the absolute urls:

'home': '/'
'users': '/users'
'users.list': '/users/list'
'users.details': '/users/:userid'
'users.details.friends': '/users/:userid/friends'

If you then as part of 'users.details': '/users/:userid' loads a directive that would create the additional states:

'users.details.address': '/users/:userid/address'
'users.details.interests': '/users/:userid/interests'

The problem becomes that those only gets defined if you have visited users.details first and/or possibly users.details.friends... However, if you just opened up a new browser and typed in:

"/users/312/address" that would trigger the "otherwise" route, and not end up loading the users.details.address state, as it is not defined at this time.

@stasgrom
Copy link
Author

Thanks for the clarification @jeme, I understand what you mean now.
This indeed convinces me that the definition should be moved to the initialization phase.

@jeme
Copy link
Contributor

jeme commented Apr 19, 2013

One way of leveraging this with services, would be to write providers for them and then run a config for them, depending on the requirement and flexibility required we could say:

var $CustomProvider = ['$stateProvider', function($stateProvider) {
    this.buildCustomSubstates = function(parantState) {
        $stateProvider
            .state(parantState + ".myCustom", { /* ... state stuff ... */}
    }

    $get = ['service_dependency', function(service_dependency) {    /*return a service*/    }];
}];
angular.module('myReuseableThingConfiguringStates', [ ... dependencies, ui.states for one ... ])
    .provider('$customProvider', $CustomProvider);

Then run that as:

angular.module('myAppUsingReuseables, [ ... dependencies, myReuseableThingConfiguringStates for one ... ])
    .config(['$customProvider', function($customProvider) { 
        $customProvider.buildCustomSubstates("some.parent.state");
    }])

Or along the lines of that, then you could depend on the "$custom" service in directives etc. maybe if you need to connect something.

If you wan't users to be able to create page content where they add tabviews etc that can be targeted from an url, I think they would be better off using some implementation that would leverage search parameters instead... But in general, when it comes to "user created content" then advanced things and connecting them to the url becomes kind of complex and difficult to provide a "fit for all"

In any case, I hope you got the idea.

@timkindberg
Copy link
Contributor

@stasgrom I'm interested in how you will end up solving this issue, because I think it will be a common one.

@ksperling
Copy link
Contributor

@stasgrom Yes, this is an interesting issue. Essentially what you're asking
for is a framework where view state is automatically serialized and
deserialized to/from the URL. One advantage of such a framework as you
point out would be the ability to have large reusable components that have
multiple states internally.

On the other hand you'd likely end up losing control of what the URLs look
like, as well as the ability to understand the totality of the UI state as
a state machine and to navigate to a particular state via an atomic
transition. É.g. instead of $state.transitionTo('users.details.friends', {
... }) you would have to tell the top level view to load a 'user details'
view, and then tell that view via a view-specific API to switch to its
'friends' state.

While I think such a framework is possible, I'm not sure it's that
desirable; developing on it would probably a bit like writing a JSF or
swing app (or maybe asp.net, but I'm not familiar enough with it to reallz
make that comparison). In my experience, reuse at the level of entire
application sub-sections (e.g. a browser for items based on categories)
tends to be quite rare; are you really going to have 2 or more such areas
of the application for browsing different things, yet somehow they are
going to behave exactly the same? At this level of the application, where
you're actually dealing with the specifics of your app and business rules,
it's quite likely that you'll want e.g. your user browser to work slightly
differently from a restaurants or movies or car models browser.

That said, I think there is some scope for enabling full two-way behind
between some state parameters and the URL, and custom directives, so you
could e.g. do something like

<div my-sortable-list-directive order-by="$stateParams.sort">

so that changing the sort order via the UI would then cause the directive
to update $stateParams, which would then automatically propagate into
$location (presumably via $location.replace(), as you'd likely only want
history entries created when theres a state transition), and in the other
direction changes of those parameters would not cause a state transition or
views to be reloaded either -- the reloadOnSearch=false feature that has
been discussed on a few issues already. Maybe a better description for
those parameters (rather than reloadOnSearch=false) would be "dynamic" or
"internal" state parameters (as opposed to the default external parameters
where a different value means a completely new and different instance of
the state will be created)

@stasgrom
Copy link
Author

Hi guys,

thanks for the suggestions. Basically I used what @jeme suggested, i.e. registered an object with a provider that I can use to dynamically add the children states relative to some parent that is passed as a parameter. This extra step to make the reusable directive states-aware is an overhead we are ready to pay.

In addition the directive itself should be given the parent state as a parameter (so it's visible in the controller) because if I want to use the $state.transitionTo() API in the directive's controller (for example if the directive is rendering tabs, activation of which transfers to another state), I need to provide the correct state name relative to the parent. This is crucial if the directive is used more than once in the same application in different places.

Regards.

@nateabele
Copy link
Contributor

@stasgrom Thanks for posting back with your resolution. Closing this as resolved.

@842454360
Copy link

I 'm trying to use the ui-router as a directive provider.So I can use it in my controler.But the object what I got $RouteProvider is incorrect in MyControler.How should I do ?
Code :
module.directive('myApp', function () {
return {
template: template,
restrict: 'E',
replace: true,
scope: {},
controller: ['$scope', '$state',MyControler]
};
});

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

6 participants