Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

$RouteProvider should activate controller, even when no templateUrl or template is passed #1838

Closed
lukasolson opened this issue Jan 19, 2013 · 29 comments

Comments

@lukasolson
Copy link

Sometimes, you don't want to have a template when changing routes. You just want to activate the controller. Currently, it's not possible to do this in Angular.

@leeola
Copy link

leeola commented Jan 19, 2013

👍

Is there a commonly accepted pattern for solving this perhaps? Something akin to writing your own router?

@pkozlowski-opensource
Copy link
Member

@lukasolson This sounds really strange to me. Could you elaborate on your use-case?

The thing is that the role of a controller is to set-up a scope for a partial (coming from a template or templateUrl) so data and behavior can be available in a partial. If there is no view there is not much need to setup a scope (there is no one to take advantage of it).

@lukasolson
Copy link
Author

See http://stackoverflow.com/questions/14411740/how-to-route-without-a-templateurl/

My use case is that I have a route that makes a request to the server and routes to one of two possible routes based on the response. Is there a better way to do this?

@abh
Copy link

abh commented Jan 19, 2013

Why not just add a small "Loading..." (or empty!) template snippet to the intermediary controller?

@leeola
Copy link

leeola commented Jan 19, 2013

Why not just add a small "Loading..." (or empty!) template snippet to the intermediary controller?

But that's changing the view for no reason. The concept is to be able to route something that has no view.

This is mostly just an event (like a click-logout event), but the importance of this is that it is being routed, thus supporting deep linking for the /logout action.

I'm not saying what you suggest will not work, it's just that what you suggest is more of a hack. This specific route has no view, and giving it one is a workaround to a problem.

@lrlopez
Copy link
Contributor

lrlopez commented Jan 19, 2013

You could add a listener to the $routeChangeSuccess event (have a look into http://docs.angularjs.org/api/ng.$route). Inside the event handler you should be able to do your magic (i.e. querying the server and changing the path when the promise is returned) if the route matches.

@petebacondarwin
Copy link
Contributor

Have you tried putting the code in a resolve handler for the route?

On 19 January 2013 20:30, Luis Ramón López [email protected] wrote:

You could add a listener to $routeChangeSuccess events (have a look into
http://docs.angularjs.org/api/ng.$route). Inside the event handle you
could (i.e. querying the server and changing the path when the promise is
returned) if the route matches.


Reply to this email directly or view it on GitHubhttps://github.com//issues/1838#issuecomment-12460636.

@lukasolson
Copy link
Author

But where would I put the listener on $routeChangeSuccess or the resolve handler? In a controller?

@petebacondarwin
Copy link
Contributor

Inside the resolve handler you could change the location if necessary.
These handlers always get run before the template is instantiated.

On 21 January 2013 15:52, Lukas Olson [email protected] wrote:

But where would I put the listener on $routeChangeSuccess or the resolve
handler? In a controller?


Reply to this email directly or view it on GitHubhttps://github.com//issues/1838#issuecomment-12503461.

@lukasolson
Copy link
Author

Ah, wasn't aware of resolve handlers. I guess you could put it there. Something like

.when("/", {resolve: {redirect: RedirectController}})

where RedirectController is the function that actually redirects??

@petebacondarwin
Copy link
Contributor

Yes something like that but I would not call it a Controller! It will be
more of a service.

.when("/", {resolve: {redirect: redirectService}})

On 21 January 2013 18:21, Lukas Olson [email protected] wrote:

Ah, wasn't aware of resolve handlers. I guess you could put it there.
Something like

.when("/", {resolve: {redirect: RedirectController}})

??


Reply to this email directly or view it on GitHubhttps://github.com//issues/1838#issuecomment-12510495.

@petebacondarwin
Copy link
Contributor

If you are happy with this then we should close this issue. Let me know if you think it should be reopened for any reason.

@lukasolson
Copy link
Author

Sounds good. Thanks for all your help.

@leeola
Copy link

leeola commented Feb 19, 2013

@petebacondarwin @lukasolson

I'm noticing a problem with this method. It seems when you use the resolver as a method to trigger logic (such as a redirect or logout handler), it can only occur once.

If my understanding is right, this is because the service is being loaded so that it can be injected into the controller, but it is only ever loaded once.

This seems like a rather large flaw in this usage.. unless i am doing something wrong. Thoughts?

@lukasolson
Copy link
Author

Could you post kind of a code snippet of how you're doing it, @leeolayvar?

@ThomasDeutsch
Copy link

My call: http://localhost:9000/#/?editMode=1

as we all know, this is working:

$routeProvider
      .when('/', {
          controller: function($routeParams, $location) {
            if ($routeParams.editMode == '1') {
                $location.path('edit'); <-- change to http://localhost:9000/#/edit
            }              
          },
          templateUrl: 'views/nothing.html'
      })

But this is not:

      $routeProvider
      .when('/', {
          resolve: {
              redirect: function ($routeParams, $location) {
                  console.log($routeParams);  <-- this will return Object{}
                  if ($routeParams.editMode == '1') {
                      $location.path('edit'); <-- change to http://localhost:9000/#/edit
                  }
              }
            }
          }
      })

It seems that in the redirect function, the $routeParams get not injected properly.

And then i found out that i have to use $route instead of $routeParams
oh, i get it... i need to do this:

      $routeProvider
      .when('/', {
          resolve: {
              redirect: function ($route, $location) {
                  if $route.current.params.editMode == '1') {
                      $location.path('edit');    <-- change to http://localhost:9000/#/edit
                  }
              }
            }
          }
      })

And this my friends works very well.

@leeola
Copy link

leeola commented Feb 23, 2013

@lukasolson

Well i spent forever trying (and ultimate failing) to get a working example of this on Plunkr/Fiddle (hell, even the ngView Doc Example is broken lol), but i played more with my own code and figured it out!

In my code that was not working, i had roughly this:

$routeProvider.when('/foo', {
  resolve: { foo: 'fooService' }
})

and later in my code i defined the service, which was roughly this..

app.factory('fooService', [$http, function ($http) { //do stuff }]);

Well it seems this is the incorrect way to do it? If i ran the resolve like this, my //do stuff code was only executed once. All subsequent url resolves did nothing.

Why did i do it this way? Well, i was looking to keep logic code out of the routing code.

In the end, the solution seems to be to define the function in the route call itself. So, like this:

$routeProvider.when('/foo', {
  resolve: {
    foo: function ($http) { //do stuff }
  }
})

I am however a little nervous about injecting $http in the resolve handler like that. Is there a way to inject that in a minify-safe way?

@JustinTArthur
Copy link

After seeing these proposed solutions, the original poster's suggestion of allowing template-free controllers still looks cleaner to me. Conditional routing is a common necessity and yet almost no tutorial has dared to share how this has to be done in AngularJS right now.

The argument that a controller's role is to set up scope is a good one, but $scope is an injected dependency. If a controller doesn't need to set up scope, then in the DI frame of mind, we simply don't inject that dependency, right?

@jlmakes
Copy link

jlmakes commented Oct 1, 2013

I would welcome support for this; I too tried to implement a /logout route that executes it's controller with no template…

@nerdburn
Copy link

I'm dealing with this same issue and agree that routes that can point to controllers, without the requirement of a templateUrl would be beneficial.

@buunguyen
Copy link
Contributor

@julianlloyd I dealt with logout before, this is what I did to make it work w/o a controller or template.

 routeProvider.when('/logout', {
   resolve: {
     logout: function($q, $location, User) {
       var deferred = $q;
       User.logout(
         function success() {
           deferred.reject();
           $location.path('/login');
         },
         function error(error) {
           deferred.reject(error);
           $location.path('/');
         }
       );
       return deferred.promise;
     }
   }
 });

@jlmakes
Copy link

jlmakes commented Oct 25, 2013

@buunguyen Awesome, thanks for the idea; here’s my implementation:

 .when('/logout', {
        resolve: {
            logout: function ($q, $location, $http) {
                var deferred = $q.defer();
                $http.post('/logout')
                    .success(function (data, status, headers, config) {
                        deferred.reject();
                        $location.url('/login');
                    });
                return deferred.promise;
            }
       }
})

With Express handling POST requests to /logout as such:

app.post('/logout', function (req, res) {
    req.session.destroy(function (err) {
        if (!err) {
            res.send(200);
        }
    });
});

@andrew-aladev
Copy link

Sometimes, you don't want to have a template when changing routes. You just want to activate the controller. Currently, it's not possible to do this in Angular.

What do you meen "sometimes"? I want to do this always. Angular is written by wonderful, charming people. This is not surprising in company, that is fully populated by magestic elves.

Is there a commonly accepted pattern for solving this perhaps

oh lol. pattern driven development.

I dont know why people has tolerance about angulajs routing.

ng-view data will be replaced by provided template (url or string)

I can say that this is the most crappy abstraction that can be found. It's author should be adored and showered with flowers.

@andrew-aladev
Copy link

I have understand why people has tolerance about angulajs routing. Someone messes comments.

@caitp
Copy link
Contributor

caitp commented Feb 3, 2014

@andrew-aladev please don't make statements like "Its author should be killed and thrown out of the window." in the future, okay? That is not how things are done. The framework is open to criticism, but there is no place for mean-spiritedness on the bug tracker.

@cha0s
Copy link

cha0s commented Mar 2, 2014

Yeah this should totally be a thing.

Also guys above, I just wanted to note that you should call deferred.resolve() instead of deferred.reject() if there is no error.

@btoone
Copy link

btoone commented Jul 2, 2015

@leeola Thanks for posting about how using a service doesn't work. Saved me tons!

@petebacondarwin
Copy link
Contributor

@cha0s - I think they are calling reject because they want the route change to fail. But since they are changing the location anyway, I don't think this would make much difference...

@leeola - the reason that your version providing the name of a service to the resolve only worked once is that you are asking the injector to provide a service, which will contain the promise to resolve. Since all services are singletons in Angular, the injector will create this promise on first request but then return the same promise every time afterward. The way to do this better is to define a service that returns a function that will run the request and return a new promise every time it is called:

app.factory('fooService', [$http, function ($http) {
  return function() {
    return $http(...);
  };
}]);

Then in your resolve you can do this:

$routeProvider.when('/foo', {
  resolve: { foo: ['fooService', function(fooService) { fooService(); } } // note that we are actually calling the service in the resolve
});

which is minification safe.

@jlmakes and @buunguyen - it is worth noting that if you have a promise already, as in the result of calling $http you do not need to provide your own deferred:

 .when('/logout', {
        resolve: {
            logout: function ($location, $http) {
                return $http.post('/logout').then(function(response) {
                    $location.url('/login');
                    throw '';  // effectively rejects the resolve (but I don't think this is needed)
                });
            }
       }
})

@Artur93gev
Copy link

I have the same issue, but I'm lazy-loading modules using route resolve.
So I made a service that is helping router to activate the module if the router can't

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

No branches or pull requests