Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically assign ng-controller on runtime

I have a situation where I need to dynamically change the controller, so that the scope variables are influenced accordingly. General structure:

<div ng-controller="GameController">
   // some general HTML which fits all types of games
    <div ng-controller="someScopeVar"> // Type of game
      // some game-type-specific ng-models that should respond to the change of controller, i.e scope
    </div
</div>

I saw here that it can be done within an ng-repeat. Can it be done outside of it? In other words, can I tell angular to read it as a variable rather than a string literal?

like image 840
Matanya Avatar asked Jul 08 '14 18:07

Matanya


2 Answers

As discussed in the comments, angular has a really powerful feature/library for handling these scenarios - ui-router (with its powerful wiki).

The ui-router is an answer to need to develop functional pieces - states, rather then think in view/url (cite from a home page):

AngularUI Router is a routing framework for AngularJS, which allows you to organize the parts of your interface into a state machine. Unlike the $route service in the Angular ngRoute module, which is organized around URL routes, UI-Router is organized around states, which may optionally have routes, as well as other behavior, attached.

There are some very interesting blog posts:

  • AngularJS State Management with ui-router (by Ben Schwartz)

...The most interesting thing about AngularJS's new router, isn't the router itself, but the state manager that comes with it. Instead of targeting a controller/view to render for a given url, you target a state. States are managed in a heirarchy providing inheritance of parent states and complex composition of page components, all the while remaining declarative in nature...

  • The basics of using ui-router with AngularJS (by Joel Hooks)

...ui-router fully embraces the state-machine nature of a routing system. It allows you to define states, and transition your application to those states. The real win is that it allows you to decouple nested states, and do some very complicated layouts in an elegant way.

Exactly the point we need - decouple child states... dynamically change the controller in fact ... could be by url change or just a state change (one sibling child instead of other without url change)

You need to think about your routing a bit differently, but once you get your head around the state-based approach, I think you will like it...

Finally, there are few links, which I would mark as the saint grail of ui-router

  • Sample application. In action we can see, how the ui-router state machine does work. We can load list as a parent state, then we can select row items, which do represent their own child state... while parent is not reloading (here I tried to explain it in more details)

  • state.js - the essential piece of code of the sample application. This is one of the best documented code snippets I've seen... Spent some time to go through and this will give you the 80% percent of answers: How ui-Router is working

From my experience, this is really suitable for small apps as well as for large scale systems... love it...

like image 130
Radim Köhler Avatar answered Sep 29 '22 21:09

Radim Köhler


The way this works for ngRepeat is different. It works because it compiles each repetition, which is what you need to do here.

For example:

mainCtrl = function($scope, $compile, $element) {
    $scope.someScopeVar = ctrl1;
    $scope.changeCtrl = function(id) {
        $scope.someScopeVar = id === 1 ? ctrl1 : ctrl2;
        var elm = $element.find('div');
        elm.replaceWith($compile(template)($scope));
    }
}

var ctrl1 = function($scope) { 
  $scope.name = 'World';
}

var ctrl2 = function($scope) {      
  $scope.name = 'John';
}

We assign each controller function to $scope.someScopeVar, then we must compile a new element with the new controller and replace the old one.

I don't believe angular has the capability to update a controller on the fly like that. Might be a good idea for a new feature.

FIDDLE


Another option would be to use the mixin pattern to update the main scope. You don't get a new shiny scope but it may work for your purposes.

mainCtrl = function($scope, $compile, $element) {
    angular.extend($scope, ctrl1());
    $scope.changeCtrl = function(id) {
         angular.extend($scope, id === 1 ? ctrl1() : ctrl2());
    }
}

var ctrl1 = function() { 
  var obj = {};
  obj.name = 'World';
  return obj;
}

var ctrl2 = function() {      
  var obj = {};
  obj.name = 'John';
  return obj;
}

FIDDLE

like image 36
Mosho Avatar answered Sep 29 '22 21:09

Mosho