mgechev / angular2-style-guide

[Deprecated] Community-driven set of best practices and style guidelines for Angular 2 application development
https://mgechev.github.io/angular2-style-guide/
1.2k stars 98 forks source link

Discuss about directory structure #5

Closed ByJC closed 8 years ago

ByJC commented 8 years ago

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:

├── app
│   ├── about
│   │   └── components
│   │       ├── about.e2e.ts
│   │       ├── about.ts
│   │       └── about.spec.ts

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 :

├── app
│   ├── about
│   │   └── about.e2e.ts
│   │   └── about.component.ts
│   │   └── about.service.ts
│   │   └──about.html (if you require the template)
│   │   └── about.css
│   │   └── about.spec.ts

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

├── app
│   ├── todo
│   │   └── todo.e2e.ts
│   │   └── todo.component.ts
│   │   └── todo.service.ts
│   │   └── todo.html
│   │   ├── todoitem
│   │   │   └── todoitem.e2e.ts
│   │   │   └── todoitem.component.ts
│   │   │   └── todoitem.html
│   │   ├── todolist
│   │   │   └──todolist.e2e.ts
│   │   │   └── todolist.component.ts
│   │   │   └── todolist.html

What do you think about it ? I never worked on large-scale angular 2 application, only angular 1, so maybe I missed something.

mgechev commented 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.

ByJC commented 8 years ago

@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 ?

e-oz commented 8 years ago

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.

deeleman commented 8 years ago

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.

evanplaice commented 8 years ago

@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';

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 ¯_(ツ)/¯._

e-oz commented 8 years ago

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.

  1. When you need to prefix multiple things with same prefix, it's a good sign to encapsulate them inside folder/entity/table with name of this prefix.
  2. Each component is a capsule, multiple templates in one folder means we have guts of multiple components in one box, mixed (filesystem will not always sort them logically). Better to use folders:
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).

evanplaice commented 8 years ago

@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....

componentception

e-oz commented 8 years ago

@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 :)

ByJC commented 8 years ago

@evanplaice Why can't you extract the todo feature as you said from the explanation and structure of @e-oz ?

evanplaice commented 8 years ago

@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.

e-oz commented 8 years ago

@evanplaice you are mixing 2 kinds of dependencies.

  1. Components can have dependencies of services - it's out of this discussion, because Services are much more encapsulated and should know nothing about components, templates and even your app. Services are black boxes and DI gives us this feature.
  2. Also components can be dependent on other components, they reuse them inside of templates. And in this case, if we will keep some component, let's say "select-box" inside of feature "header", then each other component will be wired with header just to reuse it's sub-component. At some point we may think "select-box" is unique to "header" feature, but later we will realize we can reuse it - so we should try to encapsulated it from the beginning, by not moving to parent's (feature) folder, but making it independent. As I said, it's not 100% rule, but more is better here.
evanplaice commented 8 years ago

@e-oz That's the point. They're not 'mixed' as much as 'grouped' logically based on the functionality they provide.

  1. That doesn't make any sense. If you reuse a feature across many applications, do you re-implement the service for every application you import it into? A service manages the state and handles requests to external resources for the components. If you make a service single-purpose to act as the data layer of feature, it can be packaged for reuse.
  2. The select-box will likely be reused in multiple places throughout the application so it'd put it under 'shared'. HeaderCmp will likely be a subcomponent of AppCmp so I'd put it there.
e-oz commented 8 years ago

@evanplaice

  1. It actually means Service is much more reusable and can be moved to node_modules even faster.
  2. Why you think so? Because of name 'select-box'? It's wrong approach. We should try to create reusable component first, not coupled to some feature. Otherwise we will end with huge amount of copy-pasted components with slight changes for each feature. In your example "shared" is last point of technical debt for me, because all components should be able to be "shared" from the beginning, it's why they are called "components". And there are non-0 chance you'll want to reuse even your HeaderCmp in other apps.
evanplaice commented 8 years ago

@e-oz

  1. So you'd package the service as it's own module, then package each component and it's subcomponents as their own modules, same for pipes, also don't forget about models.
  2. A feature is reusable by default. There is no copy-pasted code, everything is logically grouped. And yes there's absolutely zero chance I want to reuse HeaderCmp because it contains router links that are specific to that application.

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.

e-oz commented 8 years ago

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.

e-oz commented 8 years ago

@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:

screenshot 2016-02-04 18 19 23

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.

evanplaice commented 8 years ago

@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.

mgechev commented 8 years ago

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.

e-oz commented 8 years ago

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 :)

the-ult commented 8 years ago

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.

e-oz commented 8 years ago

@the-ult any component is routable in ng2.

the-ult commented 8 years ago

@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.

e-oz commented 8 years ago

@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.

the-ult commented 8 years ago

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.

e-oz commented 8 years ago

@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.

deeleman commented 8 years ago

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):

  1. Splitting components by folder within the feature domain tree makes it easier to remove the files belonging to a specific component when such component (or components) is ditched from that feature, also removing the (now unnecessary) child components in one go. We can safely remove the specific component folder instead of pinpointing the files inside the feature's unique components folder, which can lead to either remove files by mistake or leave unused files cluttering the project workspace.
  2. We can clearly assess the application components hierarchy just by taking a look into the feature branches and then the component folders tree within each one. Here the file directory itself sets the narrative of our application.

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):

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.

c-ice commented 8 years ago

hello

  1. What about rename folder "shared" to "common"? Common sound more like some "module" or something that is like "core" part of application.
  2. About previous structure discussion I think that right structure is combination between all options that was discussed. Definitely with facade pattern and naming with type. Something like this: (its flatty because project will be really big?)
├── 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.

deeleman commented 8 years ago

My two cents on @c-ice post:

  1. I do prefer shared and it is actually a naming convention that you find quite often in directory structures at many other web environments, easing developers in.
  2. I am not a big fan of appending the type to the filename like 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.

joshwiens commented 8 years ago

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.

deeleman commented 8 years ago

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.

joshwiens commented 8 years ago

@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.

evanplaice commented 8 years ago

@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.

joshwiens commented 8 years ago

@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?

evanplaice commented 8 years ago

@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.

mgechev commented 8 years ago

Levels of complexity

@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.

Type suffix

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.

Common vs shared

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.

Models and services directory

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?

Others

I agree we should move code units to a separate folder once they get more than X.

mgechev commented 8 years ago

Just created a gitter chat room.

joshwiens commented 8 years ago

@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.

  1. I like the idea working on a team because it makes things that much easier when someone is stepping into a new code base.
  2. Not a fan given it just makes import statements longer & chews up space in a tree view.

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.

nareshbhatia commented 8 years ago

By Feature vs. By Type

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!

evanplaice commented 8 years ago

@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.

evanplaice commented 8 years ago

@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 and services for the outside and domain or models 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.

joshwiens commented 8 years ago

@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.

evanplaice commented 8 years ago

@d3viant0ne :+1:

joshwiens commented 8 years ago

@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.

evanplaice commented 8 years ago

@d3viant0ne I'll work on fleshing out the rest. I have 2 more examples to add.

joshwiens commented 8 years ago

@evanplaice No worries, just wanted to keep that issue clean if possible as it's a really great start to the project spec.

evanplaice commented 8 years ago

@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.

nareshbhatia commented 8 years ago

@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.

joshwiens commented 8 years ago

@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'.

nareshbhatia commented 8 years ago

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 {}
ludohenin commented 8 years ago

@nareshbhatia You should check this article http://schwarty.com/2015/12/22/angular2-relative-paths-for-templateurl-and-styleurls/