You may be reading our last article about how to start learning Angular and we gave some basic concepts. The first one to discover is Component. We shared the starting point for Angular components and their compositions files.
So, what is an Angular component? How it really works? what is the lifecycle of a component? how do Angular components communicate?
We will respond to every single question in detail and we will give other useful information.
What is really an Angular Component?
An Angular Component is the most unitary element of an Angular User Interface. It is in other words the smallest block. Components are powerful, they may help us in avoiding code duplication. We can reuse the component behavior ( UI + Logic part ) in multiple parts of our application.
Components cannot be used without a declaration in a module, whether it is the root module or a separate child module.
By default, Angular will do it automatically when generating a new component by the command line “ng generate component componentName”, It will create the 4 files and update the AppModule file ( app.module.ts) like the images below shows
If you are facing an error like this
“[Angular] ‘app-nav’ is not a known element: 1. If ‘app-nav’ is an Angular component, then verify that it is part of this module. 2. If ‘app-nav’ is a Web Component then add “CUSTOM_ELEMENTS_SCHEMA” to the ‘@NgModule.schemas’ of this component to suppress this message “.
Be sure that you are not declaring your component into your module.
Angular Components LifeCycle
To make the usage of components as flexible as the developer wants Angular implements total lifecycle hooks.
We will not be focusing on every single hook of the lifecycle. We will take a look at the important ones.
By default, Angular will implement the OnInit interface by declaring an empty implementation of the ngOnInit method. This hook is called once the component is created, we can use it for example for business logic, data initialization, API calls.
The other usable hook of Angular Component is Ondestroy by Implementing the ngOndestroy method :
Mainly We use this hook in handling RxJS subscriptions to avoid memory leaks.
The last hook, that we will talk about in this blog post is Onchanges : the ngOnChanges will be triggered and called when we have an @input variable in our child component, We mostly compare the previous and current value of the updated variable.
To summarize and to make synchronous sort of these hooks as well as making the case of uses of these hooks :
Hook | Trigger and order | case of use |
---|---|---|
ngOnChanges | -The First hook -Triggered only if the @input variable is present in the child component -May be called multiple times ( depends on the @input variable value ) | – Handle logic business depending on the @Input variable value |
ngOnInit | -The second hook ( the first if ngOnChanges is not present) – called once when the component is initialized | – business logic, data initialization, API calls |
ngOnDestroy | – The last hook ever – called when destroying the component ( when we leave the component ) | – handling RxJS subscriptions ( to unsubscribe from them ==> avoid memory leaks ) |
We hope that the table was clear. We covered only some hooks and we will return later with a specific article to cover the other Hooks. So Do not miss any single article.
We will finish this part with an example of the three hooks :
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
import { Subscription } from 'rxjs';
import { ProductService } from '../services/product.service';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit,OnChanges,OnDestroy {
//@ts-ignore
@Input() categoryName : string ;
//@ts-ignore
subscriptionProducts :Subscription ;
constructor(private productService : ProductService ) { }
ngOnInit(): void {
//example of API call data
this.subscriptionProducts = this.productService.getProductList().subscribe((data)=>{
console.log(data) ;
})
}
ngOnChanges(changes: SimpleChanges): void {
console.log(changes)
}
ngOnDestroy(): void {
// unsubscription example
this.subscriptionProducts.unsubscribe() ;
}
}
Sharing data between Angular components
There are multiple ways to share data between components. We will focus on three of them. We will detect Parent to Child component sharing, Child to Parent sharing and the last method to share data is using Angular services (Singleton Pattern).
Parent Component to Child Component
In the last section, we gave an example with the @Input decorator. So what is @Input decorator? and in what context is it used?
We will explain this by an example.
Here we have the parent component app component ( app.component.ts ) in which we will inject the child component Product list component by putting its selector in the HTML file like the example below :
We want here to pass some data from the app component to the product list component, so how to do this?
We must note that We have to define in the Parent component typescript file the value to pass :
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Ecommerce-Project';
categoryName = "Electronics";
}
And in the child component ( product list component ) we must define the @Input() decorator.
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
import { Subscription } from 'rxjs';
import { ProductService } from '../services/product.service';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit{
@Input() categoryName : string ;
constructor(private productService : ProductService ) { }
ngOnInit(): void {
}
}
Child Component to Parent Component
Here, We will have a reverse treatment. Supposing that the product List Component (child component ) wants to send data and information to the parent Component (App Component), We will use here the @Output decorator.
We have in the parent component ( app component ) the list of the products added to the Card , We want to notify the Parent component from the product list that we are adding a product to the Card list.
Here in the child Component (product list ), We will add in the template file a method that detects the id of the product to add to the Card.
<p *ngFor="let product of Products">
<span >
<ul>
<li>
{{product.label}}
</li>
<li>
{{product.Price}}
</li>
<span (click)="addToCard(product.id)">
Add to Card
</span>
</ul>
</span>
</p>
In the Typescript file of the Child Component (Product list ), We will add the @Output decorator.
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core';
import { Subscription } from 'rxjs';
import { ProductService } from '../services/product.service';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit,OnChanges,OnDestroy {
Products : Product [] = [] ;
constructor(private productService : ProductService ) { }
ngOnInit(): void {
//example of API call data
this.subscriptionProducts = this.productService.getProductList().subscribe((data)=>{
this.Products= data ;
})
}
addToCard(value: string) {
this.addProductToCard.emit(value);
}
}
Then in the Parent component Template file (app.component.html), we will add the EventEmitter in order to make the link between the two components.
<app-product-list (addProductToCard)="addProductToCard($event)" ></app-product-list>
And finally, in the Typescript file of the Parent Component, we will create the method that will receive the product id to add to the Card list.
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Ecommerce-Project';
ProductsInCard : string[] = [];
addProductToCard(productId: string) {
this.ProductsInCard.push(productId);
}
}
Passing data through Angular Services
The people who know Design Patterns can easily identify that Angular services use the Singleton pattern. Because we can inject Service’s variables inside Angular components, passing data between components through services is considered one of the most common methods to share data.
Data Binding
Data binding is the concept that Angular uses to handle the communication between the view and the logic part. In other words, we will discover what is the mechanism of communication between the typescript file and the HTML file inside the same component?
We can classify Data Binding in Angular into two categories :
- One Way Binding : One direction Communication ( HTML => Typescript file OR Typescript => HTML).
- Two Way Binding: Communication in two directions ( in the two directions HTML<=> Typescript ).
One Way Binding :
As described above, it will cover the one-way or one direction. We will start with Communication from the Typescript File to the HTML File.
Typescript => HTML
Interpolation : {{value}} :
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Ecommerce-Project';
}
<p>{{title}}</p>
Property Binding : [property] = “value”
<img alt="item" [src]="itemImageUrl">
and in the Typescript File we set the value of the variable “itemImageUrl” .
And now we will move to other way Binding from Template to Typescript
HTML => Typescript
Event Binding : (event ) = “methodName() “
<p *ngFor="let product of Products">
<span >
<ul>
<li>
{{product.label}}
</li>
<li>
{{product.Price}}
</li>
<span (click)="addToCard(product.id)">
Add to Card
</span>
</ul>
</span>
</p>
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core';
import { Subscription } from 'rxjs';
import { ProductService } from '../services/product.service';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit{
Products : Product [] = [] ;
constructor(private productService : ProductService ) { }
ngOnInit(): void {
//example of API call data
this.subscriptionProducts = this.productService.getProductList().subscribe((data)=>{
this.Products= data ;
})
}
addToCard(value: string) {
this.addProductToCard.emit(value);
}
}
Two-way Binding
The two-way binding is used in Angular with the help of ngModel .
[(ngModel)] = “value”:
Here in the HTML file, you will find the ngModel directive implementation, the value that takes the ngModel can be modified via the HTML input or by the typescript file.
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core';
import { Subscription } from 'rxjs';
import { ProductService } from '../services/product.service';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit,OnChanges,OnDestroy {
productFeedback : string ="" ;
constructor(private productService : ProductService ) { }
ngOnInit(): void {
//example of API call data
this.subscriptionProducts = this.productService.getProductList().subscribe((data)=>{
this.Products= data ;
})
}
ngOnChanges(changes: SimpleChanges): void {
console.log(changes)
}
changeDescription() {
console.log(this.productFeedback)
this.productFeedback = this.productFeedback + "!"
}
addToCard(value: string) {
this.addProductToCard.emit(value);
}
}
<p *ngFor="let product of Products">
<span >
<ul>
<li>
{{product.label}}
</li>
<li>
{{product.Price}}
</li>
<span (click)="addToCard(product.id)">
Add to Card
</span>
<input (keyup)="changeDescription()" placeholder="Describe the Product" [(ngModel)]="productFeedback" type="text" >
</ul>
</span>
</p>
Conclusion
So, we dedicated this post to going in-depth with Angular components. We covered the lifecycle of an Angular component. We focused on the most used hooks NgOninit, NgOnchanges, and NgOndestroy.
Then, we moved to the three methods that we use to share data between components: @Input() @Output, and passing data through Angular services.
Finally, we concentrated on a single component. In other words, we explained how HTML and Typescript exchange their data and information. we noticed four ways to do that : Interpolation, Property Binding, Event Binding, and Two-way Binding ( [(ngModel)] ).
We will be so honored if you read this post carefully and give us your feedback.