Skip to content

More scalable examples? #114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
uberspeck opened this issue May 2, 2013 · 34 comments
Closed

More scalable examples? #114

uberspeck opened this issue May 2, 2013 · 34 comments
Labels

Comments

@uberspeck
Copy link

First, thanks for the excellent resource! I'm looking forward to using it on a major application rebuild. Just trying to wrap my head around it now ;)

Wondering if someone could give examples demonstrating how ui-router might scale to much larger applications (than the sample app for example)? I'm fairly new to Angular and just started reading up on ui-router. The sample app demonstrates everything in a single JS file. How might I proceed if my JS is spread across separate files (appConfig.js, appController.js, usersControllers.js, accountControllers.js etc). Can I pass my controllers to config using dependency injection? Defining them all inline isn't very feasible for a project of this size and I'm trying to keep things separated as much as possible for maintainability.

@uberspeck
Copy link
Author

P.S. I should add that I'd be more than happy to contribute to the sample project/docs as a result of anything learned here!

@laurelnaiad
Copy link

I'll take a crack at the controller question. Hopefully if I'm off someone will correct me.

https://github.com/angular-ui/ui-router/wiki/State-Manager shows in its simple example that the traditional ng-controller="MyController" still works (at least if it's provided in a parent view).

https://github.com/angular-ui/ui-router/blob/master/src/viewDirective.js#L2 confirms that $controller is a dependency of ui-view.

So you could leave it up to the HTML and injection, where controllers are defined like:

