• This repository has been archived on 08/Sep/2020
  • Stars
    star
    287
  • Rank 144,232 (Top 3 %)
  • Language
    JavaScript
  • Created almost 11 years ago
  • Updated over 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Example of scalable architecture for my NG-Conf 2014 talk

Scalable architecture in AngularJS

Resources

Intro

Originally forked from ProLoser/AngularJS-ORM

The project demonstrates ways to leverage ui-router to the greatest of it's abilities, how to keep your controllers down to 1 line of code, how to organize your services in a completely simplified manner, and how to leverage resolves like a god. Keeping your application down to a tiny handful of directives. Avoid the nightmare of lifecycle, transition, and session/stateful bugs. How to keep your $scope clean and tidy. It doesn't require using controller as and it doesn't turn everything into directives. Write your code to be angular-agnostic. Use the router to manage state, sessions and collections allowing you to avoid the problems addressed with complicated flux architectures. Sharing references means no more watchers and subscribers strewn across your app.

WIP: Please help clean up this repo!

The StyleGuide

Don't focus on src, css, view, controller, directives

In today's code, it's sensible keep modules together and small. HTML, JS and CSS are closely tied together, so we should organize projects that way.

Don't use controllerAs: 'vm'

This is a practice that is becoming predominant and actually screws up a lot of the benefits of controllerAs syntax. Instead of namespacing and bundling controller logic, the only benefit you gain is the little . dot notation in ng-model. The name vm does not tell you where the logic came from or what it has to do with, and does not allow you to work with multiple bundles of logic at the same time. As such, it should be completely avoided.

Don't do routing redirects inside services/factories

Even though you have an Auth service, or something else, you should always have them bubble their results up to the top of the promise chain. Alway do routing from controllers or state definitions, otherwise people have to go diving through a deeply nested chain of service callbacks to figure out why they keep getting a redirect loop.

Keep controllers implementation agnostic

Occasionally people use the ui-boostrap/modal service which lets you specify a controller and template and resolves. Inside that controller, you have access to $modalInstance, which is actually very bad practice. This means if your boss decides one day to no longer use a modal, you have to refactor the controller too (albeit trivially). Instead, have the $modalInstance resolve in a state definition, and use the onEnter() and onExit() callbacks to clean up implementation-specific logic. This leaves the controller free to just focus on it's internal view-related matters. Example

Keep It Simple, Stupid

Avoid thin wrappers that just cause obfuscation. If you are turning $http.get('/api/whatever/' + arg1 + '/' + arg2 + '/' + arg3) into whatever(arg1, arg2, arg3) you're not really gaining anything. Check out how we use resolves to handle breadcrumbs, something you need a lot of control and definition for in every state.

Understand $apply() in AngularJS

Keep your $scope.$apply() as close to the top of your stack trace as possible. You only need it if you plan to execute some angular-wrapped behavior. NEVER use the hack that checks for $scope.$$phase as this is a sign you are executing the same logic in angular and outside of angular.

If sections can be accessed logged in AND out, resolve a null authUser

The point of putting the logged in user in the top-level (authentication state) resolve is that all of your code can act on this data synchronously and leverage more bind-once in your views. If the user logs in or out, you should reload the entire state tree with the re-resolved authUser. This is because adding listeners for a user logging in and out in your entire codebase causes a massive amount of overhead, and you have to start coding listeners which deal with asynchronous lifecycles of users changing their state. Logging in and out happens at most once a week, so it's just better to not incur the performance and coding penalty just to allow simpler login.

Apps that don't let you access their content without being logged in don't have to deal with this use-case. Otherwise, $state.reload() or $state.go('...', {}, { reload: true }) are your friends.

Don't use controllerAs in routes. Use it in directives only.

pasted from slack

Controller instances are not shareable.

Meaning if you put logic into a controller (this.doSomething()) although you can reuse the logic elsewhere, you can’t reuse the instance. controllerAs syntax fixes a few issues, but it misleads people into thinking it’s okay to bloat controllers, which it isn’t (except for directives). Your stateful logic shouldn’t be in the controller, it should be in something stateful that can be shared so instead put most of your this.doSomething() into a factory, preferrably in a class or object instance. Then you can share it across multiple controllers, not just the logic, but the highly stateful data: resolve: { person: function(Person) { return new Person() } }

