How to Lazyload controller and template in one request using angular-ui-router

Ronald Brinkerink

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.

Chris T

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.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

How to pass parameters using ui-sref in ui-router to controller

Angular ui-router - how to access parameters in nested, named view, passed from the parent template?

Using $templateCache in ui-router's template

Angular ui-router: How to defer rendering the template until authorization is complete?

In Angular ui-router child template not working

Lazy load template and controller in angular UI-Router

Angular UI router, how to get a list of ancestors of current state from controller?

How can I pre load a template in Angular UI router?

Using angular ui.router how to start in a standard html angular page and than move forward to one with ui.router template inside a folder?

Angular ui-router: How to load child controller instead of parent controller?

How to handle angular-ui-router and template loading errors?

angular-ui-router only loads template but not controller not loaded

angular js controller using angular-ui-router not working?

How to properly lazyload component in angular?

Angular UI-Router not displaying template

angular-ui-router traceur controller assignation

ui-router controller or angular controller

How to redirect users with unverified emails using Angular UI-Router?

How do I add Angular-Chart.JS data into UI-Router .State Controller?

Angular UI Router not using template cache after building with grunt

AngularJS: UI Router doesn't load template and controller

Angular UI Router Wrong Template And Controller

How to pass data to the controller using angular ui modal?

Passing parameters to Angular UI router template

Angular ui.router Reload Template

Use Angular ui-router for heavy template

Prevent Angular controller from loading using UI Router resolve

Lazyload to multiple views in ui-router

How to access one lazyload module component into another lazyload module component in Angular 12?