angular.module('app').controller('MyController', [ ...

@uberspeck
Copy link
Author

That makes sense. I really liked the idea of dropping the ng-controller attribute, but if that's not an option it's not really a deal breaker.

@rynz
Copy link

rynz commented May 3, 2013

We're using it for a relatively large application now and absolutely loving it.

Our app is made up of many mini-apps with states being derived in each different "mini-app". angular.module('app').controller('MyController', [ ... is how we define our controllers in separate files of each mini-app and have a .config in the main definition of each mini-module whcih we reference the controller via 'MyController' string.

Absolutely adore this resource, I hope one day it replaces ng-view in core :)

@jeme
Copy link
Contributor

jeme commented May 3, 2013

as @sparko says, you can define your controller using angular.module('...').controller('MyCtrl', ...) and then use it as:

$stateProvider
  .state('report',{
    views: {
      'graph': {
        templateUrl: 'report-graph.html',
        controller: 'MyCtrl'
      },
    }
  })

@uberspeck
Copy link
Author

I completely missed that that was an option, thanks for pointing it out!

@lmessinger
Copy link
Contributor

Apart from using standard controllers, scaling is also related to the ability to load component code - controllers in this example - on demand. Otherwise we need to download all of our controllers on the first page. This is an Angular core question - but still, wondered if any one has any idea regarding it.
Mini-apps sounds like one solution, but wouldnt that mean full page reload every time we go to a new app?

@lmessinger
Copy link
Contributor

@uberspeck could u reopen for the scalability discussion? i believe its important. thanks

@uberspeck uberspeck reopened this May 3, 2013
@lmessinger
Copy link
Contributor

thanks. repeating...
Apart from using standard controllers, scaling is also related to the ability to load component code - controllers in this example - on demand. Otherwise we need to download all of our controllers on the first page. This is an Angular core question - but still, wondered if any one has any idea regarding it.
Mini-apps sounds like one solution, but wouldnt that mean full page reload every time we go to a new app?

@uberspeck
Copy link
Author

This 3 part article was mentioned on G+ today. It might contribute to the conversation...
http://blog.artlogic.com/2013/05/02/ive-been-doing-it-wrong-part-1-of-3/

@lmessinger
Copy link
Contributor

thanks for this article. This definitely addresses the subject from a build/folder structure perspective.

The biggest deficiency of Angular, in my view, for large scale MVC apps, is the lack of on-demand loading of Controller and Models. On the View front, ui-router leads the way for dynamic loading - that's why its such a great project. i'm trying to think how to address the M and C and whether it could be done within ui-router

This, by the way, is related to the second biggest deficiency: server-side rendering. the new Rendr project of backbone.js shows the need of bootstrapping from pre-rendered HTML. connect-prerenderer shows a nice full-page-based bootstrapping idea, but for public facing sites, where some areas are personalized (i.e. you want your login link to show as Not Lior? link to the user Lior), we need the ability to load some directives on demand while keeping others bootstrapped from the html snapshot

@laurelnaiad
Copy link

For lazy loading controllers, here's how someone has done it (using requirejs and the core router/routeProvider): http://github.com/jensaug/angularjs-requirejs-lazy-controllers

That code seems to boil down to "download and register the controller by name using requirejs within the resolve promises of the route definition". I haven't yet wrapped my head around how or if this could be applied to ui-router as is.

EDIT: this discusses the issue: #104

@uberspeck
Copy link
Author

@lmessinger, unfortunately I haven't much to contribute on either of the deficiencies you mentioned. I last looked at on-demand resource loading several years ago...before the javascript "revolution" we're seeing now. There weren't a lot of tools for it then and what existed was buggy. I dropped the approach then and haven't taken the time to revisit. I admit it would be really nice to see that baked in to Angular.

@lmessinger
Copy link
Contributor

thanks! the approach by jensaug is very helpful. someone on the list also sent me http://ify.io/lazy-loading-in-angularjs/ - i think it also takes a similar approach although still havent delved into both

@laurelnaiad
Copy link

I just came across this one: https://github.com/szhanginrhythm/angular-require-lazyload for requirejs lazyloading. Again using promises. This one seems a little cleaner/organized/complete.

@amitava82
Copy link

Converting Angular-app to ui-router would give us a clear understanding of ui-router. Putting everything in a single page for a demo is not very helpful.

@bf0
Copy link

bf0 commented May 18, 2013

So the fact that

$stateProvider
  .state('report',{
    views: {
      'graph': {
        templateUrl: 'report-graph.html',
        controller: 'ReferenceYourControllerLikeThis'
      },
    }
  })

is a possibility, is great. It should be mentioned in the docs though -- it wasn't clear to me either and I was looking for an example with more than a single js file / block as well.

++ to mention this in the docs at https://github.com/angular-ui/ui-router/wiki/State-Manager

@timkindberg
Copy link
Contributor

@bf0 example added

@laurelnaiad
Copy link

I've implemented a provider/service to lazy-load/lazy-register components using AMD. It works with the stock router. I'm trying to get it to work with ui-router and thus far no luck. The stock router resolves the entire route definition as a promise.

Since the state provider needs some of the properties right away (the url property in particular), I tried to trick it into running the couchPotato lazyLoad function within the resolve property by using a dummy property.

So, with the stock route provider, this code works:

      $routeProvider.when('/view1',
        $couchPotatoProvider.lazyLoad({
          templateUrl:'/sample/partials/partial1.html',
          controller: 'MyCtrl1',
          dependencies: [
            'lazy/directives/appVersion',
            'lazy/controllers/myCtrl1'
          ]
        })
      );

I haven't yet found a way to get anything similar to work with ui-router. Granted, I just started trying, but my initial test suggests that the code running under the resolve property is evaluated at run time, not config time, and thus a service rather than a provider is required. The $couchPotato service also has lazyLoad defined, but it doesn't seem to be called the way I'd hoped.

So this doesn't work:

        .state('contacts', {
          url: '/contacts',
          abstract: true,
          templateUrl: '/sample-ui-router/partials/contacts.html',
          controller: 'contactsController',
          resolve: {
            dummy: ['$couchPotato', function($couchPotato) { $couchPotato.lazyLoad({
              dependencies: ['controllers/contactsController']
            });}]
          }
        })

If I figure out a way to make it run the lazyLoad function, I'll post back. If anyone knows how, I'd appreciate a tip! Essentially all I need to do is force it to run the lazyLoad function when it's activating the route (and before the router or the ui-view directive try to invoke the controller).

@laurelnaiad
Copy link

This is couchPotato (derived from the examples linked above). https://github.com/afterglowtech/angular-couchPotato . I'm not finding a means to get it to run under ui-router.

@ksperling
Copy link
Contributor

It would be pretty simple to allow a promise for 'controller', as 'template' and 'resolve' need to be loaded asynchronously anyway -- currently 'controller' is just statically shoe-horned into the output of 'resolve' as a magic '$$controller' property that ui-view picks up.

In fact you should simply be able to resolve the '$$controller' property manually inside 'resolve' (and just leave out the outer 'controller' property) and it should work.

@laurelnaiad
Copy link

Thanks, @ksperling. At first glance it doesn't look like I can override $$controller, since

      var globals = dst.globals = { $stateParams: $stateParams };
      resolve(state.resolve, globals);
      globals.$$state = state; // Provide access to the state itself for internal use

      // Resolve template and dependencies for all views.
      forEach(state.views, function (view, name) {
        // References to the controller (only instantiated at link time)
        var $view = dst[name] = {
          $$controller: view.controller
        };
//etc...

However, this begs the question of why the controller that I intend to have registered by the time resolve() is finished isn't being picked up. I'll try to debug that later today. It may be that the my lazyLoad function isn't working properly when called on my service (as opposed to my provider). I woke up realizing that I should refactor it a bit anyway. Hopefully I'll end up with something that doesn't require anyone to write the word "dummy" in their route configurations. The ideal for what I'm trying to do would be to have a property that is generically resolved without any particular assignment of its result back to a "real" configuration property... but if people need to assign undefined to an unused/fake property it really won't hurt much. No biggie. :) Hopefully I can make it happen with ui-router as is.

@laurelnaiad
Copy link

ui-router is expecting the promise function, not an object that contains the promise function. So this code works now.

      $stateProvider
        .state('contacts', {
          url: '/contacts',
          abstract: true,
          templateUrl: '/sample-ui-router/partials/contacts.html',
          controller: 'contactsController',
          resolve: {
            dummy: $couchPotatoProvider.resolveDependencies(['controllers/contactsController'])
          }
        })

and here's the project for those who wanted to do lazy-loading in ui-router (it will lazy load/register controllers, directives, services, and filters -- see the sample and sample-ui-router directories for examples -- I need to make better instructions but that may need to wait till tomorrow).

https://github.com/afterglowtech/angular-couchPotato

Hopefully that's one nibble off the subject of scalability (which this thread was supposed to be about). Please refer any questions about it over there...

I think @nateabele may have cooked up a way to dynamically register modules, and I'd like to look into how that could fit into this scheme soon.

...and now we can go back to more scalable examples here.

@caitp
Copy link
Contributor

caitp commented Aug 2, 2013

So the fact that

$stateProvider
  .state('report',{
    views: {
      'graph': {
        templateUrl: 'report-graph.html',
        controller: 'ReferenceYourControllerLikeThis'
      },
    }
  })

is a possibility, is great. It should be mentioned in the docs though -- it wasn't clear to me either and I was looking for an example with more than a single js file / block as well.

++ to mention this in the docs at https://github.com/angular-ui/ui-router/wiki/State-Manager

This is not actually working for me, it's not super clear why my registered controllers are not able to be instantiated. I've checked in the code, but it's not too clear just from reading what $$controller is, or how actual controllers are instantiated.

A small splash of the code looks like this:

angular.module( "chineseCommunityApp", ["ui.bootstrap", "ui.compat", "ngResource"] )
.config([
  '$stateProvider',
  '$urlRouterProvider',
  '$locationProvider',
  ($stateProvider, $urlRouterProvider, $locationProvider) ->
    $stateProvider
    #
    # State shared by the entire application
    # - Always render header
    # - Always render footer
    #
    .state 'app',
      url: ''
      abstract: true
      views:
        'header':
          templateUrl: '/views/header.html'
          controller: 'HeaderCtrl'
        'footer':
          templateUrl: '/views/footer.html'
          controller: 'FooterCtrl'

    # Event routing
    # Event is considered the "default" resource of the site,
    # and as such the root of the module is used
    #
    .state 'app.events',
      url: '/'
      views:
        'container@':
          templateUrl: '/views/event/list.html'
          controller: 'EventListCtrl'
    # ...

And, since this app has only recently been migrating to Angular/Angular-UI, only the EventList has been implemented:

angular.module("chineseCommunityApp")

.controller("EventListCtrl", [
  '$scope', 'dataService',
  ($scope, dataService) ->
    this.events = dataService.events
    $scope.events = this.events.query()
])

So, the 'header' and 'footer' views are just dandy, and their controllers are found. However, the 'container' view has some difficulties. Chromium and FF25 report the following:

Error: Argument 'EventListCtrl' is not a function, got undefined

The script containing the EventListCtrl is included later than the script containing the app routes, but it's still not clear to me why it's claiming to be undefined. I really don't see why that would be the case at all :(

This is, for me, a very big headache which I've only been able to resolve by squishing everything in a single script -- clearly that's not acceptable, so I'd like to figure it out. After searching for resources for nearly 18 hours now I'm just about ready to give up and resort to squeezing everything together in a hideous mess.

This is with release 0.0.1, I'm not sure if it's worth trying a bleeding edge version just to see if this can work for me in the future.

I know this isn't tech support, but I would be really curious to see what exactly the issue here is ._.

@timkindberg
Copy link
Contributor

Couple things to test...

So your index has a header, footer and container ui-view?

Try adding the controller as an anonymous function in the state config, just to make sure that works.

Last make sure this isn't the bug, check the FAQ for resolution to that one.

@timkindberg
Copy link
Contributor

Correction: ...isn't the <base href> bug…

@nateabele
Copy link
Contributor

I'd be happy to help you debug your code if you reposted it as actual JavaScript.

@caitp
Copy link
Contributor

caitp commented Aug 2, 2013

@timkindberg sorry, yes, the index template has:

  <header ui-view="header"></header>
  <div ui-view="container" class="container"></div>
  <footer ui-view="footer"></footer>

header and footer and container are each populated correctly (that is, templateUrl loads), and in the case of header and footer, data actually populates the templates, because the controllers are instantiated.

In the case of container, no request for data is ever made, and the controller itself is never instantiated, at least as far as I can tell based on the error message.

edit: I will test adding the controller as an anonymous function, but there are quite a number of these unfortunately, it won't really work if that's what I end up having to do app-wide :(

edit again: Yes, as an anonymous function it works perfectly. but as I've said, I can't really do that for this app and expect my client to be happy with the results ;_;

I don't believe it's as you call the <base href> bug, because so far only a single module of this app has been restructured in Angular (just the one module, and a handful of controllers). These were working nicely without ui-router, it's really being used as a way to provide nested views/nested routes, which may have otherwise been awkward. But yeah, the templates and their related content are loaded, and the controller script itself is loaded and registered (or at least, the function to register the script is called, who knows if it fails or not -- I'm not sure why it would). But using registered names for controllers for a view's controller in ui-router seems to not work for me without defining the controller inline as an anonymous function.

@nateabele I can provide code translated from js2coffee, but it does produce the expected code and I feel that it is somewhat more readable in coffee-form.

Here:

angular.module("chineseCommunityApp", ["ui.bootstrap", "ui.compat", "ngResource"]).config([
  '$stateProvider', '$urlRouterProvider', '$locationProvider', function($stateProvider, $urlRouterProvider, $locationProvider) {
    return $stateProvider.state('app', {
      url: '',
      abstract: true,
      views: {
        'header': {
          templateUrl: '/views/header.html',
          controller: 'HeaderCtrl'
        },
        'footer': {
          templateUrl: '/views/footer.html',
          controller: 'FooterCtrl'
        }
      }
    }).state('app.events', {
      url: '/',
      views: {
        'container@': {
          templateUrl: '/views/event/list.html',
          controller: 'EventListCtrl'
        }
      }
    });
  }
]);

and

angular.module("chineseCommunityApp")

.controller("EventListCtrl", [
  '$scope', 'dataService',
  ($scope, dataService) ->
    this.events = dataService.events
    $scope.events = this.events.query()
])

@caitp
Copy link
Contributor

caitp commented Aug 2, 2013

Oh, hey, crisis averted.

Apparently I'm an idiot and was not uglifying+concatenating the script with the controller in question, even though I could have sworn that it was in the processing list.

Well I feel dumb now! But at least things are working.

@jeme
Copy link
Contributor

jeme commented Aug 2, 2013

@caitp What are you using to build your solution?... If you have a structure to your scripts, you can often use wildcards to make sure you always collect on all your scripts...

All you need to remember to do is make sure your modules are defined before you try to add things to them...
So if you had say:

[scripts/module]
 ├ module.js
 ├ [controllers]
 │   ├ controllera.js
 │   └ controllerb.js
 ├ [directives]
 │   ├ directivea.js
 │   └ directiveb.js
 ├ [services]
 │   ├ servicea.js
 │   └ serviceb.js
 └ [resources]
     ├ resourcea.js
     └ resourceb.js

In many build systems you should be able to do something like:`

concat: [ 'module.js', 'controllers/**/*.js', 'directives/**/*.js', 'services/**/*.js', 'resources/**/*.js']

This is largely possible because angular works as it does, so even if "ControllerA" is defined before "ServiceB", yet it uses that service, it will still work.

Just a tip :)

@caitp
Copy link
Contributor

caitp commented Aug 2, 2013

It's grunt-contrib-usemin, it only operates on files it finds in marked blocks on template files. Not totally ideal for dev builds, but I'm preoccupied with other things to be worried about it right now. I could have sworn that I had renamed the script in the block, but I guess it's easy to miss and I felt really stupid because of it :) I'm just really glad it wasn't a bug with ui-router because that would really ruin my friday!

@jeme
Copy link
Contributor

jeme commented Aug 2, 2013

@caitp well your not the first one with the famous "I forgot that file" or similar, I think it's safe to say that we have all tried it before, and will all experience it again, so hence the suggestion/tip to at least eliminate some of it... :-)

I don't know if it's possible with the grunt-task your using though. But hopefully you will be able to find a solution, ones you have your hands more free, that won't require you to manually maintain lists of files in the future :)...

@pilwon
Copy link

pilwon commented Sep 28, 2013

@uberspeck @stu-salsbury @SparkiO @jeme @lmessinger
Come check out ultimate-seed :)

@lucassus
Copy link

Here you could find my proposal. The solution is based on ES6 modules
#1051 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests