Scaffold your next Ionic application
When it comes to developing an application the goal is to generate a maintainable and extensible app. The concept that is hiding behind that is the modularization. This means the code can be written to be very reusable, logical, easy to understand with quicker bug fixes, less bugs and faster implementation of new features.
Especially, while your app is growing bigger the structure should adapt with it. In this article, we are going to talk about the different approaches and practices for organising your app structure. Since Ionic uses AngularJS, these structure patterns can be applied to any AngularJS app. Naturally, this does not mean that many of the following concepts are not applicable to other frameworks too.Also, we will demonstrate a simple example of an Ionic 1/Angular 1 app that adopts a modular architecture and applies some of the most important design and styling practices.
Though, structure often needs to be different according to the scale of the application.
Prevalent Structure Patterns
An Ionic app uses AngularJS so when it comes to figuring out the appropriate structure many of the rules for the structure of an AngularJS app apply to Ionic too.
/app.js --> application
/controllers.js --> all application controllers
/filters.js --> all custom angular filters
/services.js--> all custom angular services
/partials --> angular view partials (partial html templates)
This approach could be very useful when , for example, writing a tutorial as it is easy for the reader to visualize and understand the basic concepts of how AngularJS works and its basic attributes.
Nevertheless, after the initial version of angular-seed, the architecture has been improved. This is completely reasonable as, once you get a dozen of controllers or services, these files become really big and objects you are looking for are hard to find. This time, the structure of the app folder is based on the different views of the app.
/view1.html --> the partial template
/view1.js --> the controller logic
/view1_test.js --> tests of the controller
/view2.html --> the partial template
/view2.js --> the controller logic
/view2_test.js --> tests of the controller
Those two kinds of structures reflect two of the most common architectures, order by type and order by feature. Below, we will describe them in more detail.
Organise by type
This structure is simple and easy when the app starts with a really small number of features. It is better to be used in the beginning of the app development. Following this structure, all your controllers are grouped in a single file. Similarly, all your services and views are included in their own file. They are usually located under a single folder like so:
However, especially when you have numerous controllers/services/views, it is also common to break them down into separate controllers/services/views and organize them one level deeper, i.e. in folders such as controllers/services/views.
The controllers folder is where all the controllers are placed. Since an app has several controllers we could put them in a one place. The services folder contains all the units of code that are responsible to do specific tasks across your app, i.e. services. The views folder or templates folder is where all the HTML resides. Looking at the second case of this structure we understand that file names tend to lose their significance as the words “controller” and “service” have no additional advantage.
Organise by component/feature (one module per directory)
Before presenting the actual structure, let’s talk about the LIFT principle. According to this, the structure should follow 4 basic guidelines:
- Locating our code is easy: We should find the files we want to work on quickly.
- Identify code at a glance: We should know what a file contains and represents by its file name.
- Flat structure as long as we can: We should not search too many levels of folders to find a file.
- Try to stay DRY (Don’t Repeat Yourself) or T-Dry: It is important but not crucial to not to repeat words like controller and view when, for example, it is obvious that a file is a controller/view by convention.
As a consequence of this principle, structure by feature is considered to be the best practice to build scalable and maintainable AngularJS apps.The idea here is that, when you are looking for the code that makes a feature work, it is located in one place. This architecture mirrors the app structure. Folders are named according to the feature they represent.
Although the feature is quite clear for each folder, common folder is used for code that is used by many modules. For example, many services are shared with different features. This folder can also be called shared.
The concept of modularity is closely linked to this type of structure and it is especially handy for the development of bigger apps or in case that the app is supposed to build up in larger scale. Adding new features as your app grows should be as quick and easy as possible. Also, especially when working on a bigger project, the team members should be able to work on different modules independently and new team members should be able to understand the code easily.
After all, “modules” are a big part of the AngularJS methodology. According to AngularJS Developer Guide, module is a container for the different parts of your app — controllers, services, filters, directives, etc. The folder for each feature of a modular app contains the template(s), controller(s), service(s) and routing for that feature. In general, the rule is that one directory contains only the code of one module.
In a while, we will study ways to bootstrap a modular app, we will study the structure of a starter app and we will find out how easily we can add a new module.
Talking about application structure we could not leave out naming conventions. Naming conventions provide a consistent way to find content at a glance.
According to John Papa, the name of most components consists of :
- the file name (foo.controller.js)
- the registered component name with Angular (FooController)
The file names should help you find fast and easy the code you are looking for and understand it easier. In summary, the naming conventions are the following:
- Feature file names: Consist of a name that describes the feature and its type, e.g. dashboard.controller.js, dashboard.service.js. Only, for controllers, the word “controller” could be removed but all other conventions still hold a suffix of the type.
- Test file names: The component’s name with a suffix of “spec”. E.g. dashboard.controller.spec.js.
- Controller names: Consists of the feature name followed by the word “controller” with UpperCaseCamel as they are constructors. E.g. DashboardController
- Factory and service names: In camel-case, the feature name with “service” suffix only when it is not clear what they are. E.g. logger, creditService.
- Modules: When there are multiple modules, the file name consists of the feature name followed by the “module” word. E.g. admin.module.js with registered module name admin.
- Configuration: The file name consists of the module name followed by the “config” word. Configuration for the main app could be app.config.js or simply config.js. Configuration for admin module could be admin.config.js.
- Routes: the file name consists of the module name followed by the “route” word. E.g. admin.route.js.
If you want to start building your application with Ionic Framework, there’s no better place than starter apps. Starter apps are usually templates or applications that offer you a glimpse into what Ionic framework can do. These apps come in all shapes and forms.
Supermodular, an open source Ionic starter app, adopts a modular structure by feature and naming practices we described earlier.
Using Supermodular as a Starter
Clone Github Repo
Visit https://github.com/skounis/supermodular and download or fork Supermodular app.
Alternatively, you can clone the repository:
$ git clone https://github.com/skounis/supermodular.git
Personalize the app
Since Supermodular app will be used as a starter app, chances are that you want to change its name and its namespace.
Change the app name
Under root folder, you can set the name of your app in ionic.project file:
Leave the app_id empty as it will be auto-generated once you try to upload your app to your ionic.io account.
Under root folder, in config.xml file you can also change the app name and the highlighted info:
Also, the app name can be changed in bower.json and package.json files as shown below:
The package.json.local file is just a copy of the package.json file. README.md file could be alterted too with the info you want to provide.
Change the namespace
Then you should change all the module names of the app. The easiest way to do this is by opening the project folder in an editor such as Atom or Sublime and search in the project folder for the “supermodular” word.
Then, replace that with the name of your app.
Notice that you should not alter the files you changed in the previous section.
Install libraries and plugins
Open a terminal window and navigate to supermodular folder. Install NodeJS dependencies:
$ npm install
Similarly, Windows users should execute:
Run a local development server
Navigate to supermodular directory and run the application in the browser:
$ grunt serve — lab
Supermodular app structure
From a quick look it is obvious that the app is organized by feature and is modular as each folder contains all the code required for a feature/module to work.
The styles folder includes all the scss files of the app since it supports SASS.
Also, notice that there is a common folder. As mentioned earlier, the code in this folder is supposed to be shared among modules.
Here, we see
- directives/gmaps/gmaps.directive.js: a directive used for handling Google Maps
- services/external-apps.service.js: a service for opening external apps (Maps app and Browser) and
- services/html-to-plain-text.service.js: a service for the conversion of HTML to plain text.
For example, externalAppsService is used in the Home screen (module), for opening a facebook URL using the browser app of the device. This service is very likely that it will be used in any other modules in order to open any url launching the device’s browser.
Registred controllers are named with UpperCaseCamel, services and factories are named with camel-case and, in general, the app adopts the naming conventions John Papa suggests.
Adding a new module
Now, it is time to get our feet wet and see how convenient a well organised modular structure is.
We are going to add the feature of displaying a list of cards for the movies that are stored in a local file in a JSON structure. We will store this file under misc folder and we will name it movies.json. Afterwards, we will create a new folder and we will name it movies under app/scripts path. This folder will contain the controller, service, template and route of this feature.
Firstly, let’s declare the movies module in the app.js file. All the app’s modules should be included in this file.
HTML View (movies.html)
Continuing with the view part of our module (aka template), it will contain cards so each one of them shows the movie title, some teaser text and a thumbnail. Thus, the template should be similar to the following:
The movies.module.js file is going to include all the routing like so:
The movies.controller.js file should contain getMovies() function which will read the movies.json file from a URL and will make its data available to the $scope. Notice that the URL is provided by the streamService. In this way we keep only the logic in the controller and we use the service to serve the controller with the movies.json url.
The movies.service.js file is responsible for giving the MoviesController what needs to use in order to wire up the scope to its view. Therefore, moviesService() method is used to provide the URL where the movies.json file is located to the controller.
Also, we will add the live radio option in the menu so user can have access to it. Notice how easy is to locate the menu template. Therefore, we should add the following lines in menu.html under the app/scripts/menu path:
<ion-item nav-clear menu-close class=”item-icon-left” href=”#/app/movies”>
<i class=”icon ion-videocamera”></i>
Run and Test
Now we are ready to start our app and test this newly added module. We use the command:
$ grunt serve
Run on emulator
Firstly, you should add the preferred platform. For iOS run the command:
$ grunt platform:add:ios
For android run the command:
$ grunt platform:add:android
The application is ready to start in the simulator:
$ grunt emulate:android
$ grunt emulate:ios
Build the app for specific platforms
In order to build all the added platforms, run the command:
$ grunt build
In order to build Android platform, run the command:
$ grunt build:android
In order to build iOS platform, run the command:
$ grunt build:ios
Interesting in Ionic?
Enjoyed this Post?
The AppSeed team
/Stavros Kounis, skounis.github.io