You inject person into multiple controllers and they all share the same data, and update in sync. So instead of doing this.doSomething() in your controller, and keeping track of info about how a Person works inside a controller (which is reusable, but not shareable) you should keep it inside your factories, which are even FURTHER abstracted from the view/angular-centric mindset, but are also instantiatable and manageable. I can control more concretely when a person is created, destroyed, updated, who has access to it, how it gets reused, etc. Angular no longer handles the lifecycle of the data, i do.

Controllers are generally 2-10 lines of code so my controller does nothing more than putting my business logic onto the view and occasionally wrapping business logic with view logic and / or route logic:

$scope.person = person;
$scope.save = function(){
  $scope.loading = true;
  person.save().then(() => {
    $scope.loading = false;
    $state.go(...);
  });
});

Now lets say you STILL wanted to use controllerAs yet still keep things organized the way I described. You CAN, except your bindings look like this: <input ng-model=β€œpersonCtrl.person.name”> instead of just <input ng-model=β€œperson.name”>. That is fairly trivial, but there is another big annoyance I have with it. Your view is no longer reusable with different controllers.

Lets say i want to use the same view with a create vs edit controller. My view bindings have to be <input ng-model=β€œcreateCtrl.person.name”> or <input ng-model=β€œupdateCtrl.person.name”> or lets just say you call the controller person or personCtrl. You could have a personEditCtrl inside of a personViewCtrl so who gets which namespace?

When you start to build big apps and deal with these design questions, I find controllerAs is NOT the answer.
I like having brittle scope bindings, for instance you may have cringed when i did $scope.loading = true because if you put a ng-click=β€œloading = false” inside of an ng-if it won’t work. Except I want to keep my view-state flags shallow, simple, clean, and I don’t want the view to update them. I like having 2 controllers, both with their own loading flags that are not the same variable. I prefer not having to namespace all my loading flags by my controller name or variable or state name, and yet my views simply don’t care. To them, doing ng-show=personLoading is the same as doing ng-show=personCtrl.loading and my view becomes more brittle and heavily tied to the controller in use.

In directives, it’s a completely different ballgame
Because directives are entirely about view logic, and should almost never do business logic. Period. The old way of doing directives you put all your shit into a linking function. A directive controller is essentially identical to a linking function except it is reusable by other directives, a visual-widget's externally visible api. If you look at ui-select i love controllerAs because I can give it methods like uiSelectCtrl.open() and that really is what it’s doing. It’s the controls for my ui-select widget.

personCtrl.person.open() just doesn’t make sense if you read it. The β€˜controller’ (guy doing shit to people) isn’t the one with the method, the object itself has methods that work upon itself. Doing personCtrl.open() is just a sign of bad design, because controllers should be skinny.

Never use scope inheritence across controllers (ui-views)

This is like using $rootScope, it's equivalent to using global variables and relies upon assumptions that variables will exist. It makes controllers (and views) depend on variables that may or may not exist, and makes it difficult for developers to see where these variables came from. If you wish to use a service, resolve or something inside of a route controller or view, you should always re-inject the dependency and place it on the scope redundantly. This is single-handedly the key to ensuring that your codebase has a solid contract and that the quality of your code stands up to refactoring. Even if it means placing the same objects onto the same variables in the same place on the scope, do it. Period.

The only time scope inheritence is good is when working with directives. Directives are view-centric data that do nothing more than decorate the scope based on what came before or what comes after, and even this is quickly being deprecated in favor of explicit attributes and highly reusable directives.

Leverage Directive-controller communication to keep directives small and modular

If your directive is getting a bit unwieldy (assuming this is an open-sourceable directive) remember that you can use a directive's require attribute to access the controller of another directive. Think of this as a directives externally accessible api. This is where you can concretely define methods and attributes that can be accessed by other directives, instead of relying on the scope. You can then package sub-features of a directive and can pick and choose which ones to load when you build your templates. Example: <div ui-grid ui-grid-sortable ui-grid-paginate>

Use the object to manage data state

Instead of putting heavy load on view state flags that describe how things should look, use grammar that describes the verb-like state of the data or action itself. This can then be repurposed in multiple ways in the view as to the visual representation, and is not tied to visual information.

Instead of $scope.showSpinner or $scope.loading use task.uploading or project.saving which could be rendered as a spinner, form, panel, whatever. The point is the state flag is agnostic about the visual implementation.

More Repositories

1

bootstrap

PLEASE READ THE PROJECT STATUS BELOW. Native AngularJS (Angular) directives for Bootstrap. Smaller footprint (20kB gzipped), no 3rd party JS dependencies (jQuery, bootstrap JS) required. Please read the README.md file before submitting an issue!
JavaScript
14,347
star
2

