ocombe / ocLazyLoad

Lazy load modules & components in AngularJS
https://oclazyload.readme.io
MIT License
2.63k stars 510 forks source link

duplicate scope #203

Open atrusch opened 9 years ago

atrusch commented 9 years ago

Hi,

I am writing an application with ui-router state defined dynamically on startup. I used angular 1.4.1 and ocLazyLoad 1.0.1

When all goes well the scope tree is the following : scope_ok

But sometimes the scope tree for the same page is : scope_nok

There is a new scope 53 (duplicate of ng-controller) and ng-controller is a sibling. If you have a scope function it will call 2 times for one event. We can make an TeamViewer if needed.

state definition

{
    url : '/skills_user_search',
    views : {
        'RootView' : {
            templateUrl : 'plugins/usl_userSkills/searchUserWithSkill/usl_searchUserWithSkill.html',
            controller : function($scope,modules) {
                $scope.ocLazyLoad = {
                    files:[
                        'webjars/ng-grid/2.0.14/ng-grid.min.js',
                        'plugins/usl_userSkills/searchUserWithSkill/services/usl_searchService.js',
                        'plugins/usl_userSkills/searchUserWithSkill/controllers/usl_searchUserCtrl.js'
                    ]
                }
            }
        }
    },
    resolve : {
        modules : ['$ocLazyLoad', function($ocLazyLoad) {
            return $ocLazyLoad.load({
                serie : true,
                files :[
                   {type: 'css', path:'webjars/angular-ui-select/0.11.2/select.min.css'},
                   {type: 'css', path:'js/thirdparty/angular/tree-grid-directive/treeGrid.css'},
                   {type: 'css', path:'webjars/ng-grid/2.0.14/ng-grid.min.css'},           
                   'webjars/angular-ui-select/0.11.2/select.min.js',
                   'js/thirdparty/angular/tree-grid-directive/tree-grid-directive.js',
                   'plugins/usl_userSkills/usl_treeSkills.js',
                   'plugins/usl_userSkills/searchUserWithSkill/usl_searchUserWithSkill.js'
                ]               
            });
        }]
    }
}

Template

<section class="content-header">
  <h1>
        {{'Search Users with skills' | translate}}
  </h1>
</section>

<section class="content" oc-lazy-load="ocLazyLoad">
    <div class="box box-default" ng-controller="usl_searchUserCtrl">
        <div class="box-header with-border">
            <h3 class="box-title ng-binding">{{'Selected skills' | translate}}</h3>
        </div>
        <div class="box-body">
            <form name="formuser" novalidate class="form-horizontal">
                <div class="col-xs-12" ng-if="skillList.length > 0">
                    <ui-select multiple ng-model="selected.skills" theme="select2" style="width:100%">
                        <ui-select-match placeholder="Select a skill...">{{$item.text}}</ui-select-match>
                        <ui-select-choices repeat="skill in skillList | propsFilter: {text: $select.search}">
                            {{skill.text}}
                        </ui-select-choices>
                    </ui-select>
                </div>
                <div class="pull-right">
                    <br/>
                    <button class="btn btn-primary" ng-disabled="selected.skills.length == 0" ng-click="searchUsers()">{{'Search' | translate}}</button>
                </div>
            </form>
        </div>
    </div>
    <div class="box box-default">
        <div class="box-body">
            <div class="gridStyle" ng-grid="gridOptions" ng-if="gridData.length > 0"></div>
        </div>
    </div>
</section>
ocombe commented 9 years ago

Hmm this is probably a race condition, and the controller gets called before the ocLazyLoad directive is able to remove the dom. This directive needs a rewrite because there are a few bugs that are hard to fix right now.

Could you try something like this ?

$ocLazyLoadProvider.config({
  modules: [{
    name: 'searchUserWithSkill', // give the name that you want here
    files:[
      'webjars/ng-grid/2.0.14/ng-grid.min.js',
      'plugins/usl_userSkills/searchUserWithSkill/services/usl_searchService.js',
      'plugins/usl_userSkills/searchUserWithSkill/controllers/usl_searchUserCtrl.js'
    ]
  }]
});
{
    url : '/skills_user_search',
    views : {
        'RootView' : {
            templateUrl : 'plugins/usl_userSkills/searchUserWithSkill/usl_searchUserWithSkill.html'
        }
    },
    resolve : {
        modules : ['$ocLazyLoad', function($ocLazyLoad) {
            return $ocLazyLoad.load({
                serie : true,
                files :[
                   {type: 'css', path:'webjars/angular-ui-select/0.11.2/select.min.css'},
                   {type: 'css', path:'js/thirdparty/angular/tree-grid-directive/treeGrid.css'},
                   {type: 'css', path:'webjars/ng-grid/2.0.14/ng-grid.min.css'},           
                   'webjars/angular-ui-select/0.11.2/select.min.js',
                   'js/thirdparty/angular/tree-grid-directive/tree-grid-directive.js',
                   'plugins/usl_userSkills/usl_treeSkills.js',
                   'plugins/usl_userSkills/searchUserWithSkill/usl_searchUserWithSkill.js'
                ]               
            });
        }]
    }
}
<section class="content-header">
  <h1>
        {{'Search Users with skills' | translate}}
  </h1>
</section>

<!-- searchUserWithSkill is the same name as in config, it's a string so double quote followed by single quote -->
<section class="content" oc-lazy-load="'searchUserWithSkill'">
    <div class="box box-default" ng-controller="usl_searchUserCtrl">
        <div class="box-header with-border">
            <h3 class="box-title ng-binding">{{'Selected skills' | translate}}</h3>
        </div>
        <div class="box-body">
            <form name="formuser" novalidate class="form-horizontal">
                <div class="col-xs-12" ng-if="skillList.length > 0">
                    <ui-select multiple ng-model="selected.skills" theme="select2" style="width:100%">
                        <ui-select-match placeholder="Select a skill...">{{$item.text}}</ui-select-match>
                        <ui-select-choices repeat="skill in skillList | propsFilter: {text: $select.search}">
                            {{skill.text}}
                        </ui-select-choices>
                    </ui-select>
                </div>
                <div class="pull-right">
                    <br/>
                    <button class="btn btn-primary" ng-disabled="selected.skills.length == 0" ng-click="searchUsers()">{{'Search' | translate}}</button>
                </div>
            </form>
        </div>
    </div>
    <div class="box box-default">
        <div class="box-body">
            <div class="gridStyle" ng-grid="gridOptions" ng-if="gridData.length > 0"></div>
        </div>
    </div>
</section>
atrusch commented 9 years ago

Thanks for you fast response.

Actually I load elements only on demand. But what I really don't understand it is why if I try to load my controller in the resolve like this :

{
    url : '/skills_user_search',
    views : {
        'RootView' : {
            templateUrl : 'plugins/usl_userSkills/searchUserWithSkill/usl_searchUserWithSkill.html',
            controller : function($scope,modules) {
                alert(modules);
                $scope.ocLazyLoad = {
                    files:[
                        'webjars/ng-grid/2.0.14/ng-grid.js',
                        'plugins/usl_userSkills/searchUserWithSkill/services/usl_searchService.js',
                    ]
                }
            }
        }
    },
    resolve : {
        modules : ['$ocLazyLoad', function($ocLazyLoad) {
            return $ocLazyLoad.load({
                serie : true,
                files :[
                   {type: 'css', path:'webjars/angular-ui-select/0.11.2/select.min.css'},
                   {type: 'css', path:'js/thirdparty/angular/tree-grid-directive/treeGrid.css'},
                   {type: 'css', path:'webjars/ng-grid/2.0.14/ng-grid.min.css'},           
                   'webjars/angular-ui-select/0.11.2/select.min.js',
                   'js/thirdparty/angular/tree-grid-directive/tree-grid-directive.js',
                   'plugins/usl_userSkills/usl_treeSkills.js',
                   'plugins/usl_userSkills/searchUserWithSkill/usl_searchUserWithSkill.js',
                   'plugins/usl_userSkills/searchUserWithSkill/controllers/usl_searchUserCtrl.js'
                ]               
            });
        }]
    }
}

I have the following error :

Error: [ng:areq] http://errors.angularjs.org/1.4.1/ng/areq?p0=usl_searchUserCtrl&p1=not%20aNaNunction%2C%20got%20undefined
    at Error (native)

Normally the controller is loaded 1rst and template only after no ?

ocombe commented 9 years ago

Actually I think that the router is loading the template and then the controller (because the controller will be applied to your template).

But the resolve is loaded before the controller & the template.

atrusch commented 9 years ago

So why it say that the function does not exist ? The function is defined in plugins/usl_userSkills/searchUserWithSkill/controllers/usl_searchUserCtrl.js

Code

"use strict";

