-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Proposals to Make Components More Reusable #109
Comments
This is exactly the way it should be! I'm also a fan of ngBoilerplate. Structuring an angular app by feature rather then by layer makes the app more flexible. Your proposal looks also pretty generic, +1 for that! |
Wow - that's a fantastically presented set of suggestions. I'm not experienced enough with Angular to know if all of this is a 'good idea' but the logic sounds solid to me. |
I have been using this encapsulated type of component based architecture for years in FLEX I agree on everything apart from integrating the routes into the component buddle, I think you might want to leave this part in your app code... But apart from that +100 @joshdmiller |
I agree with @Bretto on not integrating the routes into the component bundles however everything else look pretty good. |
Not familiar at all with how Yeoman/Bower works, but I have a question: Does the deployment process from this allow you to limit what files get sent up to your production server? Thinking that sending your tests to a publicly accessible web server not the best idea. |
@MikeMcElroy Yes, that would be easy to exclude regardless of whether they're in a separate folder or not. |
Then very cool. Carry on. :-) On Thu, Mar 7, 2013 at 10:34 AM, Pascal Hartig [email protected]:
|
I'm working on a non-trivial angular app currently, and have been flapping about trying to come up with a structure that sat well with me (knowing that the default did not). This is extremley well articulated and clear -- thanks @joshdmiller for taking the time and effort for writing this up. |
Great work ... I agree with this 100%. |
+1 Makes perfect sense to me. It makes it easier to manage large projects. |
I appreciate all of the positive comments - it's nice to know there are others who agree. :-) That said, I already have a pretty healthy ego - surely there's someone out there who wants to call me an idiot. |
Idiot. That said, I like your proposals very much. |
I think the directory structure you laid out is good regardless of portability or not, organizationally it is good. Instead of calling you an idiot, which @rstuven already did, let me ask you a question as I have been thinking about this more. What do I gain from using multiple smaller modules? For example, I have a set of components I have been building and if I switch to using small modules instead of one big module, I would go from 1 module to ~24 modules (probably more as I still have functionality to add). Now if I use the same directory structure you have laid out but still only use one module, what do I really gain from converting them multiple small modules? If you dont need some of the components I have, just don't include the js file for that component. Now I am going to need all these modules for an application that I am building. I can see this application easily having 20+ main modules with this setup and a lot of those modules would have sub modules, probably ranging from most commonly from 2-5. Now we are talking about 50 - 100+ small modules in this application and most of the modules in the application itself are not really going to be portable as they are going to be built specifically for this application. So are there any downsides to having a bunch of smaller modules (50 - 100+) when portability it not important? |
@ryanzec - Nice. I see the benefits of multiple small modules as these:
I don't really see any downsides to having multiple modules; the build process will take care of concatenation and minification. That said, this issue really isn't about forcing anyone to use this directory structure, but that the current generator doesn't even support the use of this directory structure. I'd like this tool to be flexible above all else, but also encouraging of a more "correct" architecture. AngularJS is just so different than other client-side libraries that most new developers don't know what an app should look like. I provide a lot of community support on the mailing list, on SO, and through a few projects, and I often have to wade through insanity - seeing a little less crap out there would lower my blood pressure. :-) A useful next discussion would be how the CLI tools might support this structure; for routes, it's pretty obvious, but for others the debate may get a little more contentious...and interesting. |
+1. This is currently where I'm stalled on this idea. Any bright ideas on what you'd like the CLI to look like for this? |
💯 This is so inspriing! |
Well, shoot-darn - now you're taxing my brain. I can think of lots of ways to do it, but there is difficulty in finding something flexible, so we don't force anything on anyone, and that doesn't require a bunch of parameters, defeating the purpose of the scaffolding. I should also mention that I've never been a big fan of generators and so don't use them really often; those that do are more likely to provide a better implementation. But I'll kick things off ... First, I've encountered a problem with my above recommendations; technically, it's a problem with Bower. Bower is not nearly robust enough for use with AngularJS projects. With jQuery, it makes sense to have the plugin's repository be the Bower component's source; all it usually has is a JavaScript file, along perhaps with a demo HTML file and some styles. But when we move into something more complicated, using the source repository totally breaks my build. :-( Take Bootstrap. I see two use cases: (1) we just include the CSS; (2) we leverage the LESS/SASS files. For the former, the automated build works great; for the latter, we need to throw those files into some But installing Bootstrap with Bower doesn't well support either. We just get a massive directory of all of the files - including build scripts and jQuery plugins. I can't automate any building with that. But that's not the issue - the issue is that Bower makes no distinction between kinds of packages. It offers no way to specify which parts of the package we need. It offers no way to build on the fly. So if we use Bower for our components, we cannot place our own components alongside them. Additionally, by using Bower, we have two choices when it comes to the build:
These options totally suck. In my opinion, a great package manager for AngularJS projects would standardize the packages and offer them built or unbuilt, uglified or not, with the ability to select sub-components, so we can completely automate our build regardless of what components we choose to include. The Takeaway: But Bower is what we have for now. So I have to change my above proposal. Okay, now that we've re-defined things to fix that issue with my original recommendations, we can get on with the To avoid really complex generator commands, I think we have to buy into a few basic concepts:
app$ yo angular:app Combining everything from the previous post, this yields:
route$ yo angular:route <route> [module] To provide maximum flexibility, the user can provide a module name. In the absence of a module name, perhaps just $ yo angular:route "/products"
And, of course, the $ yo angular:route "/products/show"
Obviously, this would add the I would also think that running the second without the first (which may make more sense in some cases), would yield this:
Note there is no template on the controller, directive, filter, service, & viewI see these as fundamentally the same (though the templates vary, obviously). $ yo angular:controller <name>
$ yo angular:directive <name>
$ yo angular:filter <name>
$ yo angular:service <name>
$ yo angular:view <name> My thinking here is that $ yo angular:controller DoesSomethingCtrl
Or: $ yo angular:controller products.preview.PreviewCtrl
The same logic from the route example would be used to add the new module to the dependencies array of its parent module. It should also add non-existing parent modules; so if There are a few rough spots in the above and I can envision some potential objections. Also, the generator logic to support the above may well be too onerous. This is more to start the discussion. So, what are your thoughts? |
I like the direction this is going. On routing, would it be possible to let the app decide the prefix for a component? I'm struggling to think of a really reusable component that would need this though. Maybe a user manager 'page' - always routing at /users/, when you want an /admin/ prefix to handle permissions - dunno. On the subject of angular components and bower being too simplistic: I noticed that the angular team simply use a separate repo for their compiled files. (https://github.com/angular/angular.js and https://github.com/angular/bower-angular). This makes a lot more sense to me. In the bootstrap case you mention, I would expect to either 'bower install' the CSS files or submodule the source. If you are including something to be part of your build process, it's not really a simple web component. Well, that's just a rule of thumb I use. Components aren't very mature. I also found that most yeoman generators (or any really) are aimed at scaffolding a web app and there is a missing generator to scaffold a component. I was going to try my hand at writing a generator, but then I'd be unable to take advantage of angular:view etc. Finally, I think it starts to get a bit Java-esque when creating a controller defaults to a directory containing a file (another reason not to have co-located test files). Might it be better to default something like angular:controller back to the existing behaviour? |
@stackfull You make good points; I'll respond inline here to avoid confusion.
I may not be following. Once the generator creates the route, the URL string is hard-coded into the file. How would we change this?
The separate repo is also what we chose for the AngularUI Bootstrap project. But we're really just working around the tool here; when we have to do a workaround, that usually means there's something wrong with the tool - or at least that it's not "optimal".
A component should be able to be concatenated and uglified as part of our build; if we want a single payload, this is really a hard requirement. With Bower, we don't get that option unless we manually add these files to our Gruntfile - that's a shame. But as I said, we don't have any other options at this point.
Interesting. How do you see this generator looking?
Why is "Java-esque" wrong? I'm certainly not saying we should wire our app together with a half-dozen XML files. :-) What's the connection to the co-located test files?
The existing behavior is incompatible with my proposals. It makes no sense to have a "controllers" directory (and/or module) that doesn't contain all of the controllers. But I'm not sure when this would be an issue; how often are you going to create a controller that is neither part of a route nor has any related code in its module? |
+1 For better reusability I think the default router would not be the right choice. Cause it is working on static routes. Maybe ui-router would be a much better choice, cause you can define states instead of static routes. So when you say you want to create a product you go in the state 'product.create' and link it to a route in your app, where you bind all your "components" together. But when saying we have reusable components I think they should all be placed in "components". Everything that is placed under the |
Just to ask the question, this refers only the client (browser) side of an app, yes? The server side (node, python, ruby, ColdFusion, etc), would be in a separate repository, or completely outside the scope of the project, with, say, a Twitter client? |
@joshdmiller I like it! One small tweak I would make would be to the module names. Make the module names match the route names: $ yo angular:route "products/new" This would would generate the folder structure you suggested, but create the "products/show" module instead of "products.show". Then the controller generator would be easier to guess: $ yo angular:controller "products/preview" PreviewCtrl This would make things more consistent and easier to generate. |
@joshdmiller I've overestimated what angular routing can do. (thanks for the pointer @mlegenhausen.) The "java-esque" comment just meant verbosity. Java project tend to have deeply nested directory structures and 2 or 3 files for each logical entity. That's not me hating on Java, that's just how the languages shape their projects. As for structuring a project for a component, I'm hitting the odd roadblock in angular atm. Not being able to refer to templates relative to module code is making my grandiose plans look a bit far-fetched. My habit is to have |-- dist/ |-- Gruntfile.js |-- package.json |-- component.json |-- src/ | |-- demo/ | | |-- app.js | | |-- app.spec.js | |-- [component-name]/ | | |-- module.js | |-- assets/ | |-- index.html | |-- styles/ | |-- vendor/ |-- testacular.conf.js |
Okay, I stayed away for a few days and now everyone has an opinion! Here we go... @mlegenhausen I think uiRouter is awesome and it should be a component one can add (and possibly an additional generator to install), but for the broadest possible applicability, @davemerrill The generator is client-side only, so I suppose the structure would depend on how you did the backend. My perspective is that the back- and front-ends should be developed completely independently, but there are many ways to approach this that would depend on your workflow. @ajoslin Cool. I don't care too much about the module separator; if we like @stackfull I'm not a fan of Java either. For relative templates, I think this would be a good feature for AngularJS to support, but at the moment it can be solved through standardization. If you use a directory structure like that for which I am advocating, it will actually "just work". A template is specified relative to its module. So in the same way that one must add the module to the dependencies array, so does the template get specified. Because we've standardized how the structure should look, it will be portable across projects (as well as self-contained). But hopefully AngularJS will support relative paths eventually to allow a little more flexibility. But I don't see why your demo can't exist outside of the source. You don't need demo code "compiled", so can't it exist outside |
demo can exist outside the source, but unless you code your own server task in grunt, the default "serve everything from this directory" breaks the template paths. It's like you said right back at the start - working with the tools you have. |
As I said, if you use a "serve everything from this directory" strategy (which I don't recommend - I prefer compiled code, which can be opened on |
If you use slashes as the separators this would "just work" with a couple of pretty minor changes to the code. The relevant controller generating code is currently this:
but changing it to (untested pseudo code):
would allow you to do |
You guys are great. I'm starting fresh with Angular and JavaScript (coming from Java) and struggled a bit with the current layout of yeoman's angular generator. I like the idea to put everything related to a feature together. I found angular-sprout (https://github.com/thedigitalself/angular-sprout) and now I found this issue:) What do you think about adding the css and maybe even images as well? Furthermore I like the idea of suffixing the files:
Thanks, Leif |
I usually take a very low-tech approach. One of the following is sufficient and is usually enough to make me happy...
|
Interesting approach, @thardy. I tend towards similar advice. Prefixing the module file with a |
If projects are using Gulp, gulp-angular-filesort does a great job of outputting files in the correct order. I've been using it with a different generator and it's worked well for us so far. |
@DanDaniel For large, complex apps, I'm not sure there's a way around all the dependencies and manual resolution without using a script loader (RequireJS / AMD / whatever). If the files would organize themselves nicely into a list without much yak shaving, we could at least use standard glob matching to build an include list. Once upon a time there was an |
I would wager that the type of components that will be reused by angular are visual ones, such as a edit form that is used on more than one page, or a set of filter controls, etc. Because of this I propose using the term "control" (or "widget") in the place of "component", and renaming the directory "components/" to "controls/" to reduce confusion with bower naming. Although bower has packages that are "controls" in this sense, it also has components like bootstrap which are the more abstract "component". Are there any examples you've come across using this fractal hierarchy in Angular that would violate this? |
@brandon-arnold That brights up the question of what does the words control, widget and component mean? To me a component is a part that is not complete. You take a collection of components and compose a widget or control from it. What differentiates a widget from a control? They are both complete, but a widget is more closed than a control. I expect the control to have a controller or api that I may interface with, wether it be through events, a more specialized configuration object or binding functions to it. Does it make sense to force this or a similar understanding of terms on people? I'm not so sure. I think I would prefer it if there was simply a directives folder, and then I could specify the kind of directive when generating one. The generator would then put it in the correct subfolder under directives and thus implicitly allow me to use whatever words the way they would make sense to me and my team. The boilerplate and/or generator could make suggestions but should probably not enforce them. |
Agreed with @gaute: suggest don’t enforce; leave that work for more opinionated forks.
|
Just watched John Papa's Pluralsight video "AngularJS Patterns: Clean Code" and I liked his solution to the module declaration issue discussed in a few places above - How can you guarantee a module reference doesn't get included in your build system before that module's declaration? Basically, he just recommends a feature.module.js file in your feature folder. I like it because it's simple, declarative, and very discoverable. You just have to alter your build to always include the *.module.js files before the *.js files. I've got this working locally in an angular project using grunt and will publish this change to generator-ngbp as soon as I get a chance. |
@thardy. We use a similar approach, but we name all files containing module definitions as The Angular PhoneCat app tutorial has been rewritten to incorporate this AngularAtom component-based organization: https://github.com/demisx/angular-phonecat-components |
I would use something like this : |-- angular_modules/
| |-- social_share/
| | |-- controllers/
| | | |-- facebook.js
| | | |-- twitter.js
| | |-- directives/
| | | |-- menuBar.js
| | | |-- expandableMenu.js
| | |-- templates/
| | | |-- menuBar.html
| | | |-- expandableMenu.html
| | |-- services/ It's just example didnt give it much though, but i would keep the module organize in subfolders. |
Also might be nice to be able to minify each module something like this : |-- angular_modules/
| |-- social_share/
| | | |-- module.js
| | | |-- social_share.min.js
| | |-- controllers/
| | | |-- facebook.js
| | | |-- twitter.js
| | |-- directives/
| | | |-- menuBar.js
| | | |-- expandableMenu.js
| | |-- templates/
| | | |-- menuBar.html
| | | |-- expandableMenu.html
| | |-- services/ The "social_share.min.js" would contain everything about the module so we can just load that file inside our app. |
For anyone still following this, I just updated generator-ngbp with some pretty hefty improvements. Even if you aren't interested in the generator, there are some very interesting mechanics introduced, namely configurable mocking via grunt and $httpBackend. Switching from I've also added RESTful scaffolding to the module subgenerator (as an option). If you choose to add the scaffolding, you'll get controllers, views, a form directive, and a service using ngResource under the covers. You can add a "products" module to see a full working example out of the box. |
+1 for @thardy for the massive improvements. |
+1 |
1 similar comment
+1 |
+1 |
With Angular 2 I feel this is not relevant anymore. Any thoughts ? On Sun 7 Feb, 2016 14:04 Guruprakash Rajakkannu [email protected]
|
@ravitez I have not tried Angular 2 yet. so i have no clue :( |
Any idea when this modular directory structure is going to be implemented? Anybody? |
not necessarily. Its for angular 2. Angular 2 isn't really backwards compatible with angular 1. Plus, its good to have a different take so angular 2 generators will still be relevant |
So, if we want to start an Angular 1.4.x project today, without using es2015, angular 1.5.x component approach or angular 2, should we use this generator as is? |
Had the idea died already ? |
I guess so.
…On Sat, Feb 18, 2017, 18:49 xfg ***@***.***> wrote:
Had the idea died already ?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#109 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AEYmTJ7Ghsvjf2O0hw2FdnB7uFHwQpsKks5rdu_0gaJpZM4Aex3P>
.
|
I apologize in advance for this issue's massive length.
I would like to start a discussion on some ways I think the generators could be changed to make the resulting components more easily reusable. I think when developing, we should have the potential for as much of our code as possible to be "drag and drop reusable". AngularJS already sets us well on our way by promoting separation of concerns in its internal architecture, so why shouldn't our development tools do the same?
The reference structure I am using is my own ngBoilerplate.
Each section builds on the previous.
Update [07 Mar, 0840 PST]: I use the term "component" here a little more loosely than Bower. I just mean it to refer to functionality that is somewhat self-contained. Unless otherwise noted, it refers to both bundled code and internal app features.
Organize by Feature
Instead of bundling code by the layer to which it belongs (controllers, services, filters, directives, etc.), I would like to see code bundled by the feature to which it belongs. There can be many manifestations of this, but here are two examples:
(a) Routes
Instead of something like this for a
/home
route:I'd prefer to see it like this:
This is a much simpler directory structure, but makes the
home
module very portable. It is also self-contained: it can be totally refactored without impacting the rest of the application.(b) Multi-Component Features:
If we have complex component, it is likely composed of multiple smaller components. For example, an editing component might have a persistence service, a representation service for translating markup to HTML, and a directive or two for display. With the existing structure, each component would be created independently and be mixed throughout the various layers; without a comprehensive, holistic understanding of the application, it is difficult to see how these components are (or should be) inter-related. It demands manually surfing through the code and/or doing searches for component names.
A cleaner directory structure would look something like this:
Now it's pretty clear we're talking about one component, albeit a complex one that spans multiple layers. But each sub-component of the editor is still standalone, if need be - that's just good programming.
Isolate the Modules
A logical extension of this reorganization is to so-define our modules. In this pattern, each directory roughly corresponds to a module. Instead of this:
We would have this:
Similarly for the editing component:
Where those modules are defined in their respective files. And our main
app.js
file simply requires the top-level modules:Adjacent Tests
Everyone coming from server-side development (including myself) is familiar with the separate
test
directory, but I've always found it vexing. Placing test files directly adjacent to the code they test makes them easier to locate. More important, however, is reusability; if our tests are in a separate directory, we now have two things we have to copy between projects in order to reuse a component.This is where it's important to separate two kinds of projects: libraries and apps. When developing libraries, we design them to be self-contained and self-sufficient, so we have no need to take tests with us to reuse in another project - that should have all been part of a build. But when developing apps, where some components may depend more subtly on other components (or at least on assumptions about our app architecture), it makes sense to take the tests with us and ensure they still pass in the new environment. Also see the 'Internal "Components"' section below.
It's okay to keep the tests side-by-side because our build tools are sophisticated enough to be able to tell the difference. Grunt 0.4, for example, now includes negative file selectors, so our build can exclude files with patterns like
src/**/*.spec.js
orsrc/**/*Spec.js
from compilation and minification.Adjacent Templates
The same concept also applies to views, partials, and templates. I also prefer they be suffixed with
.tpl.html
or something similar to indicate they're fragments.Modularized Routing
Using the above example,
yo angular:route home
would generate this:But instead of defining the route in
app.js
, we would let thehome
module set up its own routing:Nothing is required in the
app.js
in terms of routing, unless the user should choose to define a default redirect, such as to/home
.Feature Nesting
The existing directory structure is very flat. For small projects, this is perfectly fine, but for non-trivial projects it can become a file management nightmare. If we organize our code by the feature or component they implement and use adjacent templates and tests, it also makes sense to be able to nest them.
Considering the route example:
In this case, each directory should roughly correspond to a single "submodule". The
products
directory is a module calledproducts
, makingcreate
something likeproducts.create
. Using this pattern, theproducts
module can require all requisite submodules:Again, because the target is reusability, each app module is responsible for declaring its own dependencies, which will "bubble up" from
products.create
toproducts
tomyApp
. Routing would work similarly; each submodule can define its own routing, in theory "namespacing" to its parent module. For example,products.create
could define a route of/products/create
.This same "nested" pattern would also apply to complex components, though they would obviously not include routing. E.g.:
Internal "Components"
Lastly, I make a distinction between app code, the stuff that is somewhat unique to our problem domain, and components, the stuff that may come from a third party but that is more immediately reusable in unrelated projects.
With this concept in mind, we should be able to mix in the
components
directory the third-party libraries that come from Bower, the third-party libraries we download manually, and the reusable components that we are coding for this application specifically. e.g.:All combined, I think this would improve code reusability and readability.
The text was updated successfully, but these errors were encountered: