Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I dynamically load a template based on parameters in a route registered with $routeProvider?

I found that you can use template or templateUrl as a function like that

.when('/:controller/:action',{
   templateUrl:function(params){
     return: '/'+params.controller+'/'+params.action
  }
})

And then I wondered if it's possible lazy load the template, but I can't make that work

.when('/:ctrl/:action',{
   template:function(params){
      injector = angular.injector(['ng']);
      $q = injector.get('$q');
      var dfrd = $q.defer();

      // fetch template from server
      // dfrd.resolve()

      return dfrd.promise;      
   }
});

Tell me please there's a way - I want to fetch entire template by making ajax request to the server

like image 926
iLemming Avatar asked Jun 17 '13 21:06

iLemming


4 Answers

As pointed out, templates loaded by templateUrl will be lazy loaded in normal circumstances, and there are methods of playing with the cache if you need something other than the norm.

This comment seems to be the crux of the matter:

I want to fetch entire template by making ajax request to the server

This is not necessarily a normal way to work with angular. It can have its usefulness, but it's off the beaten path. Usually templates are immune from everything that is context-sensitive because they represent code that gets its critical state information from $scope or parameters of some sort. So why ajax?

What does your template want/need that would come from an ajax request? The answer to that question may help with the ultimate decision of how to make it happen most efficiently and in the right place.

It may be that you want or need some server-side dynamism to be part of your system, or that there are other ways to do that than to have an ajax return defining the template.

Update: so you've said that 'On route change I need to get template from the server, asynchronously using $http".' But that's all built in to templateUrl. @wmluke seems to have presented some interesting alternatives.

If we just accept that you need ajax to get your template, then here's one more possibility. With ui-router you can use a templateProvider -- which could call you own custom service that does ajax and whatever else you want.

This example just returns Hello world asynchronosly, but you could call your own service here or directly execute ajax requests if you wanted to.

templateProvider:
 [        '$timeout',
  function ($timeout) {
   return $timeout(function () { return "Hello world" }, 100);
 }],

Buying in to ui-router is a potentiall bigger commitment, but it might yield rewards above just being able to write code to produce templates. Good luck!

like image 20
laurelnaiad Avatar answered Nov 04 '22 09:11

laurelnaiad


So, I see you're a .NET MVC developer...

Let's have a look at what Actions do in .NET MVC: They return ActionResults... which are really manifests for what view and model should be ran and returned from the architecture. A "controller" in MVC is a collection of such actions, and may contain some shared logic between those actions in the form of private methods or shared dependencies on that class.

Now let's have a look at what Angular is doing: Angular is calling the controller function one time, as a constructor, generally to set up a $scope object which will act (sort of) as your "model". Every controller in Angular is (generally speaking) associated to one and only one way to set up a $scope**. Once that controller is processed, a $digest is called on the $scope it altered, which is then applied to a the view that is bound to the scope... which is the html encapsulated by the element with your ng-controller (or ng-view) attribute.

So.. the question, can you dynamically load templates based on route parameters? Yes. You definitely can. The easiest way would be to route your requests to a single template that included nothing but a div with an ng-include on it, that you changed.

In your routes:

$routeProvider.when('/:controller/:action', { 
    controller: 'DynamicCtrl', 
    template: '<div ng-include="include"></div>' 
});

Then your dynamic controller declaration:

app.controller('DynamicCtrl', function($scope, $routeParams) {
    $scope.include = $routeParams.controller + '/' + $routeParams.action;
});

MyController/MyAction (which I assume you may be generating with Razor) should return something like this:

<div ng-controller="MyActionCtrl">
   do stuff!
</div>

... from there you would define your MyActionCtrl controller all to itself.

Can you bend the architecture to make it "ASP.Net MVC-esque", in that "one controller" has a whole bunch of "action" functions in it that dictate the entire behavior of your view? Yeah... But not without making your application really silly with switch statements and the like... So you probably don't want to do that.

In the end you're going to have to get out of the mindset of ASP.Net MVC's "Controllers and Actions" with Angular. It's an MVC architecture, but MVC != "Controllers && actions".


** Angular also creates and saves an instance of the controller as an object too, but that feature is rarely used in Angular development, and I'm not talking about that here, per say.)

like image 195
Ben Lesh Avatar answered Nov 04 '22 08:11

Ben Lesh


Why not just bypass templateURL all together and have your controllers intercept the requests?

  • You will be able to dynamically lazy load pages by URL

  • Optionally intercept partials.

config.js

$routeProvider.when('/contact', {
  template: '/views/contact.html'
});

controllers.js

controller('AppController', ['$scope' function($scope) {    
  //Do whatever you want here
  $scope.$on("$routeChangeSuccess",function( $currentRoute, $previousRoute ){
    console.log($route.current.template);
    $scope.page = $route.current.template;
  });

  //Optionally intercept partials
  $scope.returnView = function(partial){
    console.log(partial);
    return partial;
  }  
}]).

index.html

<html lang="en" ng-app="myApp" ng-controller="AppController">
<body>
<ng-include src="returnView('/views/global.header.html')"></ng-include>
<ng-include src="page"></ng-include>
<ng-include src="returnView('/views/global.footer.html')"></ng-include>
</body>
</html>
like image 2
Dan Kanze Avatar answered Nov 04 '22 09:11

Dan Kanze


Angular templates are lazy loaded by default. Specifically, templates are not fetched until they are needed and then they are cached in $templateCache.

However, based on your example I suspect you're looking for the resolve attribute for route definitions in $routeProvider. This attribute allows you to pass promise dependencies to a controller. See the delay example in $route...

$routeProvider.when('/Book/:bookId', {
    templateUrl: 'book.html',
    controller: BookCntl,
    resolve: {
        // I will cause a 1 second delay
        delay: function($q, $timeout) {
            var delay = $q.defer();
            $timeout(delay.resolve, 1000);
            return delay.promise;
        }
    }
});

Also, you might be interested preloading some templates but lazy loading others. In this case you should check out...

  • http://docs.angularjs.org/api/ng.directive:script
  • https://github.com/ericclemmons/grunt-angular-templates
  • https://github.com/karlgoldstein/grunt-html2js
  • https://github.com/wmluke/grunt-inline-angular-templates (shameless plug)
like image 1
wmluke Avatar answered Nov 04 '22 08:11

wmluke