Closed ByJC closed 8 years ago
Another problem with the nested structure that we currently have is that when your component depends on a service in order to require it you need to type:
import {TodoStore} from '../services/todo_store';
Instead of simply:
import {TodoStore} from './todo_store.service';
To me the second option seems more reasonable, so I think your suggestion makes sense.
@mgechev I have another questions about large-scale application in angular2. you create for each feature a folder at the root of the app. But what about if you have 30 or more folders ? is it still easily readable ? Have you already met the case and how do you manage your folder ?
What is the purpose of single "components" folder in each "feature" folder? Each component should have own folder, to keep ts, css and html files. I think it's obvious.
But what about if you have 30 or more folders
I have this same concern too. Specially when it comes to nested components which are tightly coupled to its parent component so nesting folders makes more sense in order to favor encapsulation over reusability.
A calendar component, wrapping week, day, appointment and many more sub-components (which do not have much sense outside the scope of the calendar container component itself) might be a good example of this case-scenario.
@ByJC The folder structure is by-feature not by-component. That's part of the reasoning behind having components, services, pipes, models subfolders.
For example:
.
├── app
│ ├── todo
│ │ ├── components
│ │ │ ├── todo.html
│ │ │ ├── todo.ts
│ │ │ ├── todoitem.ts
│ │ │ ├── todoitem.html
│ │ │ ├── todoitem.e2e.ts
│ │ │ ├── todolist.ts
│ │ │ ├── todolist.htm
│ │ │ ├── todolist.e2e.tsl
│ │ │ └── todoitem.ts
│ │ ├── models
│ │ ├── pipes
│ │ └── services
│ │ └── todo.ts
│ ├── some_other_feature
│ │ ├── components
│ │ ├── models
│ │ ├── pipes
│ │ └── models
│ └──index.html
└── package.json
The flattened structure can get really messy when you have a feature that is made up of a lot of components and models. Plus, the flattened structure requires a suffix for each type.
Flattened or not, it doesn't really matter. Organizing by-feature is the important part because it makes it easy to extract that code to be reused or versioned separately.
Modular API structure using the facade pattern
Whatever the structure, it doesn't matter if a facade is provided to hide the messy internals.
.
├── app
│ ├── todo
│ │ ├── components
│ │ │ ├── todo.html
│ │ │ ├── todo.ts
│ │ │ ├── todoitem.ts
│ │ │ ├── todoitem.html
│ │ │ ├── todoitem.e2e.ts
│ │ │ ├── todolist.ts
│ │ │ ├── todolist.htm
│ │ │ ├── todolist.e2e.tsl
│ │ │ └── todoitem.ts
│ │ ├── models
│ │ ├── pipes
│ │ ├── services
│ │ │ └── todo.ts
│ │ └── todo.js <- module facade
The facade is a mapping of imports to exports to provide a clean external API.
app/todo/todo.ts
import { TodoCmp as TODO } from './components/todo';
import { TodoItemCmp } from './components/todoitem';
import { TodoListCmp } from './components/todolist';
import { TodoService } from './services/todo';
export default TODO;
export const TODO_COMPONENTS = [
TodoCmp,
TodoItemCmp,
TodoListCmp,
];
export const TODO_SERVICES = [
TodoService
];
The facade makes managing imports from external dependencies really nice... With facades there's no need to waste cognitive overhead memorizing the folder structure of every dependency that is imported. Rxjs in it's current form is a perfect example of a lib that has grown to the size that it could really use a facade.
import 'app/todo/todo';
todo
feature into any other part of the application.import { TODO_COMPONENTS, TODO_SERVICES } from 'app/todo/todo';
Note: It would be really awesome if Angular2 recognized a certain script as the default for a folder (ex index.js). That way the import path could be shortened from app/todo/todo
-> app/todo
.
To see this in action, I use it extensively in ng2-resume to compose higher order components from sub-components and sub-sub-components.
For an example of reuse, ng2-resume
was mostly developed as a feature of evanplaice.com before it was extracted into it's own repo. After it was extracted all I had to do was install it via JSPM and fix a single import.
The same approach was used when I created the ng2-markdown-component.
Note: If I forget to come back and update this later, ng2-markdown-component will be renamed to just ng2-markdown. Naming is hard ¯_(ツ)/¯._
Multiple components in one folder is a pile. Even if they all are coupled to one feature.
I personally can't read this:
│ │ ├── components
│ │ │ ├── todo.html
│ │ │ ├── todo.ts
│ │ │ ├── todoitem.ts
│ │ │ ├── todoitem.html
│ │ │ ├── todoitem.e2e.ts
│ │ │ ├── todolist.ts
│ │ │ ├── todolist.htm
│ │ │ ├── todolist.e2e.tsl
│ │ │ └── todoitem.ts
Just some list of files, all with one prefix.
app/
---/components
---/---/home
---/---/---/home.html
---/---/---/home.ts
---/---/login
---/---/---/login.html
---/---/---/login.ts
---/---/contract
---/---/---/contract-item
---/---/---/---/contract-item.html
---/---/---/---/contract-item.ts
---/---/---/contract.html
---/---/---/contract.ts
---/---/select-box
---/---/---/select-box.html
---/---/---/select-box.ts
---/---/---/select-box.css
In this example, most of components should be in root of the "components" folder, and they all should be a folders (with template, component class, and css if needed). It will encourage people to create really encapsulated components, not coupled to other components tightly, to be able reuse them as much as possible in other apps, to be able remove them from this structure seamlessly and replace by npm modules.
Some components, of course, are too specific to be reused, but they should be in minority. And they deserve own folders, to keep structure clean and.. structured, predictable. I have 2 apps built this way and this structure works great (in second app I have ~70 components and 5 nested).
@e-oz by-feature
is the convention recommended in this styleguide due to the fact that grouping by-type
destroys reusability.
What happens if/when you want to extract the Todo
feature as an module to be versioned independently and reused in other applications?
There's a reason by-feature
was chosen as the convention. It decouples the structure of the code from the structure of the application. With this approach, each feature can be viewed as it's own self-contained application. If you choose to extract the component into its own repo, it can literally be used as a self contained application.
Using the by-feature
approach, if a feature becomes to unwieldy another level of nesting can be included to specify sub features. If you see it as a self contained application, it's structure will look the same as the application it's contained in.
Some might even say it's....
@evanplaice nothing criminal will happen, in my proposal "todo" is a component and it will be happily reused as npm module. Each feature should be a component.
If we are using words "chosen" as something what can't be changed, then there is no reason to discuss it at all and we can just agree to disagree :)
@evanplaice Why can't you extract the todo feature as you said from the explanation and structure of @e-oz ?
@e-oz And when you go to extract it you'll have to rip out and combine the components, services, and models; create a structure identical to what the by-feature
approach already provides; and remap all of the import statements to avoid breaking the application.
If you were already using that structure, it would be a simple folder copy. Everything is already wired up with relative links.
@evanplaice you are mixing 2 kinds of dependencies.
@e-oz That's the point. They're not 'mixed' as much as 'grouped' logically based on the functionality they provide.
HeaderCmp
will likely be a subcomponent of AppCmp
so I'd put it there.@evanplaice
@e-oz
Enough with the theoretical axe grinding. Show proof (in the form of actual code) that your approach provides a clearer path to reusability. In the form of a reusable component and 2 or more applications where is imported as a dependency. Here's mine.
Component: ng2-resume Application1: evanplaice.com Application2: ng2-resume-demo
Here are links where the feature can actually be seen in action:
I don't expect proof now but feel free to come back and ping me when you're ready so we can do a comparison.
You forgot "please", @evanplaice . I'm not going to write code for you and spend my time, sorry. I have experience with my approach, as I said, and I don't have to prove anything to you (only difference is I'm not trying to call you a lier), just let's keep different opinions on this subject, it's ok to have different opinions. Bye.
@evanplaice your updates to posts are almost not visible to me, I'm only getting notifications on first submission - it's how github works, so please consider to write separate messages, not updates to existing.
Thanks for "feel free", but I'm not going to prove you anything, really :) My proposal is 100% understandable for other readers. If you think I don't have practice with it, here is screenshot of one of apps structure:
Some components are in npm_modules. It's all I can give you - I better spend some time to my family, rather than arguing in internets. And don't call random people liars, it's kind of rude.
@e-oz How do you take disagreement as the equivalent of calling you a liar. I'm not even claiming that your wrong. I just saw the commentary was stuck in a bad look and tried to break out of it by taking a more objective approach.
I would genuinely like to see 2 equivalent samples to compare, contrast, and check assumptions. Maybe there's room for improvement.
Anyway, thank you for sharing, I understand that not everybody has code immediately available to share on GH. It looks like you have a pretty good logical structure there. My only disappointment is that I can't dig deeper and see how it all fits together. I'm sorry to keep you from your family.
I'd prefer to stick to the by-feature folder structure, because of the reasons mentioned above.
It is also a good question whether once grouped by feature the components should be also grouped by type. If we take this further and there are a lot of components for given feature, should we create another set of directories for the individual components?
For instance in the calendar example:
- schedule
- calendar
- components
- calendar.html
- calendar.ts
- calendar.spec.ts
- month.html
- month.ts
- month.spec.ts
- day.html
- day.ts
- services
- foo.ts
- bar.ts
Whether we are going to use custom suffixes for the code units (i.e. calendar.component.ts
and foo.service
) depends completely on whether we'll group them by type once grouped by feature.
Although creating such a nested structure might be a bit annoying while navigating through your project it somehow looks more organized to me.
All files of calendar without folders is a mess, and this mess will grow with count of components. And nesting them inside parent component will encourage to make them coupled, not reusable. I've said my words, now I'll shut up :)
I'd like to seperate specific view/route component from the more commen components which are reusable and might end up in a seperate component repository/library, so we can load them with npm. Besides the view component you might also have layout specific components like the navbar, side-bar, footer, header, etc.
I was wondering what the best place is to store layout specific components.
The way we are handeling it now is more like:
.
├── README.md
├── app
│ ├── assets
│ │ ├── img
│ │ │ └── smile.png
│ │ └── main.css
│ ├── components
│ │ └── list
│ │ ├── list.component.e2e.ts
│ │ ├── list.component.html
│ │ ├── list.component.ts
│ │ └── list.component.spec.ts
│ │ ├── list-item.component.e2e.ts
│ │ ├── list-item.component.html
│ │ ├── list-item.component.ts
│ │ └── list-item.component.spec.ts
│ ├── core
│ │ └── common
│ │ └── services
│ │ ├── invoice-service.ts
│ │ │ └── models
│ │ │ │ ├── invoice.model.ts
│ ├── layout
│ │ └── app
│ │ ├── app.css
│ │ ├── app.html
│ │ ├── app.ts
│ │ ├── app.e2e.ts
│ │ └── app.spec.ts
│ │ └── footer
│ │ └── navigation
│ │ └── sprite
│ ├── views (routes/states??)
│ │ └── home
│ │ ├── home.css
│ │ ├── home.html
│ │ ├── home.ts
│ │ ├── home.e2e.ts
│ │ └── home.spec.ts
│ │ └── invoice-detail
│ │ └── login
│ │ └── user-profile
This still doesn't feel right. But putting common components and layout and feature/view components together doesn't feel good either.
@the-ult any component is routable in ng2.
@e-oz , you're right.. But that's how it work technically.. But in practice, you still have view/page components like home, user-profile, etc. And layout components like navigation, side-bar etc. So while navigation your code it would be nice to find/group the different kind of components together.
@the-ult I disagree. User profile easily can be embedded component and also routable page. Your proposal will encourage people to create components, which are only usable as routable pages, when they could have chance to reuse them as real components.
That's true. And how about the navigation/side-bar etc components? It won't take long before we exceed the 7 components limit per tree..
@e-oz: I meant the suggestion/best-practice/advice
When a folder grows to contain more than 7 files, start to consider creating a folder for them.
@the-ult sorry, maybe I missed that "7 items" limitation. I don't understand why not 8, 3 or 99, so don't see point in such limitation.
It is also a good question whether once grouped by feature the components should be also grouped by type. If we take this further and there are a lot of components for given feature, should we create another set of directories for the individual components?
I share that very same concern as well. Let's summarize some pros and cons of this and try to take a bird's view of the current stage of this discussion.
The approach pointed out in Minko's comment features two main pros (which are open to discussion though):
Regarding the latter, many people will say that the filenames themselves tell the whole story (which is correct for small-to-mid sized projects) and it just takes a quick overview of such filenames to clearly see which is the root component for each feature. Taking the TODO example for instance (HTML templates or spec files removed for brevity sake but try to imagine how this would look with all companion files in place - I have left the .component
suffix in the examples until there is an agreement on that issue as well):
│ organizer-app
│ ├── todo-list
│ │ └── components
│ │ ├── list.component.ts
│ │ ├── list-item.component.ts
│ │ ├── list-item-options.component.ts
│ │ └── list-item-options-option.component.ts
However, what would happen if we bring a generic component from the outside world into the context of this feature and we do not want to leverage a top root shared
folder for whatever reason. P.eg.
│ organizer-app
│ ├── todo-list
│ │ └── components
│ │ ├── list.component.ts
│ │ ├── list-item.component.ts
│ │ ├── list-item-options.component.ts
│ │ ├── list-item-options-option.component.ts
│ │ └── vendor-fancy-checkbox.component.ts // <-- Hello, I'm new to the party!
Can we tell where is this component being used just by looking at the folder contents? Obviously not. Not a big deal in our todo example, but a bit of a burden in bigger projects.
We must also assess the impact of the following:
Create folders named for the feature they represent. When a folder grows to contain more than 7 files, start to consider creating a folder for them. Your threshold may be different, so adjust as needed.
How would the directory tree get with a sub tree structure based on all the requirements pointed out above?
│ /organizer-app
│ ├── /todo-list
│ │ └── /components
│ │ │ ├── list.component.ts
│ │ │ └── /list-item
│ │ │ └── /components
│ │ │ ├── list-item.component.ts
│ │ │ └── /list-item-options
│ │ │ └── /components
│ │ │ │ └── list-item-options.component.ts
│ │ │ └── /directives
│ │ │ └── vendor-fancy-checkbox.directive.ts
│ │ └── /services
│ │ └── list.service.ts
In the example above each sub-component not only features its own folder but its own components
folder along with its own services, directives, pipes, etc where required...
There are some cons and additional concerns with this approach though (I came up with just two, but there're obviously more):
components
folder is fairly redundant and we can save a level) or store them in a directives
or pipes
subfolder along with the components
folder (having import WhateverType from '../type/whatever.ts'
instead of import WhateverType from './whatever.type.ts'
blocks at the top, as someone pointed out already in this same thread).The problem with this last concern is that we will wind up confusing sub-features with sub-components in the semantic context of our project.
This is a hot topic definitely.
hello
├── src
│ ├── todo
│ │ ├── todo.js <- module facade
│ │ ├── todo.view.html ---> [view] for pages
│ │ ├── todo.component.ts
│ │ ├── todo.service.ts
│ │ ├── todo.e2e.ts
│ │ ├── todoItem
│ │ │ ├── todoitem.component.ts
│ │ │ ├── todoitem.html ----> no view its not page
│ │ │ └── todoitem.e2e.ts
│ │ ├── todoList
│ │ │ ├── todolist.component.ts
│ │ │ ├── todolist.html
│ │ │ └── todolist.e2e.ts
│ │ X── models ----> * because models are shared
│ │ ├── pipes
│ │ └── ...by-type... ---> ** group by type when it will be messy
* it'll better to collect them at one place if you are using ERM, relationship-database. Because its very probably that you will need the model at a lot of different places. (but not so good for re-use)
\ e.g. one whatever.service.ts
is OK to leave at root but two or more services rather group to one folder services
And at the end some structure is better for apps(small/mid/large) another better for libs maybe.
My two cents on @c-ice post:
shared
and it is actually a naming convention that you find quite often in directory structures at many other web environments, easing developers in.todolist.component.ts
, but have to recognize that the directory structure you suggest makes a lot of sense to me and it is quite intuitive at a glance. From my own experience, the feature/type pattern (eg src/todo/components/[lotsa files mixed up]
- including more type subfolders) scales pretty bad when you have a lot of components in the same level of functionality. Nesting component folders ensures that each folder level will contain a limited amount of files plus other component subfolders, being the latter a good cue of the app hierarchy.RE models: 100% Agree on this, but why not wrapping all those shared concerns (models, pipes, etc) inside a shared
folder?
RE services: I would definitely advocate for a services
folder right from day one. It doesn't hurt to have a services
folder with one file only and that prevents you from refactoring your import paths as your app eventually grows and you find yourself forced to move service files into a services directory.
I'm pretty much with @c-ice
I use "common" which imo speaks to the intent of the contents more concisely than "shared" but maybe that's just me.
The more I see @c-ice's take on this the more I like it. It would be great to have some more feedback from more people in order to leverage the momentum this discussion has gained and hence reach an agreement fast.
@deeleman Yeah, imo the direction @c-ice took it resonates with me. I'm going to try it based on one of my production apps so I can see it mocked up at scale.
@c-ice @deeleman @d3viant0ne Where a facade is provided, the internal structure of a feature doesn't matter.
As a tie-breaker, the default and most basic use case can just follow what angular-cli already uses.
I suggest this be used as the default recommendation for level 1 complexity.
|-- app
| |-- hero
| | |-- hero-detail.component.html
| | |-- hero-detail.component.css
| | |-- hero-detail.component.spec.ts
| | |-- hero-detail.component.ts
| | |-- hero-list.component.html
| | |-- hero-list.component.css
| | |-- hero-list.component.spec.ts
| | |-- hero-list.component.ts
| | |-- hero-root.component.spec.ts
| | |-- hero-root.component.ts
| | |-- hero.service.spec.ts
| | |-- hero.service.ts
| |-- ...
|-- app.ts
For level 2 complexity, the parts can be split into subfolders by type.
├── app
│ ├── todo
│ │ ├── components
│ │ │ ├── todo.view.html ---> [view] for pages
│ │ │ ├── todo.component.ts
│ │ │ └── todo.e2e.ts
│ │ ├── todoItem
│ │ │ ├── todoitem.component.ts
│ │ │ ├── todoitem.html ----> no view its not page
│ │ │ └── todoitem.e2e.ts
│ │ ├── todoList
│ │ │ ├── todolist.component.ts
│ │ │ ├── todolist.html
│ │ │ └── todolist.e2e.ts
│ │ ├── directives
│ │ ├── models
│ │ ├── pipes
│ │ ├── services
│ │ │ └── todo.service.ts
│ │ └── todo.js <- module facade
Aside: I can't say I'm a huge fan of adding the type to the name. It makes the import statements much longer and the folder tree within an editor much more cluttered. Neither of these issues apply where a facade is used.
For level 3 complexity, the subcomponents can be further broken down with subfolders for each type.
The shared
folder should always contain subfolders by type.
├── app
│ ├── shared
│ │ ├── assets
│ │ ├── components
│ │ ├── directives
│ │ ├── models
│ │ ├── pipes
│ │ ├── services
│ │ └── shared.js <- shared facade
@c-ice Models application-wide models should be included under shared
. The major benefit to organization by feature and the shift to web components comes from localizing complexity as much as possible, make it easier to reason about the whole application.
@deeleman @d3viant0ne
This is matter of subjective preference, it doesn't really matter what the folder is named. Can you find examples of common
being used elsewhere in the NG2 community?
Exactly, that's the point. Everything else falls into place nicely once if the convention is consistently followed.
Usually, a component will only contain one service and import other application-wide services located under shared
. Either way, the service will likely include unit tests.
In which editor? Fuzzy finding using [feature-name] [type]
works perfectly fine in ST3.
@evanplaice
Absolutely. I think there is a lot of benefit in the "simplest form" matching the cli. Far easier for people new to the framework to pick up and will also probably aid the adoption of this style guide to a broader audience
Converting a large prod app to ng2 this afternoon to apply this conversation to something I am familiar with. I see your point in regards to imports becoming unruly. Point conceded
It was an ng1 habit I picked up been too long to remember where. Outside of that, it's personal preference and not terribly important other than picking a standard. No issue if it is decided to go with shared.
I'm fine with doing it this way. Easy enough to reason about and should be simple to pack/bundle with whatever too people decide to use.
My only concern here would be if it becomes taxing for packing, hot loading, authorization strategies. Not to say I see an issue with it. I think it would be a decent idea for a few of us to take a commonly known project with L3 complexity and apply ng2 / this directory structure / style guide as a real world p.o.c
Essentially take what you have outlined in #10 and apply that to something at reasonable scale to include most of the more advanced tooling we use these days. Proposal would be for you to pick a project and control what lands then we do a before & after. Hopefully it would be more than two of us working on it but it may prove as a selling point to drive this discussion home.
Opinion?
@d3viant0ne Just to be clear, the module facade isn't specific to level 2. Using a facade is even more valuable for even larger modules.
The facade should be recommended for:
Folder structure doesn't reflect application structure. With a facade, the structure of a module is essentially flat, no mater how a module is organized.
I agree that it would be useful to see these practices be put to use. Unfortunately, I don't have anything to apply it to absent starting another project from scratch. Most applications I've seen for NG2 in the wild are really basic.
@evanplaice I like the proposal for a few directory structures based on the app complexity. Three levels of complexity seems reasonable to me and the suggested structures for each of them makes sense.
Definitely it's worth following the angular-cli convention for the first level of complexity.
I'm not huge fan of the type suffix neither. However, we cannot omit it in case we have a couple of different code units with the same name but different type.
In case we don't use type suffix when we spread them among directories for the individual types we loose:
The last two are not very solid arguments.
In case we keep the code unit-type suffix we have more typing/duplication which can be annoying as well. We can weaken the influence of this one by using facade modules.
Although I see more positives of keeping the suffix I'm still not confident that its the correct decision.
I agree it is more a matter of a personal opinion. Both work for me. Angular 2 uses common
internally so we can stick to it eventually.
I think we should have both from the beginning and they should follow the same convention as everything else. If there are models/services that are unique for a specific module we should keep them there, otherwise move them to higher-level module's models or common
.
view
suffix for templates of components with @RouteConfig
Makes sense to me. I think we can take it even further and add this suffix even to the controller files. Such components can be used as regular components as well but I don't think this means that we will treat them differently. This way we only append some additional metadata in order to add further semantics on how the component is being used.
What are your thoughts on this?
I agree we should move code units to a separate folder once they get more than X.
Just created a gitter chat room.
@mgechev - Type Suffix
in case we have a couple of different code units with the same name but different type
Initially when I read that, i had considered the rule should not be a yes or no but conditional. Essentially Applying the folder concept to type suffixes.
If you have multiple code units of different types, the type suffix is included in the name. That does on the other hand bring up the problem of renaming files already under source control, import refactoring and so on.
The first point is personally something I feel is important. Easier to reason about > a few characters saved here and there.
All that said, I think @evanplaice suggestion of using facade modules is the most viable solution. At a glance, it makes the structure easy to reason about without the editor bloat issues. Renaming files would just get frustrating and provide some unwanted side effects in some VCS.
@evanplaice - Sorry, should have been more clear in what I was proposing.
Take a known Angular1x project based on the 1.x style guide, of reasonable complexity and convert it to 2.x using our style guidelines. While this isn't about "upgrading" I think it would be beneficial for most people to see something familiar and then that same project after ng2 & style.
Realistically that is going to be quite a bit or work but in the end, I think the value add to the community would be worth the effort it would take to make it happen. It would also be a really good way to find any holes in the guide & it's recommendations.
When reading this thread, I was struggling with questions like "What is a feature?" and "How do I break down my app into a reasonable set of features?". Then I remembered the Domain-Driven Design concept called Bounded Contexts and it helped me figure out how to proceed. From the linked article:
As you try to model a larger domain, it gets progressively harder to build a single unified model...So instead DDD divides up a large system into Bounded Contexts, each of which can have a unified model...Bounded Contexts have both unrelated concepts (such as a support ticket only existing in a customer support context) but also share concepts (such as products and customers).
Armed with this knowledge, a feature is a completely self-supporting unit representing a bounded-context within a large domain. For example, in @evanplaice's site, Thoughts, Designs, Projects and Vitae are completely different contexts and hence can be packaged as different features with their own components, services and domain entities (a.k.a. models). On the other hand, if we had a simple application that dealt with only one concept, say to-dos, then there is no need for an elaborate by-feature directory structure - there is only one! If two features share the same model entities and/or services, then I would question if my partitioning is correct.
Now within a feature, we should look at organizing our code into two areas: the outside and the inside - as suggested by the Hexagonal Architecture. The outside is how the feature interacts with the outside world - the user, the server(s), local storage etc. The inside is the domain entities and the logic encapsulated in and around them. This nicely translates into components
and services
for the outside and domain
or models
for the inside. The components folder could be as flat or as nested as desired. My initial approach would be to make it as flat as possible to promote reuse (as suggested by some in this thread), but if the component is very specific to the context, then nesting is probably ok. I don't think there is a right or wrong answer here. It really depends on how "general-purpose" the component is. For example, a date-picker probably does not even belong in a feature folder, it goes in a shared folder somewhere or even better, someone has already written it and you are simply npm installing it!
Hope this helps. Feedback welcome!
@mgechev
Type Suffix
I agree that consistency trumps all. I'm not a fan of the type suffix but that's just personal preference. Since there's justifiable reason to use it for Level 1 complexity, it should be used for all levels of complexity.
I wasn't arguing in favor of renaming and remapping the imports to everything as the level of complexity increases.
Common vs Shared
I have no opinion on this. I chose shared
because that's what angular2-seed already uses.
Models and services directory
I agree
view
suffix for templates of components with @RouteConfig
Not sure why this is limited to route views. Views in NG2 are applied per-component not per-route as they would be for most MVC frameworks.
One could assume that anything with a .html extension is a view but we may yet see NG2 be extended to support other template formats in the future.
@d3viant0ne By no means do I suggest a structure that requires renaming as it grows. I added the comment as an aside because it doesn't add much value in the bigger picture of things. If the type suffix is used on level 1 complexity, it should be used for all levels of complexity.
I would love the opportunity to convert an AngularJS app to NG2. I don't currently have an opportunity do do so. We'll have to wait and see until somebody does.
@nareshbhatia
DDD has a lot of good ideas. The issue I find with generalized methodologies is they attempt to define in a single level of granularity, 'one way' to address the problem of complexity. The assumption being that a single 'unified model' is even possible.
There will always be parts of a system that bleed across contexts. The shared
/common
folder exists as a central location to define application-wide and/or cross-cutting concerns.
What you don't see in the current version of my app are the versions where ng2-markdown
and ng2-resume
were included as internal features. Both were useful enough that it made sense to extract them out to their own repos and offer them as reusable modules.
On the other hand, if we had a simple application that dealt with only one concept, say to-dos, then there is no need for an elaborate by-feature directory structure
Maybe I should have defined a Level 0 of complexity. For the todo feature, it should be trivial to extract the parts to run as a standalone application (assuming there are no cross-cutting concerns). That's the power of a component-based architecture.
Essentially, a feature is self-contained and the complexity is localized as much as possible. Reusability is a good justification but it's just a side-effect of a system that defines a convention for encapsulation by default.
I would ditch the Hexagonal Architecture
altogether. It makes for pretty diagrams but IMHO doesn't add much value. Unless I've missed some key essential benefit, it appears to divide concerns on arbitrary boundaries. That has a lot of potential for bleeding abstractions. I could see some benefit in it's use at the system-level where the system is made up of many microservices, at the application-level I would avoid it.
The inside is the domain entities and the logic encapsulated in and around them. This nicely translates into
components
andservices
for the outside anddomain
ormodels
for the inside.
I don't see any such clear distinction. For instance, a service can be both internal where's it's used to provide and/or cache state and external if it acts as a wrapper for external service calls. Models can be extracted out for use on both client-side and server-side structure/validations. Components can be used internally or extracted out to independent modules for reuse across many different applications. Isomorphic JS may yet add more potential for abstraction breaking characteristics.
For example, a date-picker probably does not even belong in a feature folder, it goes in a shared folder somewhere or even better, someone has already written it and you are simply npm installing it!
A date picker would likely be placed in shared
/common
. You're right in that it would ideal to install and use a pre-exitsting datepicker.
It's also important to consider the case where somebody is creating new modules. It's not always immediately apparent that a module is a good candidate for reuse. I'm very much a fan of the build-and-extract approach to creating new libraries. Ie start with a 'solution to a problem' rather than a 'solution looking for a problem'.
Starting something like a datepicker as a subfeature, perhaps in a Level 2 complexity directory structure. When it's ready, move it to the shared/components directory to test that all the facade mappings still work. If that's good, extract it into a separate repo and install directly from the repo via NPM/JSPM.
Feel free to share you perspective and correct if I've missed anything.
@evanplaice
By no means do I suggest a structure that requires renaming as it grows.
I was referencing my original thought above not implying you suggested renaming. In short, i was discounting my original though and agreeing with you.
@d3viant0ne :+1:
@evanplaice @mgechev - Can we lock the API design issue and use that as the "approved" rules? Issues like this are going to get messy, it will probably be helpful to have a clean reference containing what has been decided that can't be commented on directly.
@d3viant0ne I'll work on fleshing out the rest. I have 2 more examples to add.
@evanplaice No worries, just wanted to keep that issue clean if possible as it's a really great start to the project spec.
@d3viant0ne I don't have write access so I couldn't lock it, even if I wanted to.
BTW, the API guide has been updated if you'd like to take a look. The next 2 sections are the 'really good' parts.
@evanplaice, thanks for your thoughts.
It's not always immediately apparent that a module is a good candidate for reuse. I'm very much a fan of the build-and-extract approach to creating new libraries. Ie start with a 'solution to a problem' rather than a 'solution looking for a problem'.
Really enjoyed reading this perspective.
The issue I find with generalized methodologies is they attempt to define in a single level of granularity, 'one way' to address the problem of complexity. The assumption being that a single 'unified model' is even possible.
Actually DDD and specifically the Bounded Context concept say quite the opposite - we create bounded contexts because it is not always possible to create a single unified model. There may be overlapping domain concepts within two contexts. For example, an Order
in an order management system may have a slightly different meaning than an Order
in a shipping system. The APIs between these contexts would have to take care of the impedance mismatch. Applying these principles to the discussion at hand helped me a lot. In fact, I was able to better understand why you partitioned your site the way you did.
@evanplaice - I have to agree here, I tend to work the same way. Applications evolve as they and our plans for them are refined.
It's not always immediately apparent that a module is a good candidate for reuse. I'm very much a fan of the build-and-extract approach to creating new libraries. Ie start with a 'solution to a problem' rather than a 'solution looking for a problem'.
Talking about modules and reuse, I do have a question. The module examples I have seen so far use paths relative to the root of the source. This ties the module to the folder structure of the source. Is there a way to specify paths relative to the component? Here's an example from angular2-seed that uses paths relative to the source root:
import {Component} from 'angular2/core';
@Component({
selector: 'home',
templateUrl: './home/components/home.html',
styleUrls: ['./home/components/home.css']
})
export class HomeCmp {}
@nareshbhatia You should check this article http://schwarty.com/2015/12/22/angular2-relative-paths-for-templateurl-and-styleurls/
Hi @mgechev ! I just read your style guide, it's awesome ! I was wondering about directory structure.
you create a folder for each feature you have as following:
You mention that if your folder grows to contains more than 7 files you create subfolders (components or services). it feels like it's grouped by type and not based on feature
I just tought to simply flatten the entire folder this way :
In the case you have more than 10 files and the folder became difficult to read, I would suggest to create folders by sub-features
What do you think about it ? I never worked on large-scale angular 2 application, only angular 1, so maybe I missed something.