In this episode, I'll be sharing one of the ways to structure Single Page Apps build with Angular 2 that I have found it useful.
What are we going to build?
A simple Application called "Angular Restaurant". We have a header, a menu category and a list of menu items to choose from. A search text box also appears on top of the list as a filter. On the top right hand corner, we also have a "Bag" area showing number of items now in bag.
How will you break this simple interface into an Angular 2 application? Angular 2 is heavily component based. We need to figure out where are the component boundaries. Which parts of the UI can live on their own, independent of any thing else. Which part of the UI can be tested in isolation. There are many factors we should consider while dividing interfaces into Angular 2 components but that's not the point of this post.
After some consideration, we divided our interfaces into components like this (red borders of separation)
In terms of componentization, there are 3 types of components:
- Container Component : Components with no content of their own and all what they have are other components living inside of them. For example, Menu Category component containing a list of menu categories like "Top", "American", "Italian" e.t.c.
- Content Component : Components which only has some content in it but no component nested within it. This component has no child components. For example, Search box, it does have any child component. All what it has is its own self i.e. a text box and nothing else.
- Hybrid Component : You guessed it right! These are components which have their own content as well child components nested within. For example, Menu Component, it has its "own" heading which depends on the selected Menu Category. In the snap shot it says "Top Menu". It also a list of nested "Menu Item" components like Burger, Fries e.t.c.
Folders by File Type
This is how it might look like if you want to divide your files based on file extension i.e. all .html files are kept in one place, all .js files are kept in another folder and so on. Let's take a quick look at how this would look like:
Are we happy? What's the problem ?
This is one crazy idea that I have seen friends do in real world apps - trust me! What's the problem ? You are never working on only one part of a component at a time. Components have behaviors in .ts files and views in .html files. How can we forget testing and that's another file type .spec.ts. To work on a component, you'll have to open these 3 different folders. These folder by types can increase because you may want to save configuration per components in a json file. Here you go, now we will have a .json folder. The problem might look small in this tiny app but suppose you have 50 components in your app, which you will end up with in any medium size app, you now will have around 5 differnt folders (may be) that you ll have to open and you will be looking at 5 x 50 = 250 files at the same time! Check this out! Not only this, how about when you want to work on more than 1 component simultaneously, which you normally do (because components are talkative), each time you will have hard time locating files. One might argue that sexy IDEs like Sublime and WebStorm can help search files but that does not do any good to the idea of keeping files separate by file type. You will grow into serious problems as your application grows. You may have to look at 10 different folders to look at while you are working only on 1 component. Only if your app is super tiny and will never grow to more than 3/4 components, this scheme is okay.
Piece of advice: Never try this at home!
Folders by Component Type
Do not mix it up with Angular2 "Components". When I say component here, I am talking about every building block of Angular 2 including but not limited to Components, Services, Pipes, Directives e.t.c. I am still in search of a better name.[ Tweet me about it ]
Dividing your stuff based on component types was one famous idea when Angular 1 was new to the scene. In this style, you separate files based on type of Angular2 components. For example, you will keep all your components in one folder, all your services in another folder, all your pipes in another folder and so on ... This is how it might look like:
Pay no attention to the green, blue and white color of file names. That's just some Webstorm color scheme for tracked, untracked and new items.
Are we happy? Is there still a problem ?
In this scheme, the number of folders may not increase because its based on Angular2 building blocks like Component, Service, Pipe e.t.c which will not increase drastically over time. This is a good thing but what's the bad thing? Each business functionality is spread over several different folders. Suppose you are working on the Search Box module. You will have to open Components, Services, Pipes, Directives and Specs, all folders at the same time. You may have more folders depending on your scenario. Apps grow and suppose you now have around 50 modules and you are working only on one of them. You have at least 5 folders open (discussed above) and you will be seeing 50 x 5 = 250 files once again! The only benefit here over Folder by File Type is you know your component will only be in the component folder, your service will only live in the service folder (and so on) and no where else. Service are independent of number of components/modules. So there can still be 100s of services. Navigating between files jumping from one component to another becomes a mess.
Folders by Functionality to the rescue!
This is by far the most widely accepted way of keeping your toys in Single Page Applications. Not by file type, not by component type but isolated by business functionality type. Everything related to a module i.e. it's component definition, it's views and everything else is kept in the same folder. This is how it looks:
Why are we happy with this?
Doesn't it look more cleaner than the previous 2 styles? It certainly does to lots of folks. For instance, when you are working on the Header Component all what you will open is the "Header" folder and nothing else. You will find everything from component to views to specs related to Header Component all within the same folder. You do not have to navigate to 3 different folders to get to component definition, views and specs.
In my opinion, services and pipes which are used only by a particular component should also be kept within the same folder. We will discuss about shared services and pipes later but for now I am talking about services, pipes, directives e.t.c that are only used by a particular component. What if a particular pipe or service is later used across components? Simple: you take them out and move them to the shared folder! We will discuss shared folders in the end.
Let's look inside the folder
Everything I ever needed for my Search Component is right there, all in one folder. I do not have to navigate to another folder nor do I have to worry about the component being spread out all over my app. This increases productivity and focus. You only look at stuff that you are working on. One more thing, Search Component, fortunately, was a Content Component. It did not have any childs.
One more interesting discussion related to structuring Angular 2 apps is about Nested Components (Weather they ar Container Component or Hybrid Component - does not matter)
Nested components are not straight forward and this discussion is debatable. I will show you both sides of the coin. Majority of folks keep nested components within their parent component. It is a good idea. You keep them in same structures in folders as they are in your app.
This is how it looks like:
The "Menu" folder contains the container "Menu List" component with all its view, component, spec e.t.c. It also contains a "Menu Item" folder which contains stuff for each item in the menu list. "Menu Item" is a nested component that lives within "Menu List" component on UI as well as in our folder structures.
Folders by Functionality is by far the most popular scheme for structuring Angular Apps.
There are scenarios in which I keep my nested components flat on the root "/src" and within the parent component.
Why and when?
When there is a high probability that the inner(child) component will be used across app within other components. This will keep the component visible to all other components you are working with.
I do not have any specific scenario for this application but if you are interested in looking at one, tweet me about it.
Each file as you may have noticed is affixed with the type of file. For example,
One may argue keeping the type of file in file name makes it lengthy. It does but it make things clear. How will you differentiate between a component and a service if you do not affix it with file name?
search.ts Is this a component or service?
search.ts Is this a component or service?
You may fix this by giving different names to each type,
Argggggggggghhhh ... How many times I see friends do this? 100 times a day!
I think the above solution is ugly and one should never do this. If you are doing a Solo project(may be do whatever) but on a team always prefer to have simple clean meaningful file names. We will stick to the naming convention we introduced.
Shell Page and Outer Container Component
Let's take a look at our app once again,
The black borders is our index.html page. A shell page that holds everything within a single page application(SPA). This page lives for the entire life time of a SPA. It holds all scrip links, style links, html header body and everything that you can think of.
I always keep Shell Page (index.html), every type of config file (karma.conf , protractor.conf, webpack.conf e.t.c.) and all json files (package.json, tslint.json, tsconfig.json) all on the root at the same level as "/src" folder.
The index.html file hosts the outer container component which is "usually" named "app"
Always keep your Outer Container Component within your main app folder. Our main app folder is "/src".
Shared & Common
One last thing i want to cover today is shared stuff.
There are 2 kinds of shared things,
a) Shared assets
a) Common services, directives and pipes used across multiple components.
The best place is to keep shared assets is within your main app folder ("/src" in our case). This is where they belong.
Assets hold your styles(CSS/Less/SaaS/SCSS), fonts, icons, images and can also contain .json data files. Don't mix these data files with the config .json files that we kept on the root. You can also have a "data" folder for these type of files. They include any static data shared across all components. You may have noticed a folder named "extra" within the assets folder. I always keep this folder for "anything else" in every product. Put your cat photos, your own dps and on a serious note the white board snapshots that you take during meetings within this folder. It turns out to be very handy in the longer and you do not miss on your assets.
Common Services/Directives/Pipes e.t.c
Angular2 stuff that is shared across components is best kept in a separate folder within the "/src" folder. It is usually named "common" or "shared".
One Last Thing ...
I may not have done it in this example, but one important thing that you can do is try to keep your folders as well as file and folder names either singular or plural. This helps you avoid file misspells.
I will try and add/update stuff to this post as Angular 2 gets closer to final release. Your feedbacks and suggestions are highly appreciated.