From 9d702c916126d0ca47d02a144cb4debe08904447 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:42:13 -0700 Subject: [PATCH 01/36] Update README.md --- README.md | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/README.md b/README.md index a4e56cd..c4a0d2a 100644 --- a/README.md +++ b/README.md @@ -11,43 +11,3 @@ The project demonstrates ways to **leverage ui-router to the greatest of it's ab * [Slidedeck](http://slid.es/proloser/angularjs-orm) (OLD) * [Conference talk video](http://www.youtube.com/watch?v=Iw-3qgG_ipU) (OLD) [![NG-Conf 2014 Talk](http://i1.ytimg.com/vi/Iw-3qgG_ipU/0.jpg)](http://www.youtube.com/watch?v=Iw-3qgG_ipU) - -ES6 Syntax ------------- - -I use ES6 because it gives me easy-to-code classes and because the last line is always returned in arrow functions (which is great for promise chaining). You do not have to use ES6, and should not refactor into it 'just because'. - -### How To Read - -**Javascript:** -```js -function( x, y, z ){ - return z -} - -function x(z) { - // constructor - this.y = z; -} -x.prototype.method = function(){} -``` -**ES6** -```js -// `this` is bound to OUTER scope -( x, y, z ) => { - this.whatever; -} -// single-line functions without brackets returns their expression -( x ) => x.y -// single-argument signatures don't need parenthesis -response => response.data - - - -class x { - constructor(z) { - this.y = z; - } - method(z) {} -} -``` From 8b7f266aee7ba914f8a1015ddcdba346caa5366d Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:42:27 -0700 Subject: [PATCH 02/36] Create ES6-Cheat-Sheet.md --- ES6-Cheat-Sheet.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 ES6-Cheat-Sheet.md diff --git a/ES6-Cheat-Sheet.md b/ES6-Cheat-Sheet.md new file mode 100644 index 0000000..6239ab7 --- /dev/null +++ b/ES6-Cheat-Sheet.md @@ -0,0 +1,39 @@ +ES6 Syntax +------------ + +I use ES6 because it gives me easy-to-code classes and because the last line is always returned in arrow functions (which is great for promise chaining). You do not have to use ES6, and should not refactor into it 'just because'. + +### How To Read + +**Javascript:** +```js +function( x, y, z ){ + return z +} + +function x(z) { + // constructor + this.y = z; +} +x.prototype.method = function(){} +``` +**ES6** +```js +// `this` is bound to OUTER scope +( x, y, z ) => { + this.whatever; +} +// single-line functions without brackets returns their expression +( x ) => x.y +// single-argument signatures don't need parenthesis +response => response.data + + + +class x { + constructor(z) { + this.y = z; + } + method(z) {} +} +``` From e5a53966165684e88e225385d6c22f656979757d Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:42:54 -0700 Subject: [PATCH 03/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4a0d2a..9d07208 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ The project demonstrates ways to **leverage ui-router to the greatest of it's ab * [Tips and Tricks](https://github.com/ProLoser/AngularJS-ORM/blob/master/TIPS-AND-TRICKS.md) * [Older Branch (uses coffeescript and sockets)](https://github.com/ProLoser/AngularJS-ORM/tree/coffee-sockets) -* [ES6 Cheat Sheet](#es6-syntax) +* [ES6 Cheat Sheet](ES6-Cheat-Sheet.md) * [Slidedeck](http://slid.es/proloser/angularjs-orm) (OLD) * [Conference talk video](http://www.youtube.com/watch?v=Iw-3qgG_ipU) (OLD) [![NG-Conf 2014 Talk](http://i1.ytimg.com/vi/Iw-3qgG_ipU/0.jpg)](http://www.youtube.com/watch?v=Iw-3qgG_ipU) From 43f31091172c51490ef0f82c68d19b08532ac947 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:43:27 -0700 Subject: [PATCH 04/36] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d07208..46c4ed0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ -AngularJS-ORM - Example of scalable architecture +Example of scalable architecture ============= +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. From 48edd4ec4f6acc77bb112a7fa8c0979cdbdabea0 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:43:36 -0700 Subject: [PATCH 05/36] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 46c4ed0..10ee96f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -Example of scalable architecture -============= AngularJS ORM +============= +Example of scalable architecture ------------- 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. From bf38af04cd8df49fe778118943001678f85b8f2d Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:44:07 -0700 Subject: [PATCH 06/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 10ee96f..ca47201 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ AngularJS ORM ============= -Example of scalable architecture +Scalable architecture in AngularJS ------------- 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. From 207642e83306070d5a19b549525c0521d0e91195 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:45:39 -0700 Subject: [PATCH 07/36] Update and rename TIPS-AND-TRICKS.md to StyleGuide.md --- TIPS-AND-TRICKS.md => StyleGuide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename TIPS-AND-TRICKS.md => StyleGuide.md (99%) diff --git a/TIPS-AND-TRICKS.md b/StyleGuide.md similarity index 99% rename from TIPS-AND-TRICKS.md rename to StyleGuide.md index c24eb4e..e617d34 100644 --- a/TIPS-AND-TRICKS.md +++ b/StyleGuide.md @@ -1,4 +1,4 @@ -Tips / Tricks +StyleGuide ------------- #### **I don't use a src, css, view, controller, etc folders** From 7534faa078da040afee581f2c1c340a70d95389e Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:45:58 -0700 Subject: [PATCH 08/36] Update and rename StyleGuide.md to TheGuide.md --- StyleGuide.md => TheGuide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename StyleGuide.md => TheGuide.md (99%) diff --git a/StyleGuide.md b/TheGuide.md similarity index 99% rename from StyleGuide.md rename to TheGuide.md index e617d34..e88ac46 100644 --- a/StyleGuide.md +++ b/TheGuide.md @@ -1,4 +1,4 @@ -StyleGuide +The StyleGuide ------------- #### **I don't use a src, css, view, controller, etc folders** From 65cd0a3f9586bb44ff07e5dc8b60f34bd82dcc31 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:46:45 -0700 Subject: [PATCH 09/36] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ca47201..970bd6d 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ Scalable architecture in AngularJS 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. +### [The StyleGuide](TheGuide.md) + ### Resources -* [Tips and Tricks](https://github.com/ProLoser/AngularJS-ORM/blob/master/TIPS-AND-TRICKS.md) * [Older Branch (uses coffeescript and sockets)](https://github.com/ProLoser/AngularJS-ORM/tree/coffee-sockets) * [ES6 Cheat Sheet](ES6-Cheat-Sheet.md) * [Slidedeck](http://slid.es/proloser/angularjs-orm) (OLD) From 6e973295cbecd226537f0c547bfadfa48942df65 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:48:14 -0700 Subject: [PATCH 10/36] Update README.md --- README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 970bd6d..6917deb 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,8 @@ AngularJS ORM Scalable architecture in AngularJS ------------- -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. - -### [The StyleGuide](TheGuide.md) +### [Resources](Resources.md) -### Resources +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. -* [Older Branch (uses coffeescript and sockets)](https://github.com/ProLoser/AngularJS-ORM/tree/coffee-sockets) -* [ES6 Cheat Sheet](ES6-Cheat-Sheet.md) -* [Slidedeck](http://slid.es/proloser/angularjs-orm) (OLD) -* [Conference talk video](http://www.youtube.com/watch?v=Iw-3qgG_ipU) (OLD) -[![NG-Conf 2014 Talk](http://i1.ytimg.com/vi/Iw-3qgG_ipU/0.jpg)](http://www.youtube.com/watch?v=Iw-3qgG_ipU) +### The StyleGuide From 8e827a9e76b56bee07c984036d0b9f24130c2935 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:48:30 -0700 Subject: [PATCH 11/36] Create Resources.md --- Resources.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Resources.md diff --git a/Resources.md b/Resources.md new file mode 100644 index 0000000..4ae3097 --- /dev/null +++ b/Resources.md @@ -0,0 +1,8 @@ +Resources +========= + +* [Older Branch (uses coffeescript and sockets)](https://github.com/ProLoser/AngularJS-ORM/tree/coffee-sockets) +* [ES6 Cheat Sheet](ES6-Cheat-Sheet.md) +* [Slidedeck](http://slid.es/proloser/angularjs-orm) (OLD) +* [Conference talk video](http://www.youtube.com/watch?v=Iw-3qgG_ipU) (OLD) +[![NG-Conf 2014 Talk](http://i1.ytimg.com/vi/Iw-3qgG_ipU/0.jpg)](http://www.youtube.com/watch?v=Iw-3qgG_ipU) From e683a875b22336ce626c6096234aabb4ad047ab9 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:49:19 -0700 Subject: [PATCH 12/36] Update README.md --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6917deb..2eddaa1 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ -AngularJS ORM +AngularJS ORM: Scalable architecture in AngularJS ============= -Scalable architecture in AngularJS + +[Resources](Resources.md) ------------- -### [Resources](Resources.md) + +Intro +------------- 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. -### The StyleGuide + +The StyleGuide +------------- From da5a03e4448afeab41371429f7b59f1a174652c7 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:49:45 -0700 Subject: [PATCH 13/36] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2eddaa1..e0b7979 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -AngularJS ORM: Scalable architecture in AngularJS +Scalable architecture in AngularJS ============= [Resources](Resources.md) @@ -8,6 +8,8 @@ AngularJS ORM: Scalable architecture in AngularJS 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. From ebad0376f5ee8e77df559e527fafb579177992c4 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:49:59 -0700 Subject: [PATCH 14/36] Delete TheGuide.md --- TheGuide.md | 103 ---------------------------------------------------- 1 file changed, 103 deletions(-) delete mode 100644 TheGuide.md diff --git a/TheGuide.md b/TheGuide.md deleted file mode 100644 index e88ac46..0000000 --- a/TheGuide.md +++ /dev/null @@ -1,103 +0,0 @@ -The StyleGuide -------------- - -#### **I don't use a src, css, view, controller, etc folders** -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. - -#### **If you can't open-source your directives, they probably shouldn't exist** -A lot of people will create what I refer to as 'one-off' directives. They should usually just be sub-states. -If you create directives that are specific to your app's business logic, and aren't focused on purely UI visual implementation (regardless of data, application, etc) then you are too tightly coupling your business logic to your view. You are making it more difficult to quickly refactor your view or view structure. You have to track down where business logic is being executed or modified in multiple places. You start keeping track of data state and lifecycle and implementing things like events and streams because your view lifecycle isn't consistent with your data lifecycle. -Instead, 0 business logic in views. Rendering logic in views only. Publicly, reusable, agnostic, unopinionated, highly versatile/reusable view logic. - -#### **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](https://github.com/ProLoser/AngularJS-ORM/blob/62ce345d6b6152a332562d58b0ec73d194ca3d8c/modules/Authentication/Login.js#L28-L37)** -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](https://github.com/ProLoser/AngularJS-ORM/blob/62ce345d6b6152a332562d58b0ec73d194ca3d8c/modules/Authentication/Login.js#L28-L37) - -#### 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](https://github.com/ProLoser/AngularJS-ORM/blob/b6482fab60a5b0207e2a39929681b10668552745/modules/Authentication/Authenticated.js#L23-L25) to [handle breadcrumbs](https://github.com/ProLoser/AngularJS-ORM/blob/b6482fab60a5b0207e2a39929681b10668552745/modules/Project/Project.js#L22-L28), 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](https://github.com/ProLoser/AngularJS-ORM/blob/8f6fafa2048ec301672c23828ba4eb591fb6cca5/modules/Socket.js#L46). 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: `` -instead of just ``. -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 `` or `` -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](https://github.com/angular-ui/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](#if-you-cant-open-source-your-directives-they-probably-shouldnt-exist)) 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: -`
` - -#### 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 -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**. - -[Example of clean state flag management](https://github.com/ProLoser/AngularJS-ORM/tree/7eee7eac0573bb8d53beef3cc2e11bb75cfe967f#L100-L133) From 7713b8f07d2710006bf8d70f22b6803734e581da Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:50:23 -0700 Subject: [PATCH 15/36] Update README.md --- README.md | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/README.md b/README.md index e0b7979..e5669f5 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,104 @@ The project demonstrates ways to **leverage ui-router to the greatest of it's ab The StyleGuide ------------- + +#### **I don't use a src, css, view, controller, etc folders** +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. + +#### **If you can't open-source your directives, they probably shouldn't exist** +A lot of people will create what I refer to as 'one-off' directives. They should usually just be sub-states. +If you create directives that are specific to your app's business logic, and aren't focused on purely UI visual implementation (regardless of data, application, etc) then you are too tightly coupling your business logic to your view. You are making it more difficult to quickly refactor your view or view structure. You have to track down where business logic is being executed or modified in multiple places. You start keeping track of data state and lifecycle and implementing things like events and streams because your view lifecycle isn't consistent with your data lifecycle. +Instead, 0 business logic in views. Rendering logic in views only. Publicly, reusable, agnostic, unopinionated, highly versatile/reusable view logic. + +#### **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](https://github.com/ProLoser/AngularJS-ORM/blob/62ce345d6b6152a332562d58b0ec73d194ca3d8c/modules/Authentication/Login.js#L28-L37)** +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](https://github.com/ProLoser/AngularJS-ORM/blob/62ce345d6b6152a332562d58b0ec73d194ca3d8c/modules/Authentication/Login.js#L28-L37) + +#### 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](https://github.com/ProLoser/AngularJS-ORM/blob/b6482fab60a5b0207e2a39929681b10668552745/modules/Authentication/Authenticated.js#L23-L25) to [handle breadcrumbs](https://github.com/ProLoser/AngularJS-ORM/blob/b6482fab60a5b0207e2a39929681b10668552745/modules/Project/Project.js#L22-L28), 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](https://github.com/ProLoser/AngularJS-ORM/blob/8f6fafa2048ec301672c23828ba4eb591fb6cca5/modules/Socket.js#L46). 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: `` +instead of just ``. +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 `` or `` +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](https://github.com/angular-ui/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](#if-you-cant-open-source-your-directives-they-probably-shouldnt-exist)) 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: +`
` + +#### 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 +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**. + +[Example of clean state flag management](https://github.com/ProLoser/AngularJS-ORM/tree/7eee7eac0573bb8d53beef3cc2e11bb75cfe967f#L100-L133) From 5ae0879c52819060dffc840996db491629f8b90e Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:51:41 -0700 Subject: [PATCH 16/36] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e5669f5..b632f36 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,18 @@ The project demonstrates ways to **leverage ui-router to the greatest of it's ab The StyleGuide ------------- -#### **I don't use a src, css, view, controller, etc folders** +#### Don't worry about src, css, view, controller, etc folders 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. -#### **If you can't open-source your directives, they probably shouldn't exist** +#### If you can't open-source your directives, they probably shouldn't exist A lot of people will create what I refer to as 'one-off' directives. They should usually just be sub-states. If you create directives that are specific to your app's business logic, and aren't focused on purely UI visual implementation (regardless of data, application, etc) then you are too tightly coupling your business logic to your view. You are making it more difficult to quickly refactor your view or view structure. You have to track down where business logic is being executed or modified in multiple places. You start keeping track of data state and lifecycle and implementing things like events and streams because your view lifecycle isn't consistent with your data lifecycle. Instead, 0 business logic in views. Rendering logic in views only. Publicly, reusable, agnostic, unopinionated, highly versatile/reusable view logic. -#### **Don't do routing redirects inside services/factories** +#### 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](https://github.com/ProLoser/AngularJS-ORM/blob/62ce345d6b6152a332562d58b0ec73d194ca3d8c/modules/Authentication/Login.js#L28-L37)** +#### [Keep controllers implementation agnostic](https://github.com/ProLoser/AngularJS-ORM/blob/62ce345d6b6152a332562d58b0ec73d194ca3d8c/modules/Authentication/Login.js#L28-L37) 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](https://github.com/ProLoser/AngularJS-ORM/blob/62ce345d6b6152a332562d58b0ec73d194ca3d8c/modules/Authentication/Login.js#L28-L37) #### Keep It Simple, Stupid From ee5c2b34b8490fd24bc10f2fa7f4ea261ab9624c Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:53:24 -0700 Subject: [PATCH 17/36] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b632f36..23aeb5d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ 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 ------------- From bd7b7e266b712f97e7ff7002e0733c3c49055fae Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:53:33 -0700 Subject: [PATCH 18/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23aeb5d..5466ad7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ 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!** +**WIP: Please help clean up this repo!** The StyleGuide From 0c837196c0d8dc16cdaa12a0eaf36699cbd008ca Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:53:46 -0700 Subject: [PATCH 19/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5466ad7..3ce7585 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ 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!** +**_WIP: Please help clean up this repo!_** The StyleGuide From b1ccba22134af915a74f6c52161a150ddbb57264 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:54:37 -0700 Subject: [PATCH 20/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ce7585..a01b4ab 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The project demonstrates ways to **leverage ui-router to the greatest of it's ab The StyleGuide ------------- -#### Don't worry about src, css, view, controller, etc folders +#### 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. #### If you can't open-source your directives, they probably shouldn't exist From 97ee1f32e877bf27f73398432c1fca3ece0718e6 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:57:10 -0700 Subject: [PATCH 21/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a01b4ab..d5974f3 100644 --- a/README.md +++ b/README.md @@ -117,4 +117,4 @@ in the view as to the visual representation, and is not tied to visual informati 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**. -[Example of clean state flag management](https://github.com/ProLoser/AngularJS-ORM/tree/7eee7eac0573bb8d53beef3cc2e11bb75cfe967f#L100-L133) +[Example of clean state flag management](modules/Task/Task.js#L100-L138) From a93469097b270ba611012032a4761fe85f3964bd Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:57:57 -0700 Subject: [PATCH 22/36] Update Task.js --- modules/Task/Task.js | 51 -------------------------------------------- 1 file changed, 51 deletions(-) diff --git a/modules/Task/Task.js b/modules/Task/Task.js index b842cef..1e656f6 100644 --- a/modules/Task/Task.js +++ b/modules/Task/Task.js @@ -88,54 +88,3 @@ module.controller( 'TaskForm', ($scope, task) => { // injected `task` is either a new object or an existing object $scope.task = task; }); - -module.factory( 'Task', (BaseObject, $http) => { - class Task extends BaseObject { - static list(projectId) { - return $http.get('/api/tasks', { params: { project_id: projectId } }) - .then( (response) => response.data.map( task => new Task(task) ) ); - } - - - /** - * task.create() - Creates a new task - * - * @note Demonstrates how to have the create wait until uploading is complete - * Normally you might have to wait until the attachment is done before enabling - * submission of the form, but this way allows the user to submit before uploading - * is complete and makes it easy to show loading spinners. - * - * You could check either the `task.saving`, `task.creating`, or `task.uploading` - * properties for truthy value when the user tries to navigate away from the page. - * - */ - create() { - // wraps `this.uploading` in a promise that resolves immediately if it is `null` or waits for the promise - return this.creating = $q.when(this.uploading) - .then( () => $http.post('/api/tasks', this) ) // uploading callback - .then( response => return Object.assign(this, response.data) ) // creating callback - .finally( () => this.creating = null ); // state cleanup (doesn't affect chaining) - } - - update() { - // wraps `this.uploading` in a promise that resolves immediately if it is `null` or waits for the promise - return this.updating = $q.when(this.uploading) - .then( () => $http.post(`/api/tasks/${this.id}`, this) ) // uploading callback - .then( response => return Object.assign(this, response.data) ) // creating callback - .finally( () => this.updating = null ); // state cleanup (doesn't affect chaining) - } - - /** - * task.upload() - Allow you to upload attachments to issues - * - * @note Added to demonstrate clean ways to have 1 method wait for another method to finish - */ - upload(attachment) { - return this.uploading = $http.post(`/api/attachments`, attachment) - .then( response => this.attachments = response.data ) - .finally( () => this.uploading = null ); // state cleanup (doesn't affect chaining) - } - } - - return Task; -}); From ca7c86589094b517001abcf4ec481cf0ed400f3c Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:58:20 -0700 Subject: [PATCH 23/36] Create TaskObject.js --- modules/Task/TaskObject.js | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 modules/Task/TaskObject.js diff --git a/modules/Task/TaskObject.js b/modules/Task/TaskObject.js new file mode 100644 index 0000000..054381b --- /dev/null +++ b/modules/Task/TaskObject.js @@ -0,0 +1,51 @@ +module = angular.module('App.Task'); +module.factory( 'Task', (BaseObject, $http) => { + class Task extends BaseObject { + static list(projectId) { + return $http.get('/api/tasks', { params: { project_id: projectId } }) + .then( (response) => response.data.map( task => new Task(task) ) ); + } + + + /** + * task.create() - Creates a new task + * + * @note Demonstrates how to have the create wait until uploading is complete + * Normally you might have to wait until the attachment is done before enabling + * submission of the form, but this way allows the user to submit before uploading + * is complete and makes it easy to show loading spinners. + * + * You could check either the `task.saving`, `task.creating`, or `task.uploading` + * properties for truthy value when the user tries to navigate away from the page. + * + */ + create() { + // wraps `this.uploading` in a promise that resolves immediately if it is `null` or waits for the promise + return this.creating = $q.when(this.uploading) + .then( () => $http.post('/api/tasks', this) ) // uploading callback + .then( response => return Object.assign(this, response.data) ) // creating callback + .finally( () => this.creating = null ); // state cleanup (doesn't affect chaining) + } + + update() { + // wraps `this.uploading` in a promise that resolves immediately if it is `null` or waits for the promise + return this.updating = $q.when(this.uploading) + .then( () => $http.post(`/api/tasks/${this.id}`, this) ) // uploading callback + .then( response => return Object.assign(this, response.data) ) // creating callback + .finally( () => this.updating = null ); // state cleanup (doesn't affect chaining) + } + + /** + * task.upload() - Allow you to upload attachments to issues + * + * @note Added to demonstrate clean ways to have 1 method wait for another method to finish + */ + upload(attachment) { + return this.uploading = $http.post(`/api/attachments`, attachment) + .then( response => this.attachments = response.data ) + .finally( () => this.uploading = null ); // state cleanup (doesn't affect chaining) + } + } + + return Task; +}); From 1c426630be74396c8a819d60e83ac29668525b1a Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:58:51 -0700 Subject: [PATCH 24/36] Update Project.js --- modules/Project/Project.js | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/modules/Project/Project.js b/modules/Project/Project.js index adf9993..afa0a00 100644 --- a/modules/Project/Project.js +++ b/modules/Project/Project.js @@ -83,32 +83,3 @@ module.controller( 'ProjectForm', ($scope, project) => { // injected `project` is either a new object or an existing object $scope.project = project; }); - -module.factory('ProjectObject', (BaseObject, $http) => { - class Project extends BaseObject { - static list(userId) { - return $http.get('/api/projects', { params: { user_id: userId } }) - .then( (response) => response.data.map( project => new Project(project) ) ); - } - - static get(id) { - return $http.get(`/api/projects/${id}`) - .then( (response) => new Project(response.data)); - } - - - create() { - return $http.post('/api/projects', this ) - .then( (response) => { - this.id = response.data.id; - return response.data; - }); - } - - update() { - return $http.put(`/api/projects/${this.id}`, this); - } - } - - return Project; -}); From 00423df0a965ddf3e03596e7f5280c921d00761a Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:59:13 -0700 Subject: [PATCH 25/36] Create ProjectObject.js --- modules/Project/ProjectObject.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 modules/Project/ProjectObject.js diff --git a/modules/Project/ProjectObject.js b/modules/Project/ProjectObject.js new file mode 100644 index 0000000..10c52a9 --- /dev/null +++ b/modules/Project/ProjectObject.js @@ -0,0 +1,29 @@ +module = angular.module('App.Project') +module.factory('ProjectObject', (BaseObject, $http) => { + class Project extends BaseObject { + static list(userId) { + return $http.get('/api/projects', { params: { user_id: userId } }) + .then( (response) => response.data.map( project => new Project(project) ) ); + } + + static get(id) { + return $http.get(`/api/projects/${id}`) + .then( (response) => new Project(response.data)); + } + + + create() { + return $http.post('/api/projects', this ) + .then( (response) => { + this.id = response.data.id; + return response.data; + }); + } + + update() { + return $http.put(`/api/projects/${this.id}`, this); + } + } + + return Project; +}); From e614905b67f35ac04d6535ddef42bbf820dfbeea Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 14:59:38 -0700 Subject: [PATCH 26/36] Update User.js --- modules/User/User.js | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/modules/User/User.js b/modules/User/User.js index 0e4db9c..c3e36c2 100644 --- a/modules/User/User.js +++ b/modules/User/User.js @@ -70,33 +70,6 @@ module.controller( 'UserForm', ($scope, user, wizard) => { $scope.wizard = wizard; }); -module.factory( 'User', (BaseObject, $http) => { - class User extends BaseObject { - static list() { - return $http.get('/api/users') - .then( (response) => response.data.map(User.new)); - } - - - // checks validity of the property (probably should be in BaseObject) - validate(property) { - if (!User.rules[property]) - return true; - - var pattern = new RegExp(User.rules[property]); - return pattern.test(this[property]); - } - } - - User.rules = { - name: '[a-zA-Z0-1]{4,}', // 4 or more alphanum chars - email: '.+this.gmail.com' - }; - - return User; -}); - - module.factory( 'RegistrationWizard', () => { class RegistrationWizard { constructor(user) { From 4d533c08fd0fab224d4c936d7253e678b0c3c2ac Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Jul 2015 15:00:00 -0700 Subject: [PATCH 27/36] Create UserObject.js --- modules/User/UserObject.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 modules/User/UserObject.js diff --git a/modules/User/UserObject.js b/modules/User/UserObject.js new file mode 100644 index 0000000..016a1e4 --- /dev/null +++ b/modules/User/UserObject.js @@ -0,0 +1,26 @@ +var module = angular.module('App.User'); +module.factory( 'User', (BaseObject, $http) => { + class User extends BaseObject { + static list() { + return $http.get('/api/users') + .then( (response) => response.data.map(User.new)); + } + + + // checks validity of the property (probably should be in BaseObject) + validate(property) { + if (!User.rules[property]) + return true; + + var pattern = new RegExp(User.rules[property]); + return pattern.test(this[property]); + } + } + + User.rules = { + name: '[a-zA-Z0-1]{4,}', // 4 or more alphanum chars + email: '.+this.gmail.com' + }; + + return User; +}); From c9f27973ab69a18751975dcc03c80a06c0cc588b Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Thu, 23 Jul 2015 11:21:10 -0700 Subject: [PATCH 28/36] Update App.js --- modules/App.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/modules/App.js b/modules/App.js index f96becf..51249aa 100644 --- a/modules/App.js +++ b/modules/App.js @@ -1,12 +1,26 @@ var module = angular.module('App', ['ui.router', 'App.Authentication', 'App.Guest']); - -module.config(function($urlRouterProvider, $rootScope) { +module.config(function($urlRouterProvider) { + // Default URL if no matches $urlRouterProvider.otherwise('/projects'); + +}); - // Global catching of uiRouter errors (for development) - $rootScope.$on('stateChangeError', (event, toState, toParams, fromState, fromParams, error) => { - console.log( event, toState, toParams, fromState, fromParams, error ); +module.run(function($rootScope){ + + $rootScope.$on('$stateChangeError', console.error.bind(console)); + + // Add support for a `redirectTo` property on state definitions for default children + $rootScope.$on('$stateChangeStart', function(event, toState){ + if (toState.redirectTo) { + if (angular.isFunction(toState.redirectTo)) { + toState.redirectTo.apply(toState, [].slice.call(arguments)); + } else { + event.preventDefault(); + $state.go(toState.redirectTo); + } + } }); + }); From ddd916f2c683466298d7e1eef98f4c4d6902a3e9 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 29 Jul 2015 17:51:50 -0700 Subject: [PATCH 29/36] Update Object.js --- modules/Object.js | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/modules/Object.js b/modules/Object.js index d18a30e..43ec95e 100644 --- a/modules/Object.js +++ b/modules/Object.js @@ -20,6 +20,25 @@ module.factory('BaseObject', ($q) => { * Unsubscribe unnecessary overhead */ close() {} + + /** + * Prevents parallel queries and provides a stateful flag for view rendering + * + * Also allows you to permanently store cache to prevent any subsequent calls + * + * @param {string} name Name of property flag to use for caching the promise + * @param {function} callback Executes the query and returns a promise + * @param {boolean} [permanent] If the cache should be removed upon completion (default:false) + */ + cache(name, callback, permanent = false) { + // sets (truthy) flag reference to promise + avoids redundant calls + return this[name] || this[name] = callback() + // flag cleanup (doesn't affect chaining) + .finally( () => { + if (!permanent) + this[name] = null; + }); + } /** * object.save() - Convenience wrapper @@ -28,9 +47,7 @@ module.factory('BaseObject', ($q) => { * Using `Promise.finally()` allows you to execute code on success OR fail withought affecting chaining */ save() { - // sets (truthy) flag reference to promise + avoids redundant calls - return this.saving = this.saving || ( this.id ? this.update() : this.create() ) - .finally( () => this.saving = null ); // flag cleanup (doesn't affect chaining) + return this.cache('saving', () => this.id ? this.update() : this.create() ); } /** @@ -39,9 +56,7 @@ module.factory('BaseObject', ($q) => { * @note Use object.save() instead of calling this method directly */ create() { - // sets (truthy) flag reference to promise + avoids redundant calls - return this.creating = this.creating || $q.when(this) - .finally( () => this.creating = null ); // flag cleanup (doesn't affect chaining) + return this.cache('creating', () => $q.when(this) ); } /** @@ -50,18 +65,14 @@ module.factory('BaseObject', ($q) => { * @note Use object.save() instead of calling this method directly */ update() { - // sets (truthy) flag reference to promise + avoids redundant calls - return this.updating = this.updating || $q.when(this) - .finally( () => this.updating = null ); // flag cleanup (doesn't affect chaining) + return this.cache('updating', () => $q.when(this) ); } /** * object.delete() - stubbed with example state flag updating */ delete() { - // sets (truthy) flag reference to promise + avoids redundant calls - return this.deleting = this.deleting || $q.when(this) - .finally( () => this.deleting = null ); // flag cleanup (doesn't affect chaining) + return this.cache('deleting', () => $q.when(this) ); } /** From 1b8b573cf0664713c6b0412907c2c694a935ca2b Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 29 Jul 2015 17:54:41 -0700 Subject: [PATCH 30/36] Update Object.js --- modules/Object.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/Object.js b/modules/Object.js index 43ec95e..d03f6f2 100644 --- a/modules/Object.js +++ b/modules/Object.js @@ -32,12 +32,13 @@ module.factory('BaseObject', ($q) => { */ cache(name, callback, permanent = false) { // sets (truthy) flag reference to promise + avoids redundant calls - return this[name] || this[name] = callback() + return this[name] || ( this[name] = callback() // flag cleanup (doesn't affect chaining) .finally( () => { if (!permanent) this[name] = null; - }); + }) + ); } /** From eddd83e0de4c38d9be99d2db62211ba1267a7f62 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 29 Jul 2015 17:57:32 -0700 Subject: [PATCH 31/36] Update Object.js --- modules/Object.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/Object.js b/modules/Object.js index d03f6f2..8bd973e 100644 --- a/modules/Object.js +++ b/modules/Object.js @@ -32,7 +32,7 @@ module.factory('BaseObject', ($q) => { */ cache(name, callback, permanent = false) { // sets (truthy) flag reference to promise + avoids redundant calls - return this[name] || ( this[name] = callback() + return this[name] || ( this[name] = $q.when(callback()) // flag cleanup (doesn't affect chaining) .finally( () => { if (!permanent) From 8508bcda517a3dfafae61fc4f755cd21a5c9eddf Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 29 Jul 2015 17:59:45 -0700 Subject: [PATCH 32/36] Update Object.js From b267cc8ac12f719e6f08b4f7f24d5667f63e7fa8 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 29 Jul 2015 18:00:39 -0700 Subject: [PATCH 33/36] Update Object.js --- modules/Object.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/Object.js b/modules/Object.js index 8bd973e..5b30596 100644 --- a/modules/Object.js +++ b/modules/Object.js @@ -32,13 +32,15 @@ module.factory('BaseObject', ($q) => { */ cache(name, callback, permanent = false) { // sets (truthy) flag reference to promise + avoids redundant calls - return this[name] || ( this[name] = $q.when(callback()) + if (this[name]) + return this[name]; + + return this[name] = callback() // flag cleanup (doesn't affect chaining) .finally( () => { if (!permanent) this[name] = null; - }) - ); + }); } /** From 2c001c53550c58fcd2b7594b737ab6b24fe1f6ca Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 29 Jul 2015 18:01:17 -0700 Subject: [PATCH 34/36] Update Object.js --- modules/Object.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/Object.js b/modules/Object.js index 5b30596..4b59ee5 100644 --- a/modules/Object.js +++ b/modules/Object.js @@ -31,10 +31,11 @@ module.factory('BaseObject', ($q) => { * @param {boolean} [permanent] If the cache should be removed upon completion (default:false) */ cache(name, callback, permanent = false) { - // sets (truthy) flag reference to promise + avoids redundant calls + // if promise already present (in progress) then return it instead if (this[name]) return this[name]; + // sets (truthy) flag reference to promise + avoids redundant calls return this[name] = callback() // flag cleanup (doesn't affect chaining) .finally( () => { From 1c3920d71cbe94435b3f0f03b0bc071cc6cd9a2c Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 29 Jul 2015 18:05:44 -0700 Subject: [PATCH 35/36] Update ProjectObject.js --- modules/Project/ProjectObject.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/Project/ProjectObject.js b/modules/Project/ProjectObject.js index 10c52a9..e7a5e83 100644 --- a/modules/Project/ProjectObject.js +++ b/modules/Project/ProjectObject.js @@ -13,15 +13,16 @@ module.factory('ProjectObject', (BaseObject, $http) => { create() { - return $http.post('/api/projects', this ) + return this.cache('creating', () => $http.post('/api/projects', this ) .then( (response) => { this.id = response.data.id; return response.data; - }); + }) + ); } update() { - return $http.put(`/api/projects/${this.id}`, this); + return this.cache('updating', () => $http.put(`/api/projects/${this.id}`, this) ); } } From 74317356e1705529ad98c491e5ef35c82c9cd477 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 29 Jul 2015 18:06:53 -0700 Subject: [PATCH 36/36] Update TaskObject.js --- modules/Task/TaskObject.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/Task/TaskObject.js b/modules/Task/TaskObject.js index 054381b..50eb408 100644 --- a/modules/Task/TaskObject.js +++ b/modules/Task/TaskObject.js @@ -21,18 +21,20 @@ module.factory( 'Task', (BaseObject, $http) => { */ create() { // wraps `this.uploading` in a promise that resolves immediately if it is `null` or waits for the promise - return this.creating = $q.when(this.uploading) + return this.cache('creating', () => $q.when(this.uploading) .then( () => $http.post('/api/tasks', this) ) // uploading callback .then( response => return Object.assign(this, response.data) ) // creating callback - .finally( () => this.creating = null ); // state cleanup (doesn't affect chaining) + .finally( () => this.creating = null ) // state cleanup (doesn't affect chaining) + ); } update() { // wraps `this.uploading` in a promise that resolves immediately if it is `null` or waits for the promise - return this.updating = $q.when(this.uploading) + return this.cache('updating', () => $q.when(this.uploading) .then( () => $http.post(`/api/tasks/${this.id}`, this) ) // uploading callback .then( response => return Object.assign(this, response.data) ) // creating callback - .finally( () => this.updating = null ); // state cleanup (doesn't affect chaining) + .finally( () => this.updating = null ) // state cleanup (doesn't affect chaining) + ); } /** @@ -41,9 +43,10 @@ module.factory( 'Task', (BaseObject, $http) => { * @note Added to demonstrate clean ways to have 1 method wait for another method to finish */ upload(attachment) { - return this.uploading = $http.post(`/api/attachments`, attachment) + return this.cache('uploading', () => $http.post(`/api/attachments`, attachment) .then( response => this.attachments = response.data ) - .finally( () => this.uploading = null ); // state cleanup (doesn't affect chaining) + .finally( () => this.uploading = null ) // state cleanup (doesn't affect chaining) + ); } }