This article is intended for the beginner and intermediate Angular developers who are preparing for a job interview. Its purpose is to provide a helicopter view of all the options, rather than to be a detailed guide. I’ve tried to include some interesting talking points highlighted in bold that you might explore further in preparation for the interview.
The @Input()
decorator is probably one of the first things that comes to mind when it comes to passing data between components in Angular. It is a rather straightforward way to pass data from a parent component to its children.
@Input()
is particularly useful when the components are directly related to each other in the component tree. If they are not, you might have to resort to the so-called "prop drilling" — passing the same data through a chain of multiple components. The term itself comes from React, where props serve a similar function to Angular’s @Input().
Prop drilling is considered a bad practice and tends to become cumbersome and error-prone as the application grows larger.
Another common use case for @Input()
is building "stupid" or "representational" components that only handle displaying the data. In this case, the parent component is managing the state and updates its children via the @Input()
decorator.
parent.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'app-parent', template: ` <h2>Parent Component</h2> <app-child [childMessage]="parentMessage"></app-child> `, }) export class ParentComponent { parentMessage = "Message from Parent"; }
child.component.ts
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-child', template: ` <h4>Child Component</h4> {{ childMessage }} `, }) export class ChildComponent { @Input() childMessage: string; }
A polar opposite of @Input()
, the @Output()
decorator is used to emit events from a child component to its parent or any component that is situated higher in the component tree.
With @Output()
, the child component defines a custom event, which can be listened to by the parent or any other component that needs to respond to the event. The event is emitted when the child component needs to notify its parent that something has happened, such as a button click, a form submission, or a change in data.
It offers a good segue to talk about Event Driven Architecture (which is beyond the scope of this article, but it is something you might want to explore further).
child.component.ts
import { Component, EventEmitter, Output } from '@angular/core'; @Component({ selector: 'app-child', template: ` <button (click)="sendMessage()">Send Message</button> `, }) export class ChildComponent { @Output() messageEvent = new EventEmitter<string>(); sendMessage() { this.messageEvent.emit('Message from Child'); } }
parent.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'app-parent', template: ` <h2>Parent Component</h2> <app-child (messageEvent)="receiveMessage($event)"></app-child> <p>{{ message }}</p> `, }) export class ParentComponent { message: string; receiveMessage($event: string) { this.message = $event; } }
In this example, the child component defines a custom event called messageEvent
using the @Output()
decorator. When the sendMessage()
method is called, the messageEvent
event is emitted with the string value 'Message from Child'
.
The parent component listens to the messageEvent
event using the (messageEvent)
binding and handles it in the receiveMessage()
method. The $event
parameter is used to receive the data emitted by the child component.
When the components are further apart in the component tree, passing data around with @Input()
and @Output()
can quickly get out of hand and become too confusing and difficult to maintain. In these cases, using a shared service can provide a central location for managing and sharing data.
The RxJS library provides a Subject
class, which can be used in the service to manage the data and allow components to subscribe to changes to the data.
If you want an allegory, think about sharing the news. If you share rumors with your neighbors, you pass the information to one person and get some information from them. It’s akin to using @Input()
and @Output()
decorators in Angular. But if you instead write an article and send it to a newspaper, it will be broadcasted to a wide audience that doesn’t know you personally. That’s similar to using a service in Angular.
data.service.ts
import { Injectable } from '@angular/core'; import { Subject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class DataService { private data = new Subject<string>(); data$ = this.data.asObservable(); setData(data: string) { this.data.next(data); } }
sender.component.ts
import { Component } from '@angular/core'; import { DataService } from '../data.service'; @Component({ selector: 'app-sender', template: ` <h3>Sender Component</h3> <button (click)="sendData()">Send Data</button> `, }) export class SenderComponent { constructor(private dataService: DataService) {} sendData() { this.dataService.setData('Data from Sender Component'); } }
receiver.component.ts
import { Component, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; import { DataService } from '../data.service'; @Component({ selector: 'app-receiver', template: ` <h3>Receiver Component</h3> <p>{{ data }}</p> `, }) export class ReceiverComponent implements OnDestroy { data: string = ''; subscription: Subscription; constructor(private dataService: DataService) { this.subscription = this.dataService.data$.subscribe(data => { this.data = data; }); } ngOnDestroy() { this.subscription.unsubscribe(); } }
In this example, the SenderComponent injects the DataService and calls the setData() method to emit data through the data Subject. This data can be received by any other component that has subscribed to the data$ observable. The ReceiverComponent subscribes to the data$ observable of the DataService to receive updates whenever new data is emitted. The subscription property is used to store the subscription, which is unsubscribed in the ngOnDestroy() method to prevent memory leaks.
But shifting gear back to Angular, let’s talk about several things concerning the topic of navigation.
Using QueryParams to pass data is an powerful way to allow users to recreate the state of a page simply by sharing a link. It’s not an Angular specific feature, but the framework comes with the tools for handling it.
Query parameters are appended to the end of the URL, following the path, and they start with a question mark (?). Each parameter is a key-value pair, with the key and value separated by an equals sign (=). Multiple query parameters can be included in a single URL. They are separated by an ampersand (&). If you are new to QueryParams, it might be easier to understand the syntax by taking a look at the example URL at the end of this section.
Generally, if you know that your users might want to share links among themselves, using QueryParams is great for UX. Nothing really beats the feeling of sharing a link with a colleague and having them look at the exact same page when they click it.
The downside is that it may make links really long and unseemly, especially if they include some long IDs, which can be intimidating for both users and product owners. As a developer it can be easy to think “Who cares if the link is obscenely long, the main thing is that it does its job”, but don’t be surprised if your management tells you to make the links shorter (true story).
QueryParams can be used to store simple data types, such as strings or numbers, as well as more complex objects. When a user navigates to a URL that contains QueryParams, Angular automatically parses the QueryParams and makes them available in the component via the ActivatedRoute service.
Path definition
{ path: 'product', component: ProductDetailComponent, queryParams: { showReviews: 'true', } }
Accessing QueryParams
constructor(private route: ActivatedRoute) {} ngOnInit(): void { console.log(this.route.snapshot.queryParamMap.get('showReviews')) }
Resulting URL
https://www.example.com/?showReviews=true&greeting=Hello%20component
(Note how the whitespace was replaced with its encoded representation.)
Similar to QueryParams, Route Params offer another way to pass data to the component via a URL. The difference is, while QueryParams are used to pass optional parameters, Route Params are mandatory.
Using Route Params might also make your URLs more expressive and navigation more intuitive, since it builds a clear hierarchy that is reminiscent of disk directories.
Path definition
{ path: 'product/:id', component: ProductDetailComponent }
Accessing Route Params
constructor(private route: ActivatedRoute) {} ngOnInit(): void { this.route.params.subscribe(params => { console.log(params.id); }); }
Relevant Information:
In an era defined by digital transformation, businesses strive to stand out and reach their target audience in unique and engaging ways. One company at the forefront of this innovation is Angrio Technologies, an emerging tech powerhouse that's setting a new standard in the world of technology with its cutting-edge front-end solutions.
At Angrio Technologies, the team understands that a company's website is often the first touchpoint for potential customers. Angular, a powerful front-end framework, empowers Angrio's developers to create robust and responsive web applications that not only load quickly but provide a seamless and engaging user experience.
By creating robust web applications and cross-platform mobile apps, Angrio elevates technology performance and drives engagement to new heights. so visit now
More content at AngrioTechnologies.
Follow us on Twitter, LinkedIn
Follow me on LinkedIn