ui-router

The de-facto solution to flexible routing with nested views in AngularJS
TypeScript
13,611
star
3

ui-grid

UI Grid: an Angular Data Grid
JavaScript
5,386
star
4

ui-select

AngularJS-native version of Select2 and Selectize
JavaScript
3,278
star
5

angular-google-maps

AngularJS directives for the Google Maps Javascript API
CoffeeScript
2,530
star
6

angular-ui-OLDREPO

DISCONTINUED REPO: This project has been restructured ->
JavaScript
2,210
star
7

ui-calendar

A complete AngularJS directive for the Arshaw FullCalendar.
JavaScript
1,494
star
8

ui-utils

Deprecated collection of modules for angular
JavaScript
1,443
star
9

AngularJS-sublime-package

AngularJS code completion, snippets, go to definition, quick panel search, and more.
JavaScript
1,422
star
10

ui-sortable

jQuery UI Sortable for AngularJS
JavaScript
1,265
star
11

ui-select2

AngularJS wrapper for select2 (deprecated, use angular-ui/ui-select)
JavaScript
594
star
12

ui-ace

This directive allows you to add ACE editor elements.
JavaScript
580
star
13

ui-tinymce

AngularUI wrapper for TinyMCE
JavaScript
490
star
14

ui-layout

This directive allows you to split !
JavaScript
410
star
15

ui-mask

Mask on an input field so the user can only type pre-determined pattern
JavaScript
393
star
16

ui-codemirror

This directive allows you to add CodeMirror to your textarea elements.
JavaScript
383
star
17

ui-grid.info

Website for ui-grid
377
star
18

ui-scroll

Unlimited bidirectional scrolling over a limited element buffer for AngularJS applications
JavaScript
324
star
19

ui-leaflet

AngularJS directive to embed an interact with maps managed by Leaflet library
JavaScript
316
star
20

ui-map

Google Maps
JavaScript
285
star
21

AngularJS-Atom

An AngularJS package for Github's Atom editor
CoffeeScript
285
star
22

ui-date

jQuery UI Datepicker for AngularJS
JavaScript
266
star
23

ui-slider

jQuery UI Slider for AngularJS
HTML
265
star
24

AngularJS-brackets

AngularJS plugin for Brackets (booya)
JavaScript
217
star
25

bootstrap-bower

This is a bower repository to hold Angular UI Bootstrap releases.
JavaScript
158
star
26

alias

Create concise aliases for third-party directives and templates
JavaScript
115
star
27

ui-mention

Facebook-like @mentions for text inputs built around composability
JavaScript
115
star
28

ui-validate

General-purpose validator for ngModel
JavaScript
111
star
29

ui-chart

This directive lets you use jqPlot with Angular
JavaScript
111
star
30

ui-uploader

Customizable file uploader
JavaScript
108
star
31

ui-tour

A native tour-type directive that will lace easily-controllable tooltips throughout your app
HTML
94
star
32

AngularJs.tmbundle

AngularJs Textmate Bundle
87
star
33

angular-ui.github.com

Angular UI homepage
HTML
80
star
34

bower-ui-grid

Bower package for UI Grid
JavaScript
39
star
35

ui-scrollpoint

Add a 'ui-scrollpoint' class to elements when the page scrolls past them.
JavaScript
29
star
36

ui-event

Bind a callback to any event not natively supported by Angular
JavaScript
28
star
37

AngularJS-tern-plugin

A, slow'ish moving, WIP plugin for Tern that enables it to understand AngularJS dependency injection.
JavaScript
26
star
38

community

Placeholder repo for discussion
24
star
39

angular-ui-publisher

Helper component for building and publishing your angular modules as bower components
JavaScript
20
star
40

ui-leaflet-draw

angular directive for ui-leaflet to utilize Leaflet.Draw
CoffeeScript
16
star
41

ui-indeterminate

Toggle a checkbox input's special 'indeterminate' property
JavaScript
14
star
42

angular-ui-router-bower

Bower publishing for Angular UI-Router for ng1
14
star
43

ui-calendar2

Angular Native Calendar, inspired by ui-calendar
JavaScript
13
star
44

angular-ui-docs

Helper component for building your angular modules as bower components
JavaScript
10
star
45

ng-grid-legacy

ng-grid 2.x
JavaScript
5
star
46

builder

Build server for angular-ui
JavaScript
3
star
47

ui-position

Helper class used by a lot of angular projects on the web
1
star