14

I'm working with an angularjs site and have a background with working with routes in Rails and also Laravel in php. With routes in Laravel we could dynamically create a set of routes similar to:

  foreach($cities as $city):
    Route::get($city.'/hotels');
    Route::get($city.'/{slug}');

  endforeach;                      

Here we defined series of seperate routes in Laravel which technically do look the same except for the value of city and slug.

I'm finding angularJS a bit limited in defining routes in this case. Frankly am a bit lost here.

UPDATE

I've made some modifications here - basically I set up a service which retrieves assets from my database such as in this case a list of cities and categories. I'm trying to do this:

If {slug} is in the array of categories retrieved from my API, then use my ListController and list view but if its not then instead use my SingleVenueController and single view. Here's my code at the moment but its not working :(

  appRouteProvider.when('/:city/:slug', {
      templateUrl : function(sharedParams, $routeParams){
        t = sharedParams.getCurrentPageType($routeParams);
        if(t=='list'){
          return '../../app/templates/list.html';
        }
        if(t=='single'){
          return '../../app/templates/single.html';
        }

      },
      controller  :  function(sharedParams, $routeParams){
        t = sharedParams.getCurrentPageType($routeParams);
        if(t=='list'){
          return 'ListsController';
        }
        if(t=='single'){
          return 'SingleController';
        }
      },


    resolve:{
      sharedParamsData:function(sharedParams){
        return sharedParams.promise;
      },
    }
  })

In the above sharedParams is a service and the getCurrentPageType just checks the url slug to decide what controller to send back - but its not really working at all :(

Sensei James
  • 2,617
  • 30
  • 36
Ali
  • 7,353
  • 20
  • 103
  • 161

4 Answers4

6

How about defining a single route with a paramater ? In angularjs v1.x you can defined as many routes you want with as many params xor query

.config(function($routeProvider, $locationProvider) {
  $routeProvider
   .when('/city/:slug', {
    templateUrl: 'book.html',
    controller: 'BookController',
    resolve: {
     // you can also retrieve some data as a resolved promise inside your route for better performance.
  }
})

ref: https://docs.angularjs.org/api/ngRoute/service/$route

mxncson
  • 178
  • 1
  • 11
  • The issue here is that city can be a dynamic value such as froma list of cities in a database or the above url can also be a static page such as /site/contact or /about-us – Ali Sep 27 '16 at 15:15
  • Tou take it by the wrong side mate. You don't need to render directly from what's inside your database but you render template from your front-end and getting data from your server where city = ??? inside the resolve of the route. – mxncson Sep 27 '16 at 16:21
  • I got this partially worked out but facing some issues here with loading the right controller and view - I've updated my code, please help - its not working.. – Ali Oct 05 '16 at 14:07
2
appRouteProvider.when('/:city/:slug', {
templateUrl : 'dafault.html',
controller  :  'DefaultController',
resolve:{
    factory: function($routeParams, $http, $location, sharedParams){
        var city = $routeParams.city;
        var slug = $routeParams.slug;
        var deferred = $q.defer();

        sharedParams.getCurrentPageType($routeParams).then(function(t) {
            if(t=='list'){
                $location.path('/' + city + '/' + slug + '/list');
                deferred.resolve();
            }
            else if(t=='single'){
                $location.path('/' + city + '/' + slug + '/single');
                deferred.resolve();
            } else {
                deferred.reject();
            }

        });

        return deferred.promise;
    },


 }
});
appRouteProvider.when('/:city/:slug/list', {
    templateUrl: '../../app/templates/list.html',
    controller: 'ListsController',
});
appRouteProvider.when('/:city/:slug/single', {
    templateUrl: '../../app/templates/single.html',
    controller: 'SingleController',
});

You can do it with separate routes. The idea is when user hits the main route it resolves first with the data from the backend. If the condition is met, resolve function will redirect to specific route if not it wont pass

Kliment
  • 2,250
  • 3
  • 18
  • 32
2

Services in Angular cannot be injected in the configuration phase since they become available only in the run phase of an Angular application.

There is however a trick to load $http service in the config phase which you can use to load your cities/categories and set up your routes. Meanwhile, since controllers aren't registered up until the run phase, you may use the $controllerProvider to register your controllers beforehand in the configuration phase:

app.config(function ($routeProvider, $controllerProvider) {
  $controllerProvider.register('ListController', ListController);
  $controllerProvider.register('SingleController', SingleController);

  // wire the $http service
  var initInjector = angular.injector(['ng']);
  var $http = initInjector.get('$http');
  ...
});

You can now call your API to get the cities (or whatever else) and iterate while registering each route:

  ...
  // fetch the cities from the server
  $http.get('/cities')
  .then(function (response) {
    var cities = response.data;

    for(var i = 0; i < cities.length; i++){
      $routeProvider
      // assuming each city object has a `name` property
      .when('/' + cities[i]['name'] + '/:slug', {
        templateUrl: getTemplate(cities[i]['name']),
        controller: getController(cities[i]['name'])
      })
    }
  });
  ...

Note that I'm using the getTemplate and the getController methods which return the templateUrl and the relevant controller name strings respectively using an ordinary switch expression. You can choose your own approach.

Plunkr Demo

Note:

While a function with the templateUrl route options property does work with setting up a custom template, but when you use a function alongside the controller property, Angular will consider it as the constructor for the controller. Therefore, returning the name of the controller in that function won't work.

Community
  • 1
  • 1
Ahmad Baktash Hayeri
  • 5,802
  • 4
  • 30
  • 43
0

As Ahmad has already pointed out in his answer, if you pass a function to controller it is considered as a constructor for the controller. Also you can't get a service injected dynamically in config block of your app.

So what you can do is, move your sharedData service in separate app (in my code below I've used appShared as a separate app where this service is defined) and then access it using angular.injector. This way you don't have to define it as a parameter to templateUrl / controller functions.

Btw, you can't pass custom parameters to templateUrl function (ref: https://docs.angularjs.org/api/ngRoute/provider/$routeProvider)

If templateUrl is a function, it will be called with the following parameters:

{Array.<Object>} - route parameters extracted from the current $location.path() by applying the current route

Now for the controller, use $controller to dynamically load either ListsController or SingleController based on your condition. Once that is loaded, extend your current controller (defined by your controller function) using angular.extend so that it inherits all the properties and methods of the dynamically loaded controller.

Check the complete code here: http://plnkr.co/edit/ORB4iXwmxgGGJW6wQDy9

app.config(function ($routeProvider) {
    var initInjector = angular.injector(['appShared']);
    var sharedParams = initInjector.get('sharedParams');

    $routeProvider
    .when('/:city/:slug', {
        templateUrl: function ($routeParams) {
            console.log("template url - ");
            console.log($routeParams);
            var t = sharedParams.getCurrentPageType($routeParams);
            console.log(t);
            if (t == 'list') {
                return 'list.html';
            }
            if (t == 'single') {
                return 'single.html';
            }

        },
        controller: function ($routeParams, $controller, $scope) {
            //getController(cities[i]['name'])
            console.log("controller - ");
            console.log($routeParams);
            var t = sharedParams.getCurrentPageType($routeParams);
            console.log(t);
            if (t == 'list') {
                angular.extend(this, $controller('ListsController', { $scope: $scope }));
            }
            if (t == 'single') {
                angular.extend(this, $controller('SingleController', { $scope: $scope }));
            }
        }
    });


});
Community
  • 1
  • 1
Vivek Athalye
  • 2,974
  • 2
  • 23
  • 32