-
Notifications
You must be signed in to change notification settings - Fork 27.5k
Multiple named ngView directives and attaching views to routes #1198
Conversation
Usage and purpose ================= Routes can have a view attached via the `view` property. Views can be named like so: <div ng-view="subsection"></div> Then you could create a route which will update only this view: $routeProvider.when('/somepath', { templateUrl: '/section-template.html', view: 'subsection' }); When the url is changed to '/somepath' only views named 'subsection' would be updated. This should be used for: - subsections on the page which should respond to some routes, other views would not be changed - modal dialogs with their own url, like Pinterest, Trello etc. Different Cases =============== - a view has a name, but the route does not have a view set * the view is not updated * the route is considered to be a main route * the view is a secondary view. - the route has a `view` property, but a view is not named * the view is not named * the route is considered to be secondary * the view is (the) main view and would update only for main routes - the route has a `view` property and the view is named, but the name does not match * the view is not updated * the view is secondary * the route is secondary - it propably updates another view - the route has a `view` property and the view is named with the same * the view is updated regarding the template and the controller associated with the route * the route and view are secondary, but they are bound together If the location is changed to a route which has a view attached, but such view does not exist then AngularJS will do nothing and no view will be updated. Implementation ============== The whole thing is happening in the `ngView` directive on `$routeChangeSuccess` event. When the `update` function in `ngView` is called the view is updated only if the current route and the view match. `$route.last` is set to the previous route when a named view is updated. This could be used from a developer to recover the location to a previous path corresponding to a main route. For example if you have a modal dialog with its own url and a matching route, when the dialog is closed you would want to recover the url to the location of the previous main route. `$route.interpolate()` is exposed so it could be used in such scenario to build the path using `$route.last.params`.
Why not user ng-include in your case? |
Because I want to use the power of routes, route parameters and so on. In my specific case I have user profiles opening in a modal dialog. They have urls like: At first I tried to use |
Thanks for your contribution! In order for us to be able to accept it, we ask you to sign our CLA (contributor's license agreement). CLA is important for us to be able to avoid legal troubles down the road. For individuals (a simple click-through form): For corporations (print, sign and scan+email, fax or mail): |
Signed :) |
Fails with .otherwise({
redirectTo: '/something'
}) At least, in hashbang mode. |
@anotherpit I have tried it in HTML5 mode with: .otherwise({
redirectTo: '/'
}) I'll look into that. |
I slightly tweaked function update(event, next, last) {
var current = $route.current,
route = current && current.$route,
locals = current && current.locals,
template = locals && locals.$template;
if ( !route && attr.ngView ) {
return;
}
// …and so on
} Works fine for me. Sorry, can't pull it on GitHub right now. |
Is this part of any stable release of Angular? if no, is there any plan for it? |
@vishal1shukla2 It is not part of a stable release or work from the AngularJS team. This is just a pull request which aims a review from the core team to be implemented in the core. There were some discussions in the Google group about such feature and I've tried to implement it with the same interface as the core team wanted. |
Another +1 for merging this. |
Not stable enough to merge. I posted a bug description earlier. Moreover, the given solution doesn't let you configure several regions (ngViews) on the same route change, which is pretty expected when you deal with a complex layout (i.e. several named ngViews). It would be great to configure routes like this: $routeProvider
.when('/index', {
// set up content region
'content' : {
templateUrl : 'indexPage.html'
},
// clear pop-up region
'popup' : {
template : ' '
}
})
.when('/login', {
// set up pop-up region
'popup' : {
templateUrl : 'loginPopup.html'
}
// doesn't change content region
})
.when('/logout', {
// traditional redirect
redirectTo : '/index'
}); |
Link? |
@hkdobrev Thanks for the patch! But to me it seems that the more common scenario involves a hierarchy of routes, not just arbitrary control over various parts of the document. For example, an application has top-level sections with URLs: /dashboard and /news. The main outer structure of the page stays the same (header, logo, footer, sidebar), but the central ngView gets reloaded when we navigate from /news to /dashboard. So far, this works out of the box. Under /dashboard, there's one or more child ngViews that get populated with longer URLs like /dashboard/articles?sort=latest or /dashboard/services?filter_topic=seo. So, here's my ideal scenario when a URL like /dashboard/articles?sort=latest is clicked.
Before I learned about AngularJS, I had a custom implementation of this using jquery.address and a bunch of ugly custom code. But AngularJS has such elegance to it - do you think a hierarchical routes/views implementation with AngularJS is possible? |
@orlenko Though such scenarios are pretty common, they also are pretty dynamic and usually heavily rely on exact application architecture and layout, so I suppose |
Sigh... Okay :( |
@anotherpit I'm also struggling with similar routing issues as those described by orlenko. If they are so common, why is it so hard to implement them? Or why is the solution with event handlers not described in one of the angular tutorials? I come from backbone and routing there is way more flexible than the current angular implementation. I would like any controller to have acces to the $routeParams, even when they are not registered with the $routeProvider service. Unfortunately $routeParams is now an empty object for controllers that are not registered with the $routeProvider. I believe it's a fundamental flaw to tightly couple the routing functionality with template loading and a single controller. Especially when there is only a single ng-view directive allowed in the whole page. +1 for merging hkdobrev's implementation (once stable) |
This is an urgent issue! It makes a lot of sense. I have a similar need: https://groups.google.com/forum/#!topic/angular/ZhdpiuR8D_c By the way: $routeProvider.when('/somepath', { In my opinion it makes no sense for angular to exist without this feature. Without it, apps will be monolithic and not optimized for the "closed for change open for extension" principle since every time we need new sections, we will have to edit the only view there is. If multiple views were possible, we could better control app upgrades. Please consider this! |
Your opinion comes out pretty harsh ramaralo. While I see named views as a welcome addition, there are quite a few ways to accomplish similar functionality. The suggestions listed above or listening on $routeParams injected from an app-level config module would be a good place to start. Good luck vanilla js'ing your "extensible multiple team plugin architecture" - of course there's a reason for Angular to exist. It's awesomeness squared and has really taken out a lot of the complexity I used to have in my projects - keep up the good work guys. |
@mordendk while it might be true that his opinion comes out harsh, but sometimes things just have to be said that way... I also find it strange that when "drill-down" is quite common, that this pattern isn't actually supported better than to reload the entire view and alternate on includes. This gives the page a clunky feel, especially since the view reload (at least in my experience) is so noticeable. Routing is so fundamental to me, so giving a much more flexible support for it makes sense to me. And besides, it seems that many of the other MVC frameworks is able to do just that, so why shouldn't Angular make it a priority to add features here? |
@mordendk I agree with you. Sorry, it wasn't meant to be so harsh. And I did find a solution (at least I think I did, because the project is in stand by, now). Thank you. ;) |
@ramaralo could you please share your solution with us? Would be much appreciated. |
@skrivanos Well, I haven't yet fully implemented it. Basically my approach relies on this ideas:
myNameSpace.angularApp = angular.module('myModule', [], function($routeProvider, $locationProvider) { myNameSpace.angularApp.config(function($routeProvider){ And then, after all the files have been loaded, I bootstrap manually: angular.bootstrap(document, ["myModule"]); as in: http://docs.angularjs.org/guide/bootstrap Let me know if this was of any help to you. Best regards, |
@ramaralo And when the URL changes you'd change/reload/re-render the whole As I said earlier, imagine a Trello-like or Pinterest-like app. The main |
@hkdobrev Yes. Any solution will always be a workaround. Most certainly I'm not seeing the all picture, but if you want to open, let's call it an item, on a modal dialog of your "Pintrest" app, without a view reload, couldn't that be achieved by a specific directive? Imagine a directive called popup, for example activated by a variable on your model like popupOpen = true | false. What I mean is that if the feature you need doesn't need to change all the view, I think it should be implemented as a directive. What do you think? |
@ramaralo for me personally this isn't to solve a dialog use-case for me (2), but rather the sub-section case (1), yes it can properly be implemented as a new directive. But since we wan't URL's to reflect where we are as in:
I think it will either cause for confusion or just be straight up strange if we don't add some support for it in the routing, so that we can express what we wan't. Especially because even changing from e.g. /item/123 to /item/432 would call for a full reload of the NG view, which then would include the "category" lists etc. And that is completely unneeded. Right now I have my own solution, solved with a sub-route pattern as such:
As I found that more "expressive" towards what I wanted. And then I have left it to the controller to react on "subroutes" for maximum flexibility on what happens on those... Not a solution I am happy with atm. because I also manually need to react to the specific subroute on a full reload (e.g. when we are on /blog/category/myCategory and hits F5) Also that custom data annoys me at the moment. In any case, this still means I have had to add changes to "route.js"... (and from here on out I use ngInclude to dynamically change the template of the core content, but keep the category lists etc. intact) |
@jeme I can understand you point. This is were not having named views will have different impacts on different projects. On my case I think I would rather have a full view refresh since either way I would not be satisfied with the solution. But you got me confused with that subroutes attribute. It is not mentioned on the angular docs (http://docs.angularjs.org/api/ng.$routeProvider)... Can attributes be added to the second argument of the "when" method? If so, will they be available on the conttroler? |
+1 to merge |
@ramaralo The sub route pattern as you see it in my example did require changes to the original routing as I mentioned, so I have a fork... But as for your question regarding if we can add our own "objects" to the route, then the answer is yes, you can do that and then obtain them in the controller through depending on $route, and then go through $route.current.$route and that will be that whole object you define as the second parameter to when. (except for routes defining a redirect to I think) If you listen to the route change and route update events, whose will also be that object.
You should be able to catch those on a scope and react as well... So that way, the feature as proposed here could properly be added purely thought custom view directives who listen on that event, looks if they are the target, and if so then respond to the event... That leaves the initial loading though, these views would need a "initial state". |
👍 A very awesome feature that is missing. |
Thanks for all this hard work, but I don't think this solution fits our vision. The multiple view problem is complex since we want to preserve deep linking. Which means that each view needs to be controlled by a portion of the URL. In your current implementation as the URLs change different views get updated, but then refreshing the page couses all but the current view to be destroyed. This prevents deep linking, and it means that the URL does not contain all of the view state. My gut feeling is that this will require a complete rethinking of the $route service to support this use case. I am sorry I can not provide a more detailed analysis of what a solution would look like, I only know that the current proposed solution breaks certain things. We do want multiple view feature, but unfortunately we don't think this approach would work. I am going to close this for now. If you can address these issues feel free to reopen. |
In the mean time... checkout my fairly kickass and highly versatile solution: http://plnkr.co/edit/vWFliiEH1lJRItoxoeMk?p=preview and hit the blue fullscreen button in the top-right corner. |
@ProLoser I think that"poor workaround" is well known, but ones you put to much into the top level view (ng-repeats etc) it becomes increasingly visible that that portion is also reloaded, that's what is undesirable. (and for some it will properly also generate extra server requests) @mhevery thanks for the update, I agree that this requires some rethinking, is there somewhere were we could have that dialog where the angular team would be ensured to participate with input and their own ideas, would the mailing list be that place? Especially people with a deep connection into routing and how it works. |
@mhevery Though I agree my PR is far from ideal, I want to clarify that it supports deep linking. For example if you have the following routes: /sections (with no view attached a.k.a main content) Then if you link to a /sections/:section URL you will get the sub view. When you close it in some way (back button, closing modal or other navigation) and go to /sections, your main view will load then. Though I have some more changes to the AngularJS code beside that on the website I have developped with the framework, you can check that out: The website shows a feed of users as main content and it opens user profiles in a popup. When you open/close a user profile the main view is not reloaded. The $viewContentLoaded event is fired, but with an additional Probably I will add this functionality to the pull request when I have the time. |
@mhevery, if you are rethinking the $route service to include nested views I'd recommend having a look into this: It's flexible enough although easy to understand and to implement. May be it could be useful have some built-in support from AngularJS... |
@lrlopez that is now the fourth version of nested routing I've seen. I've done some poking around and testing and realized the shortcomings of my approach and the main advantage to some of these nested routing systems. I have a proposal for anyone interested: There have been many approaches to nested routes and the core team is still trying to figure it out. Would anyone like to join the AngularUI org and form a team specifically for focusing a solution for nested routing? There is a lot of discrepency and inconsistency across all the solutions and it's not quite clear which one is the easiest or best to use, so it would be nice if I could get by a solution I stand behind, especially if it merges everyone's efforts. |
For those that care: https://github.com/angular-ui/ui-router/ We started a new project over at AngularUI to develop a communally accepted best solution for Nested Routes. Everyone is free to join the discussion and we want more team members! If we can refine a good solution then it may help the core team by giving them a stable solution that might be mergable in the future. |
+1 for some kind of solution to this basic problem. |
EmberJS has a very interesting nested route design http://www.thesoftwaresimpleton.com/blog/2012/08/20/routing_update/ |
@coli you may want to refer to the link I posted earlier as it follows very closely the EmberJS strategy and tries to collect the best of all solutions. |
@ProLoser You mean ui-router? I just started looking at it and so far I'm very impressed. We have a difficult requirement of 3rd party extension into our UI, and the named view concept + inherited(?) stateParams is looking very good right now. It also look like nested resolve() function is a good solution to flickering as well. (won't cover every case, but most cases) |
If I'm not wrong, the idea behind |
I think a better approach is to remove ng-view/router from the default angular distro. And just let people pick their own router implementation. Ui-router is fantastic, but it's a lot more complicated than expected for basic scenarios. |
IMO I think a routing system is essential for any complex web app, and if Angular intends to be a solution for building complex web apps it should include this core feature. I like Ui-router, but I do agree that it's probably overkill for general use. |
If you guys can provide feedback on ui-router on ways we can simplify things it'd be greatly appreciated. We are quite open to input from the community as that's how the project was started. It is currently back-compat with the existing router. |
I may be biased, but I don't think ui-router is too complex for simple scenarios. You could just use this way: (taken from the wiki) $stateProvider
.state('contacts', {
url: "/contacts",
templateUrl: 'contacts.html'
}) That would replace From there you could progressively enhance the functionality using parameters, controllers, nested states, etc. Also, as @ProLoser said, their is an optional back-compatibility module that could allow using the new system with existing projects. |
Is there a mailing list for ui-router?
|
Sorry Eddie, it seems I didn't explain my point well enough. You're completely right, ui-router can be complex, very complex, but those complexities come from using the advanced features like nested/abstract states or multiple & simultaneous views. I was talking about simple scenarios, similar to the ones covered by the Angular original routing system. My bad :) I agree with you. There is a lot of work ahead, especially on documentation. Most of your problems could have been solved by a good tutorial:
About the mailing-list, there is one: https://groups.google.com/forum/#!categories/angular-ui/router . It hasn't much activity yet, though. May be you could get a faster response opening an issue at the repository: https://github.com/angular-ui/ui-router/issues I really like the work that those guys have done so far. Please, don't hesitate in giving them feedback, it can only make ui-router better for everyone. |
@ProLoser @lrlopez I also agree it' couldn't be simplified any more than it is. I think the complexity people feel it has may be due to the example incorporating backwards compatibility. Perhaps it may worth incorporating an example without backwards compatibility and a comparison to Angular's default implementation. Angular JS Route angular.module('app', []).
// App configuration.
config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/', {
template: '<div>Home Page</div>',
controller: 'HomeCtrl'
})
.when('/contacts', {
templateUrl: 'contacts.html',
controller: 'ContactCtrl'
});
}]); UI Router angular.module('app', ['ui.compat']).
// App configuration.
config(['$stateProvider', function($stateProvider) {
$stateProvider
.state('home', {
url: '/',
template: '<div>Home Page</div>',
controller: 'HomeCtrl'
})
.state('contacts', {
url: '/contacts',
templateUrl: 'contacts.html',
controller: 'ContactCtrl'
});
}]); The only real difference being that we name the state and provide the url in the params object. Maybe it would also be worth taking the example from http://docs.angularjs.org/tutorial/step_07 and doing an comparison of the As well as explaining that if people want to use $urlRouterProvider
.otherwise('/'); |
+1 for a solution implemented in the core. i'm a angularjs "tester" (rails/backbone user, but i started to evaluate other frameworks) i would not use angularjs if i had to start with doing workarounds for that basic problem. i have to solve exactly that problem: http://angular-ui.github.io/ui-router/sample/#/contacts/42. other than that, angularjs feels pretty awesome! |
how it's going? |
I've created https://github.com/cotag/ngRouteNames that provides much of this functionality whilst reducing the complexity. State is represented in the URL, links are able to change a subset of the state or url and sub-views can be defined by ng-switch on $routeParams Done with minimal overhead and uses the existing routing system with a bit of extended functionality |
Usage and purpose
Routes can have a view attached via the
view
property.Views can be named like so:
Then you could create a route which will update only this view:
When the url is changed to '/somepath' only views named 'subsection' would
be updated. This should be used for:
other views would not be changed
Different Cases
view
property, but a view is not namedview
property and the view is named, butthe name does not match
view
property and the view is named with the sameassociated with the route
If the location is changed to a route which has a view attached, but
such view does not exist then AngularJS will do nothing and no view
will be updated.
Implementation
The whole thing is happening in the
ngView
directive on$routeChangeSuccess
event. When theupdate
function inngView
is called the view is updated only if the current routeand the view match.
$route.last
is set to the previous route when a named view is updated.This could be used from a developer to recover the location to
a previous path corresponding to a main route.
For example if you have a modal dialog with its own url and a matching route,
when the dialog is closed you would want to recover the url to the location
of the previous main route.
$route.interpolate()
is exposed so it could be used in such scenario tobuild the path using
$route.last.params
.