In this blog post, we will walk you through the step-by-step process of building a ToDo application from scratch. Whether you are new to Angular or just looking for a refresher, this guide will help you get started on your journey to become an Angular developer. This app serves as the User Interface (UI) for the ToDo API, that we have built in my previous blog.
Prerequisites
Before we dive into the development process, let's make sure we have everything we need. Ensure that we have the following installed:
- Node.js and npm (Node Package Manager)
- Angular CLI (Command Line Interface)
Order of execution
In Angular, understanding the order of execution is essential for developing robust and efficient applications. The following is a brief overview of the typical order of execution in an Angular application.
Bootstrap: Angular starts by bootstrapping the root module of the application. This triggers the initialisation process.
Module Loading: Angular loads the required modules and their dependencies. It traverses the module tree, starting from the root module, and loads each module as needed.
Component Initialisation: Once the modules are loaded, Angular initialises the components defined in the templates. It creates component instances, sets up bindings, and resolves dependencies.
Template Compilation: Angular compiles the component templates to generate the corresponding views. During this process, it combines HTML templates with component logic, transforms directives and bindings, and creates the necessary data structures for rendering.
Change Detection: Angular performs change detection to detect and propagate changes throughout the application. It checks for changes in component properties, bindings, and other data sources, and updates the views accordingly. This ensures that the UI reflects the latest state of the application.
Rendering: After change detection, Angular renders the updated views based on the application state. It updates the DOM with the changes and applies any necessary styling or layout adjustments.
Event Handling and User Interaction: Angular listens for user interactions and handles events triggered by user actions. It responds to user input, executes the corresponding logic, and updates the application state as needed.
Destruction and Cleanup: When a component is no longer needed or gets destroyed, Angular performs cleanup operations. It unsubscribes from event listeners, releases resources, and performs any necessary teardown tasks to prevent memory leaks and ensure efficient resource utilisation.
Understanding the order of execution in Angular helps developers in optimising performance, identifying potential issues, and ensuring smooth application behaviour. It allows for better control over the application lifecycle and facilitates the implementation of complex application logic.
Creating a new Angular project
To kickstart our ToDo app, we'll begin by creating a new Angular project. Open our terminal or command prompt and run the following command:
ng new todo-ui
As we embark on setting up our ToDo app, a few important considerations come to mind. Firstly, we need to determine if routing is required for seamless navigation within our app (and the answer is a resounding "yes!"). Additionally, choosing the appropriate stylesheet format is crucial, and in this case, I recommend utilising SCSS for its flexibility and enhanced capabilities. To kickstart our project, navigate to the new directory called "todo-ui" which will serve as the foundation for our Angular project's basic structure.
Node version
Lets start by creating a new .nvmrc file in the root of the project. This defines what version of node we are using. We are going to use the ‘lts/Gallium’ version. Add the following line to the file.
lts/Gallium
Run the following command and might prompt us to install the version if it is missing.
nvm use
Clean up and initial set-up
Remove the static content of app.component.html and add the following.
<app-nav-bar></app-nav-bar>
<div class="app-container">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
Now, lets add Flex layout and Angular Material packages to the project.
ng add @angular/material
npm install @angular/flex-layout
Let's add FontAwesome and flexboxgrid in the index.html. Add the following in the <head>
tag
<link rel="stylesheet" type="text/css" href="https://use.fontawesome.com/releases/v6.2.1/css/all.css" defer/>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css" defer/>
Environment
The environment.ts and environment.prod.ts files play a crucial role in managing environment-specific configuration settings. The environment.ts file contains configuration variables specific to the development environment, while the environment.prod.ts file contains configuration variables for the production environment.
The environment.ts file is used during development and allows developers to define environment-specific settings such as API endpoints, debug flags, or other variables that are required during the development and testing phases. These variables can be easily accessed throughout the application using the Angular environment service.
On the other hand, the environment.prod.ts file is used for the production build of the application. It contains configuration variables optimised for the production environment, such as production API endpoints, analytics keys, or any other settings that differ from the development environment. These variables are used when the application is built for deployment to ensure proper functionality and performance in the production environment.
By maintaining separate environment files, developers can easily switch between different environments and ensure that the application behaves correctly in each environment. It allows for seamless deployment and configuration management, as the appropriate environment file is automatically selected based on the build target.
Please do keep in mind not to add any secrets/passwords in these environment files.
Connecting to backend API
When connecting to the todo-api that we built earlier, which is running on port 3000, we can encounter Cross-Origin Resource Sharing (CORS) issues due to browser security restrictions. To overcome this, we can utilise a proxy configuration in our Angular project.
The proxy configuration allows us to forward requests from our Angular application to the todo-api server, avoiding the CORS restrictions imposed by the browser. By configuring the proxy, we can send requests to the desired API endpoint without encountering any CORS-related errors.
To set up the proxy, we need to create a file named "proxy.conf.json" in the src directory of our Angular project. Inside this file, we define the proxy configuration by specifying the target URL, which in our case is "http://localhost:3000".
{
"/api/*": {
"target": "http://localhost:3000",
"secure": false,
"logLevel": "debug"
}
}
Once the proxy configuration file is set up, we need to modify the "angular.json" file in our project's root directory. Under the "architect" section, we locate the "serve" options for our project, and inside that, we add the "proxyConfig" property, pointing to the "proxy.conf.json" file we created earlier.
"serve": {
"options": {
"browserTarget": "todo-ui:build",
"proxyConfig": "src/proxy.conf.json"
},
...
},
By configuring the proxy, our Angular application will send API requests to the specified target URL (todo-api running on port 3000) without encountering CORS issues. This enables seamless communication between our frontend and backend, allowing us to retrieve and manipulate data from the todo-api server within our Angular application.
Modules
Modules in Angular play a crucial role in organising and structuring our application. They act as containers that group related components, services, directives, and other resources together. Modules provide a way to manage the complexity of larger applications by breaking them down into smaller, more manageable parts.
Angular modules serve several purposes. They facilitate component encapsulation and provide a clear boundary for component dependencies. This promotes modularity, reusability, and maintainability. Modules also enable lazy loading, allowing us to load specific parts of our application only when needed, optimising performance.
In addition to organising our code, modules define the context in which components and services are created and used. They handle dependency injection, allowing components to access the required services and dependencies.
Angular provides different types of modules, including the root module (AppModule) and feature modules that focus on specific parts of our application. We can also create shared modules to encapsulate commonly used components, directives, and pipes, promoting code reuse across different parts of the application.
By leveraging modules effectively, we can build scalable, maintainable Angular applications with clear separation of concerns, better code organisation, and improved developer productivity.
In this project, we have followed some best practices to ensure modular abstraction and maintainability:
Shared Modules: We have created a Shared Module to gather all the shared components, directives, and pipes that are commonly used across different modules. This promotes reusability and reduces code duplication.
Material Module: To streamline the usage of Angular Material components, we have created a dedicated Material Module. Here, we import and configure all the necessary material modules to keep them isolated and easily manageable.
Feature Modules: Major components such as TodoModule have their own dedicated modules. This encapsulation ensures better separation of concerns and modularity. Each feature module contains the components, services, and other resources specific to that particular feature.
AppModule: Finally, in our AppModule, we import all the necessary modules, including the Shared Module, Material Module, and feature modules. This centralises the module imports and allows the components and services from different modules to interact seamlessly.
By adhering to these norms, we promote code organisation, reusability, and maintainability, making our project more scalable and easier to maintain in the long run.
Component and its lifecycle
In Angular components, the order of code execution follows a predefined sequence to ensure proper initialisation and functionality. Here is an overview of the typical order of execution within an Angular component:
Constructor: The component's constructor is the first code block that executes when an instance of the component is created. It is used for dependency injection and initialising local variables. However, it is important to note that the constructor should not be used for complex logic or data manipulation.
ngOnChanges: If the component has input properties bound to its template, the ngOnChanges lifecycle hook is triggered whenever the input values change. This hook allows us to respond to changes and perform actions based on the updated input values.
ngOnInit: The ngOnInit lifecycle hook is called after the constructor and ngOnChanges. It is commonly used for initialisation tasks that require the component to be fully set up. This is a good place to fetch data from a service, set default values, or perform other setup operations.
ngDoCheck: The ngDoCheck lifecycle hook is invoked during every change detection cycle. It allows us to implement custom change detection logic and perform actions based on changes detected in the component's state.
ngAfterContentInit: This lifecycle hook is called after the component's content, such as projected content or child components, has been initialised. It is useful for accessing and manipulating the component's content.
ngAfterViewInit: The ngAfterViewInit hook is triggered after the component's view and child views have been initialised. It is commonly used for interacting with the DOM or performing operations that require the view to be fully rendered.
ngOnDestroy: The ngOnDestroy lifecycle hook is called just before a component is destroyed. It provides an opportunity to perform any necessary cleanup tasks, such as unsubscribing from observables, releasing resources, or cancelling timers.
By following this order of execution, Angular ensures that components are properly initialised, their dependencies are resolved, and their lifecycle hooks are triggered at the appropriate times. Understanding this order allows developers to organise their code effectively and ensure that components function as intended throughout their lifecycle.
Shared Components
Let us create a set of shared components that promote component reusability and maintain consistency throughout the application. These shared components reside in the 'src/app/shared/components' directory and are imported and exported from the SharedModule.
NavBarComponent serves as the navigation bar of the application, providing users with easy access to different sections or pages of the app. It typically contains menus, links, or buttons for navigation purposes.
FooterComponent is responsible for displaying a footer at the bottom of the application, providing additional information, copyright notices, or links to relevant pages.
HeadingComponent is a reusable component used to display a heading or title for each page. It helps to maintain consistent styling and structure across different pages of the application.
EmptyComponent is designed to be displayed when a list or data set is empty. It provides a message or placeholder content to inform users that there is no data available.
LoadingComponent is used to show a loading spinner or animation while the application is fetching data or performing asynchronous operations. It provides visual feedback to the users, indicating that the app is working in the background.
TextModalComponent is a dialog or modal component used to add or input data in the application. It typically includes form fields, buttons, and validation to gather user input and save it to the system.
By extracting and centralising these components in the SharedModule, we can easily import and use them in various other modules and components without duplicating code. Please do find the full code of these components in github.
Other Components
Now let’s create the other components that we are after. We can make use of the Angular CLI commands for that, which is really handy.
ng g c components/home
ng g c components/not-found
This will generate the necessary files for our component, including the HTML template, SCSS styles, and TypeScript code. We need to add these components into the declaration section of AppModule.
The HomeComponent serves as the welcome or homepage of our application, acting as the landing page that users first encounter when accessing our site. It provides an overview of our app's features, highlights, or any relevant information we want to showcase to users right from the start. This component is designed to provide a visually appealing and engaging user experience, making a positive first impression.
On the other hand, the NotFoundComponent plays a crucial role as a fallback page. Whenever users navigate to a route that doesn't exist or provide an incorrect route, they will be redirected to the NotFoundComponent. This component serves as an error page, informing users that the requested page or resource cannot be found. It helps to improve the user experience by gracefully handling such scenarios and providing a clear message to users about the issue encountered.
Elephant in the room, ToDo
ToDo Modal
Let us create a new modal, Todo.ts.
export interface Todo {
_id: string;
description: string;
is_active: boolean;
updatedAt: string;
}
ToDo Service
The TodoService is a crucial component in the ToDo application, responsible for handling the data management and communication with the backend API. It encapsulates the logic and functionality related to retrieving, updating, deleting, and adding new ToDo items.
It includes the following functions: getAllToDos (retrieve all ToDo items), update (modify and update a ToDo item), delete (remove a ToDo item), and addNewTodo (create and add a new ToDo item). These functions handle the necessary HTTP requests.
By utilising the TodoService and its functions, the application can interact with the backend server effectively, ensuring seamless data management and synchronisation between the client-side and server-side components.
ToDo Component
Let's create a new ToDo component.
ng g c components/todo
As mentioned previously, this will create all the associated components.
Let us design this component so that this consists of two tabs: one for displaying active ToDos and another for inactive ToDos. The component utilises Angular Material's TabGroup component to handle the tab switching functionality.
Within each tab, we incorporate a loading spinner using Angular Material's ProgressSpinner component. This spinner is displayed while the application is fetching data from the server, providing a visual indicator of the loading process.
To handle the case when the ToDo list is empty, we employ the EmptyComponent. This component is shown within the corresponding tab when there are no ToDos to display. It provides a clear message to the user that the list is empty, enhancing the user experience.
ToDo Card Component
In addition to the TodoComponent, we will create a TodoCardComponent to enhance the visual representation of each ToDo item.
ng g c components/todo/todo-card
The TodoCardComponent is responsible for displaying individual ToDos as aesthetically pleasing card designs.
ToDo Module
To maintain a modular and organised structure for our ToDo components, it is advisable to create a dedicated ToDo module. The ToDo module will be responsible for declaring and encapsulating all the related ToDo components, providing a clear separation of concerns and promoting reusability.
By creating a ToDo module, we can easily manage the dependencies and interactions between different ToDo components. This module acts as a container that groups together all the necessary components, services, and other related entities specific to ToDo functionality.
The ToDo module also enables us to take advantage of Angular's dependency injection system. We can provide services and other dependencies within the module, allowing components to access and utilise them seamlessly.
While it may seem like overkill in the initial stages of the application, adopting this modular approach early on lays the foundation for scalability and maintainability. As the application grows and more features are added, having a dedicated module for ToDo components ensures a clean and organised codebase, making it easier to manage and extend the application in the future.
import { NgModule } from '@angular/core';
import { SharedModule } from 'src/app/shared/modules/shared.module';
import { TodoComponent } from './todo.component';
import { TodoCardComponent } from './todo-card/todo-card.component';
@NgModule({
declarations: [TodoComponent, TodoCardComponent],
imports: [SharedModule],
exports: [TodoComponent]
})
export class TodoModule {
}
ToDo Style
To make our ToDo app visually appealing, we can apply custom styles using CSS. Modify the generated CSS file (todo.component.scss and todo-card.component.scss) to match our desired design.
Style
In our project, we've taken care of incorporating various essential dependencies and resources to enhance the overall design and functionality. FontAwesome and FlexBoxGrid have been integrated, providing access to a wide range of icons and a responsive grid system respectively. We've also seamlessly integrated Angular Material, empowering us to utilise the vast collection of Material design components to enhance the visual appeal and user experience. Additionally, we've implemented custom styles, colours, and functions to further refine the app's aesthetics and extend its capabilities. Feel free to explore the source code for a closer look at these enhancements.
Angular Material
Angular Material is a valuable tool that saves us from reinventing the wheel by providing a comprehensive library of pre-built Material design components. To streamline the usage of these components, we have created a dedicated Material Module where all the necessary material modules are imported and configured. By injecting this Material Module into our AppModule, we ensure that the Material components are readily available throughout our application, simplifying the development process and maintaining consistency in the UI design. This approach allows us to harness the power of Angular Material and leverage its extensive range of components without the need for manual implementation from scratch.
Routing
To enhance the navigation of our project, let's incorporate routes with the following configuration:
Assign the empty routes to the Home page, providing users with a welcoming starting point.
Direct the todo routes to the Todo page, enabling seamless task management and organisation.
Map the 404 routes to an Error Page, ensuring a user-friendly experience when encountering unexpected routes.
For any other routes that don't match the defined paths, gracefully redirect users to the Error Page, maintaining a consistent flow throughout the app.
const routes: Routes = [
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: AppConfig.routes.todo, component: TodoComponent, pathMatch: 'full' },
{ path: AppConfig.routes.error404, component: NotFoundComponent },
{ path: '**', redirectTo: '/' + AppConfig.routes.error404 }
];
Running the app
Let’s us run the app and have a look at it spinning up at http://localhost:4200
yarn start
HomePage
ToDoPage
NotFoundPage
It works!!!
Testing and Debugging
Throughout the development process, it's crucial to test and debug our app to ensure it functions as intended. Angular provides powerful testing tools like Karma and Jasmine. Write unit tests for our components and services to verify their behaviour and catch any potential issues.
Building and Deploying the App
Once we are satisfied with our ToDo app, it's time to build and deploy it. Angular CLI makes this process straightforward. Run the following command:
ng build --prod
This will generate a production-ready build of our app, which can be deployed to a web server or hosting platform of our choice.
Conclusion
Congratulations! We have successfully built and set up our own Angular ToDo app. We have learned how to create a new Angular project, set up components, implement functionality, style the app, and deploy it. This is just the beginning of our Angular journey, and there's much more to explore and learn.
Remember to keep practising, exploring Angular's vast ecosystem, and referring to the official Angular documentation for additional guidance. Happy coding and enjoy using the new ToDo app!
Happy coding!
Code: https://github.com/rohithart/todo-ui
API Code: https://github.com/rohithart/nestjs-todo
Angular Documentation: https://angular.io/docs
Top comments (0)