angular.module('usl_searchUserWithSkill').controller('usl_searchUserCtrl',
        function($scope, $digLog, Restangular, usl_treeSkillsService, usl_searchService){

    $scope.selected = { skills : [] };
    $scope.skillList = [];

And when I load it with the ocLazyLoader directive it can found it. Can you explain me this behaviour.

ocombe commented 9 years ago

Not really :-/

atrusch commented 9 years ago

Hi,

As you suggest it really seems a race problem. I have notice that if I put a breakpoint on

broadcast(newModule ? "ocLazyLoad.moduleLoaded" : "ocLazyLoad.moduleReloaded", moduleName);

                    if (angular.isDefined(runBlocks[moduleName]) && (newModule || params.rerun)) {
                        tempRunBlocks = tempRunBlocks.concat(runBlocks[moduleName]);
                    }
                    _invokeQueue(providers, moduleFn._invokeQueue, moduleName, params.reconfig);
                    _invokeQueue(providers, moduleFn._configBlocks, moduleName, params.reconfig); // angular 1.3+
                    broadcast(newModule ? "ocLazyLoad.moduleLoaded" : "ocLazyLoad.moduleReloaded", moduleName);
                    registerModules.pop();
                    justLoaded.push(moduleName);

And an alert in the controller state definition

{
    url : '/skills_user_search',
    views : {
        'RootView' : {
            templateUrl : 'plugins/usl_userSkills/searchUserWithSkill/usl_searchUserWithSkill.html',
            controller : function($scope,modules) {
                alert(modules);
                $scope.ocLazyLoad = {
                    files:[
                        'webjars/ng-grid/2.0.14/ng-grid.js',
                        'plugins/usl_userSkills/searchUserWithSkill/services/usl_searchService.js',
                    ]
                }
            }
        }
    },
    resolve : {
        modules : ['$ocLazyLoad', function($ocLazyLoad) {
            return $ocLazyLoad.load({
                serie : true,
                files :[
                   {type: 'css', path:'webjars/angular-ui-select/0.11.2/select.min.css'},
                   {type: 'css', path:'js/thirdparty/angular/tree-grid-directive/treeGrid.css'},
                   {type: 'css', path:'webjars/ng-grid/2.0.14/ng-grid.min.css'},           
                   'webjars/angular-ui-select/0.11.2/select.min.js',
                   'js/thirdparty/angular/tree-grid-directive/tree-grid-directive.js',
                   'plugins/usl_userSkills/usl_treeSkills.js',
                   'plugins/usl_userSkills/searchUserWithSkill/usl_searchUserWithSkill.js',
                   'plugins/usl_userSkills/searchUserWithSkill/controllers/usl_searchUserCtrl.js'
                ]               
            });
        }]
    }
}

That the controller is found. Is it possible to put a delay between the modules loading et the final resolve ?

An other thing is the order the modules are loaded log_trace

Why ngGrid elements are reload and not the main module.

ocombe commented 9 years ago

No it's not possible, and it should work without it. Why do you load some files in the "controller" of your view, and not all of them in the resolve ?

atrusch commented 9 years ago

Because if I do this he can't find all my controllers, filters, .... when he display the page. I can show you directly with a teamviewer if you want.

ocombe commented 9 years ago

I can't (I don't have teamviewer & I'm at work), but if you could set up a plunkr or a zip (at olivier.combe@gmail.com) that would be much easier to debug

atrusch commented 9 years ago

Thanks again for your fast response The plunkr link http://plnkr.co/edit/c6FoQj3HZASXuCEiDtRZ

ocombe commented 9 years ago

Ok here is the problem: in the file "myprofilectrl.js", you inject the service "usl_treeSkillsService", but this service is never defined. Remove this injection from the controller and it works: http://plnkr.co/edit/slFOec6HFstHP8MfvsZB?p=preview

atrusch commented 9 years ago

Hi,

If you look pluginmanager I load in the ui-route state the module

modparent.js

var plugins = {"plugins" : [{"name":"myprofile_details","navigation":{"id":"1","title":"My Profile","glyphicon":"fa-user","order":10,"homepriority":10,"children":[{"id":"myprofile_details","title":"Details","glyphicon":"fa-circle-o","url":"myprofile_details","order":10,"homepriority":10,"parent":"1"}]},"state":{
    url : '/myprofile_details',
    views : {
        'RootView' : {
            templateUrl : 'myprofile.html',
        }
    },
    resolve : {
        modules : ['$ocLazyLoad', function($ocLazyLoad) {
            return $ocLazyLoad.load({
              serie : true,
              files :[
              'modparent.js',
                'myprofile.js',
                'myprofilectrl.js'
            ]});
        }]
    }
  }}
  ]};                 

The code of this one is :

"use strict";

angular.module('usl_treeSkills',["oc.lazyLoad","my_profile"]);

angular.module('usl_treeSkills').run(function($ocLazyLoad){
    $ocLazyLoad.load({
        files : [
            'modparentService.js'
        ]
    })
});

The module is loaded before the other. So it must know this service, no ?

ocombe commented 9 years ago

if you need a service from another module, then you need to reference this module as a dependency for your module. And "modparentService.js" is loaded at "run", so it is not available when you call load the first time.

atrusch commented 9 years ago

Hi,

Thanks for the analyze. For maintenance purpose I want to keep my module in multiple files. But also when dependencies exist to load only one file which describe the complete module.

Is it possible to do this kind of things ? If you are agree I will do it with a little help or advice.

Regards

ocombe commented 9 years ago

It is possible, but I don't know if it will be easy :)

atrusch commented 9 years ago

Ok, I'am looking your code. I understand how the elements are loaded, but what happens to promise after. What function is it called on success ? Maybe I could add a new loader (as jsLoader, cssLoader) when all the code is readed call buildelement for each js defined in the readed file. But after I don't understand how this files are registered into Angular. Too many promise. Can you give me a clue ?

Regards

ocombe commented 9 years ago

The registering with angular happens in the _register function (in ocLazyLoad.core.js), and especially in the _invokeQueue where I'm calling the provider that should register the component with angular.