This project was generated with Angular CLI version 13.3.6.
An task application made by Angular framework with angular CLI 13.3.6.
- Learned basic structure of Angular framework with Ngmodule and components.
- Created and updated components and module declarations by Angular CLI command.
- Shared data between components with using Input, Output, EventEmitter modules.
- Generated services to handle task data and adding task control features.
- Added toggle feature with Observable and Subscribe module.
- Used RouteModule and Route to control routing between components.
Run ng serve
for a dev server. Navigate to http://localhost:4200/
. The application will automatically reload if you change any of the source files.
package.json
: The package. json file is the heart of any Node project. It records important metadata about a project which is required before publishing to NPM, and also defines functional attributes of a project that npm uses to install dependencies, run scripts, and identify the entry point to our package.tsconfig.json
: A given Angular workspace contains several TypeScript configuration files. At the root tsconfig.json file specifies the base TypeScript and Angular compiler options that all projects in the workspace inherit.angular.json
: A file named angular.json at the root level of an Angular workspace provides workspace-wide and project-specific configuration defaults for build and development tools provided by the Angular CLI. Path values given in the configuration are relative to the root workspace folder.
NgModule
: @NgModule takes a metadata object that describes how to compile a component's template and how to create an injector at runtime. It identifies the module's own components, directives, and pipes, making some of them public, through the exports property, so that external components can use them.
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
Component
: Angular components are a subset of directives, always associated with a template. Unlike other directives, only one component can be instantiated for a given element in a template. A component must belong to an NgModule in order for it to be available to another component or application.
To implement global styles on this project, we can change src/styles.css
.
Now, let's make a new component with angularCLI. If you want to make header
component, ng generate component components/header
command would start adding new component.
As you can see, AngularCLI automatically create css, html, and typescript file of header.component. And also, it add HeaderComponent
inside of app.module.ts
file declarations
part like below.
@NgModule({
declarations: [AppComponent, HeaderComponent],
imports: [BrowserModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
header.component.ts
file has Component annotation and OnInit interface like below.
import { Component, OnInit } from "@angular/core";
@Component({
selector: "app-header",
templateUrl: "./header.component.html",
styleUrls: ["./header.component.css"],
})
export class HeaderComponent implements OnInit {
constructor() {}
ngOnInit(): void {}
}
OnInit
: A lifecycle hook that is called after Angular has initialized all data-bound properties of a directive. Define an ngOnInit() method to handle any additional initialization tasks such as HTTP request. interface OnInit { ngOnInit(): void }
If you want to make specific style setup, you need to add some code on the header.component.css
.
For now, we gonna make another angular component by using angularCLI. Enter the command ng generate component components/button
. The button component should be embedded inside of header component. For doing this, you can add <app-button></app-button>
tag on the header.component.html
.
<header>
<h1>{{ title }}</h1>
<app-button></app-button>
</header>
And then, we can see 'button works!' text is added on the localhost page. After changing this tag into Click, there is a button with text click.
But button component color
and text
should be changed by diffenrent context. For doing this, you can use @Input
and [ngStyle]
.
import { Component, OnInit, Input } from '@angular/core';
...
export class ButtonComponent implements OnInit {
@Input() text: string;
@Input() color: string;
constructor() {
this.text = '';
this.color = '';
}
ngOnInit(): void {}
}
As you can see, Input
make variable that can be used inside of html with {{}}. And [ngStyle]
can controll CSS style directly with color
variable.
<button [ngStyle]="{ 'background-color': color }" class="btn">
{{ text }}
</button>
Now you can change color and text of button component on the header.component.html
with attribute.
<header>
<h1>{{ title }}</h1>
<app-button color="green" text="Add"></app-button>
</header>
Depending on purpose of the button component, you probably want to customize button feature. In order to do this, we should use Output
and EventEmitter
.
<button
[ngStyle]="{ 'background-color': color }"
class="btn"
(click)="onClick()"
>
{{ text }}
</button>
In this project, we are making header component with button component. If other component want to use same button component, each button click would make different functionalities. For doing this, you need to add customized function and import several modules.
- Output: @Output decorator is used to pass the data from child to parent component. @Output decorator binds a property of a component, to send data from one component to the calling component. @Output binds a property of the type of angular EventEmitter class.
- EventEmitter: EventEmitter is a module that helps share data between components using emit() and subscribe() methods. EventEmitter is in the Observables layer, which observes changes and values and emits the data to the components subscribed to that EventEmitter instance.
First of all, add a (click)
attribute on the <button>
tag.
<button
[ngStyle]="{ 'background-color': color }"
class="btn"
(click)="onClick()"
>
{{ text }}
</button>
After than, make an onClick()
methon on the button.component.ts
with using @Output
and EventEmitter
.
import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core";
@Component({
selector: "app-button",
templateUrl: "./button.component.html",
styleUrls: ["./button.component.css"],
})
export class ButtonComponent implements OnInit {
@Input() text: string;
@Input() color: string;
@Output() btnClick = new EventEmitter();
constructor() {
this.text = "";
this.color = "";
}
ngOnInit(): void {}
onClick() {
this.btnClick.emit();
}
}
btnClick
method is for sharing data between button and header component. Inside of header.component.html
, add a ``(btnClick)="toggleAddTask()` attribute to get information whether button is clicked or not.
<header>
<h1>{{ title }}</h1>
<app-button
color="green"
text="Add"
(btnClick)="toggleAddTask()"
></app-button>
</header>
And define toggleAddTask()
function on the header.component.ts
.
import { Component, OnInit } from "@angular/core";
@Component({
selector: "app-header",
templateUrl: "./header.component.html",
styleUrls: ["./header.component.css"],
})
export class HeaderComponent implements OnInit {
title: string = "Task Tracker";
constructor() {}
ngOnInit(): void {}
toggleAddTask() {
console.log("toggle");
}
}
When you click the button, you can see 'toggle' on the console window like below.
These data below is for test UI in this project. Task.ts
exports Task
as an interface. mock-tasks.ts
imports this Task
and exports list of JSON format as TASKS
.
Task.ts
export interface Task {
id?: number;
text: string;
day: string;
reminder: boolean;
}
mock-tasks.ts
import { Task } from "./Task";
export const TASKS: Task[] = [
{
id: 1,
text: "Doctors Appointment",
day: "May 5th at 2:30pm",
reminder: true,
},
{
id: 2,
text: "Meeting at School",
day: "May 6th at 1:30pm",
reminder: true,
},
{
id: 3,
text: "Food Shopping",
day: "May 7th at 12:30pm",
reminder: false,
},
];
Using angularCLI, make task component. Import Task
and TASKS
from data files, and show tasks.text
on the tasks.component.html
.
<p *ngFor="let task of tasks">{{ task.text }}</p>
- *ngFor: *ngFor is a predefined directive in Angular. It accepts an array to iterate data over atemplate to replicate the template with different data. It's the same as the forEach() method in JavaScript, which also iterates over an array.
If we need to use font awesome, you can use angularCLI command ng add @fortawesome/angular-fontawesome@<version>
. You should check version compatibilities between angular and angular font awesome module.
Now we can use font awesome icons with adding attribute on the specific html tag.
- import specific modules from @fortawesome module
import { Component, OnInit, Input } from "@angular/core";
import { Task } from "src/app/Task";
import { faTimes } from "@fortawesome/free-solid-svg-icons";
@Component({
selector: "app-task-item",
templateUrl: "./task-item.component.html",
styleUrls: ["./task-item.component.css"],
})
export class TaskItemComponent implements OnInit {
@Input() task!: Task;
faTimes = faTimes;
constructor() {}
ngOnInit(): void {}
}
- Add attribute and change style with [ngStyle].
<div class="task">
<h3>
{{ task.text }}
<fa-icon [ngStyle]="{ color: 'red' }" [icon]="faTimes"></fa-icon>
</h3>
<p>{{ task.day }}</p>
</div>
What is the service
on the Angular?
Service is a broad category encompassing any value, function, or feature that an application needs. A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well. Angular distinguishes components from services to increase modularity and reusability.
task.service.ts
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "root",
})
export class TaskService {
constructor() {}
}
- Injectable: The @Injectable() decorator defines a class as a service in Angular and allows Angular to inject it into a component as a dependency. Likewise, the @Injectable() decorator indicates that a component, class, pipe, or NgModule has a dependency on a service. The injector is the main mechanism.
To control task data inside of task.service.ts
, we can use Observable
and of
after import Task
and TASKS
.
- Objervable: Observables provide support for passing messages between parts of your application. They are used frequently in Angular and are a technique for event handling, asynchronous programming, and handling multiple values.
task.service.ts
import { Injectable } from "@angular/core";
import { Observable, of } from "rxjs";
import { Task } from "src/app/Task";
import { TASKS } from "src/app/mock-tasks";
@Injectable({
providedIn: "root",
})
export class TaskService {
constructor() {}
getTasks(): Observable<Task[]> {
const tasks = of(TASKS);
return tasks;
}
}
task.component.ts
import { Component, OnInit } from "@angular/core";
import { Task } from "src/app/Task";
import { TaskService } from "src/app/services/task.service";
@Component({
selector: "app-tasks",
templateUrl: "./tasks.component.html",
styleUrls: ["./tasks.component.css"],
})
export class TasksComponent implements OnInit {
tasks: Task[] = [];
constructor(private taskService: TaskService) {}
ngOnInit(): void {
this.taskService.getTasks().subscribe((tasks) => (this.tasks = tasks));
}
}
JSON server is a full fake REST API with zero coding in less than 30 seconds.
Install: npm i json-server
Add scripts: package.json > "scripts" > "server": "json-server --watch db.json --port 5000"
Add db.json
file on the root position of this project and enter this commandnpm run server
. And then, you can find fake database on localhost:5000/tasks
.
In order to connect backend with angular project, we need several modules from @angular/common/http
.
-
HttpClientModule: The HttpClientModule is a service module provided by Angular that allows us to perform HTTP requests and easily manipulate those requests and their responses. It is called a service module because it only instantiates services and does not export any components, directives or pipes.
-
HttpClient: Performs HTTP requests. This service is available as an injectable class, with methods to perform HTTP requests. Each request method has multiple signatures, and the return type varies based on the signature that is called (mainly the values of observe and responseType).
-
HttpHeaders: Represents the header configuration options for an HTTP request. Instances are immutable. Modifying methods return a cloned instance with the change. The original object is never changed.
And TaskService
class would be changed like below.
export class TaskService {
private apiUrl = "http://localhost:5000/tasks";
constructor(private http: HttpClient) {}
getTasks(): Observable<Task[]> {
return this.http.get<Task[]>(this.apiUrl);
}
}
If we click x
icon, that specific task should be deleted. In order to do this feature, you need to add delete method not only task-item component
but also task.service
.
On the task-item component, onDelete
method should be created.
task-item.component.html
<fa-icon
(click)="onDelete(task)"
[ngStyle]="{ color: 'red' }"
[icon]="faTimes"
></fa-icon>
task-item.component.ts
// Method for click event
onDelete(task: Task) {
this.onDeleteTask.emit(task);
}
Task component need to figure out delete click event from task-item component. To do this, we can add @Output on the task-item.component.ts.
task-item.component.ts
@Output() onDeleteTask: EventEmitter<Task> = new EventEmitter();
And task component take this onDeleteTask
method on the task.component.html.
task.component.html
<app-task-item
*ngFor="let task of tasks"
[task]="task"
(onDeleteTask)="deleteTask(task)"
></app-task-item>
Now, you need make deleteTask
method on the task.component.ts and define on service.
task.component.ts
// Take deletaTask function from service
// Delete onDelete task and subscribe which is filtered
deleteTask(task: Task) {
this.taskService
.deleteTask(task)
.subscribe(
() => (this.tasks = this.tasks.filter((t) => t.id !== task.id))
);
}
task.service.ts
// Function to delete task -> tasks.component.ts
deleteTask(task: Task): Observable<Task> {
const url = `${this.apiUrl}/${task.id}`;
return this.http.delete<Task>(url);
}
- FormsModule: Exports the required providers and directives for template-driven forms, making them available for import by NgModules that import this module. ReactiveFormsModule. Exports the required infrastructure and directives for reactive forms, making them available for import by NgModules that import this module.
add-task.component.html
<input
type="text"
name="text"
[(ngModel)]="text"
id="text"
placeholder="Add Task"
/>
We can use [(ngModel)]=""
for data binding. Each of the input tag has their own ngModel
attribute to bind data.
Now, you probably want to make button text and color changable when user clicked. In order to do this, we need to make another service. ng generate service services/ui
Then we will make two private variable and functions.
ui.service.ts
export class UiService {
private showAddTask: boolean = false;
private subject = new Subject<any>();
constructor() {}
toggleAddTask(): void {
this.showAddTask = !this.showAddTask;
this.subject.next(this.showAddTask);
}
onToggle(): Observable<any> {
return this.subject.asObservable();
}
}
On the header component, import UiService
and subscribe after making uiService
object by using constructor.
header.component.ts
export class HeaderComponent implements OnInit {
title: string = "Task Tracker";
// To add toggle feature
showAddTask: boolean = true;
subscription!: Subscription;
constructor(private uiService: UiService) {
this.subscription = this.uiService
.onToggle()
.subscribe((value) => (this.showAddTask = value));
}
ngOnInit(): void {}
toggleAddTask() {
this.uiService.toggleAddTask();
}
}
And you can change color and text of the button by using attribute below.
header.component.html
<app-button
color="{{ showAddTask ? 'red' : 'green' }}"
text="{{ showAddTask ? 'Close' : 'Add' }}"
(btnClick)="toggleAddTask()"
></app-button>
Finally, we will add showing add-task form feature with toggle button. To do this, you need to use UiService
, Subscription
on the add-task
component such as the header component. We also make variables showAddTask
, subscription
and UiService
object. After this, *ngIf="showAddTask"
should be added on the form
tag of add-task component.
<form *ngIf="showAddTask" class="add-form" (ngSubmit)="onSubmit()"></form>
In order to fully use Angular framework, RouteModule
and Route
is mandatory. With route controll, you can handle components with url such as /about
. For using this, there are several steps.
app.module.ts
import { RouterModule, Routes } from '@angular/router';
...
// Make Route list
const appRoutes: Routes = [
{ path: '', component: TasksComponent },
{ path: 'about', component: AboutComponent },
];
To use router feature, routerLink
should be added on the specific html tag with targeted url.
<div>
<h2>Task App</h2>
<h4>Version: 1.0.0</h4>
<a routerLink="/">Go Back</a>
</div>
- https://youtu.be/3dHNOWTI7H8
- https://heynode.com/tutorial/what-packagejson/
- https://angular.io/guide/typescript-configuration
- https://angular.io/guide/workspace-config
- https://angular.io/api/core/Component
- https://angular.io/api/core/OnInit
- https://www.pluralsight.com/guides/repeating-data-with-ngfor
- https://angular.io/guide/architecture-services
- https://indepth.dev/posts/1142/exploring-the-httpclientmodule-in-angular
- https://angular.io/api/common/http/HttpClient#description