Join the Stack Overflow Community
Stack Overflow is a community of 6.8 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

I'm trying to lazy-load components. The component is an html fragment with an embedded script tag that contains the controller.

<script>
    ... controller code .....
</script>
<div>
    ... template ....
</div>

The fragment is generated in ONE html request so I cannot use templateUrl AND componentURL in the state definition.

I have tried to use the templateProvider to get the component, than extract the script code for the function and register it using a reference to the controllerProvider.

I'm sure there must be a better way to do this than the ugly solution I have come up with. I make a global reference to the controllerpovider, then I read the component thru the templateProvide using a getComponent service. Next I extract the script and evaluate it, which also registers the controller.

See the plunker for the way I'm trying to solve this.

.factory('getComponent', function($http, $q) {
  return function (params) {
    var d = $q.defer();
    // optional parameters
    $http.get('myComponent.html').then(function (html) {
            // the component contains a script tag and the rest of the template.
            // the script tags contain the controller code.
            // first i extract the two parts
            var parser = new window.DOMParser();
            var doc = parser.parseFromString(html.data, 'text/html');
            var script = doc.querySelector('script');
            // Here is my problem. I now need to instantiate and register the controller. 
            // It is now done using an eval which of cours is not the way to go
            eval(script.textContent);
            // return the htm which contains the template
            var html = doc.querySelector('body').innerHTML;
            d.resolve(html);
    });
    return d.promise;
  };
})

Maybe it could be done using a templateProvider AND a controllerProvider but I'm not sure how to resolve both with one http request. Any help / ideas would be greatly appreciated.

share|improve this question
    
to add script tag, you need jquery – YOU Jun 20 '15 at 12:02
up vote 1 down vote accepted

Here's a working plunkr

  • You do not have access to $controllerProvider at runtime, so you cannot register a named controller.
    • UI-Router doesn't require named/registered controller functions. { controller: function() {} } is perfectly valid in a state definition
    • However, if you need the controller to be registered, you could use a tool like ocLazyLoad to register it.
  • UI-Router links the controller to the template, so there's no need for ng-controllersprinkled in the html.

Here's how I hacked this together. getController factory now keeps a cache of "promises for components". I retained your eval and template parsing to split the response into the two pieces. I resolved the promise with an object containing the ctrl/template.

component factory

  .factory('getComponent', function($http, $q) {
      var components = {};
      return function (name, params) {
        if (components[name]) 
          return components[name];

        return components[name] = $http.get(name + '.html').then(extractComponent);

        function extractComponent(html) {
          var parser = new window.DOMParser();
          var doc = parser.parseFromString(html.data, 'text/html');
          var script = doc.querySelector('script');
          // returns a function from the <script> tag
          var ctrl = eval(script.textContent);
          // return the htm which contains the template
          var tpl = doc.querySelector('body').innerHTML;
          // resolve the promise with this "component"
          return {ctrl: ctrl, tpl: tpl};
        }
      };
  })

myComponent.html

<script>
  var controller = function($scope) {
    $scope.sayHi = function() {
      alert(' Hi from My controller')
    }
  };
  // This statement is assigned to a variable after the eval()
  controller;
</script>

// No ng-controller
<div>
  Lazyloaded template with controller in one pass.
  <button ng-click="sayHi()">Click me to see that the controller actually works.</button>
</div>

In the state definition:

  • I created a resolve called 'component'. That resolve asks the getComponent factory to fetch the component called 'myComponent' (hint: 'myComponent' could be a route parameter)
  • When it's ready, the resolve is exposed to the UI-Router state subtree.
  • The state is activated
  • The view is initialized
    • The controller provider and template provider inject the resolved component, and return the controller/template to UI-Router.

Smell test

I should mention that fetching a controller and template in a single html file and manually parsing smells wrong to me.

Some variations you could pursue:

  • Improve the getComponent factory; Split the component into html/js and fetch each separately. Use $q.all to wait for both fetches.
  • Use ocLazyLoad to register the controller globally with $controllerProvider
  • Fetch and store the template in the $templateCache.
share|improve this answer
    
Thanks for your reply.. How in your solution can i refresh the ui-view after re-activating the state. Smell test: Manually parsing might not be the best way, but: Template and the controller are loaded in one because they are parsed on the server where html is build from dynamic data and other data is tored in the model. The html and the datacome from the same source and cannot be loaded in 2 calls. I think that Angular 2 will also load controller and template in one request. The webcomponents standard is actually well-suited for this kind of approach (shadow-dom, isolated scope and style). – Ronald Brinkerink Jun 23 '15 at 10:32
    
What do you mean "refresh the ui view"? Do you want to re fetch the component? – Chris T Jun 23 '15 at 11:56
    
Correct. The component is build on the server so when the state becomes active the component needs to reload. – Ronald Brinkerink Jun 24 '15 at 16:19
    
Then just take out the components array that manages caching of the server response – Chris T Jun 24 '15 at 16:21

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.