In this blog post, we will discuss a common issue that developers face when working with Angular components: the component not updating when an input value changes. We will dive deep into the problem, explore the code causing the issue, and offer a solution to ensure your Angular components render correctly.
The Problem: Component Not Updating
The issue we are tackling revolves around an Angular component that takes a value from an object. The outer div is subscribed to a BehaviorSubject, and when the BehaviorSubject’s value is updated using next()
, the container div’s title updates correctly. However, the inner component does not re-render as expected. Let’s take a closer look at the code causing this issue.
BehaviorSubject in Controller
ngOnInit() { this.selectedRole$.next({ name: 'Current Permissions', permissions: this.getUserPermissions() } showRoleDetails(role: any): void { this.selectedRole$.next(role); }
Parent Component HTML
<div *ngIf="selectedRole$ | async as selectedRole" class="col-8"> <h5 class="font-weight-medium mb-0 text-gray-500 mb-4 permissions-title">{{ selectedRole.name }}</h5> <div class="section-container"> <c-permissions-display [permissions]="selectedRole.permissions"></c-permissions-display> </div> </div>
Child Component (c-permissions-display)
import { Component, OnInit, Input } from '@angular/core'; import { UtilityService } from 'src/app/canopy-common/services/utility.service'; @Component({ selector: 'c-permissions-display', templateUrl: './permissions-display.component.html', styleUrls: ['./permissions-display.component.scss'] }) export class PermissionsDisplayComponent implements OnInit { @Input() permissions: any[]; formattedPermissions: any[]; constructor( private util: UtilityService ) { } ngOnInit() { this.formattedPermissions = this.formatPermissionsArray(); } private formatPermissionsArray(): any { return stuff } }
If a console log is placed in the ngOnInit, it calls once when the page loads but never again after calling next()
on the BehaviorSubject. We can get this to work if we implement OnChanges and the ngOnChanges() method, but it should update and re-render on its own if its input value changes. So, how do we solve this issue?
The Solution: Pass Observable to Child Component
Instead of passing the value directly to the child component, we can pass the observable to the child component and subscribe to it inside the child component. This way, whenever the value changes in the parent component, the child component gets notified and updates accordingly.
Parent Component HTML
<div *ngIf="selectedRole$ | async as selectedRole" class="col-8"> <h5 class="font-weight-medium mb-0 text-gray-500 mb-4 permissions-title">{{ selectedRole.name }}</h5> <div class="section-container"> <c-permissions-display [permissions]="selectedRole$"></c-permissions-display> </div> </div>
Child Component (c-permissions-display)
import { Component, OnInit, Input } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { UtilityService } from 'src/app/canopy-common/services/utility.service'; @Component({ selector: 'c-permissions-display', templateUrl: './permissions-display.component.html', styleUrls: ['./permissions-display.component.scss'] }) export class PermissionsDisplayComponent implements OnInit { @Input() permissions: BehaviorSubject = new BehaviorSubject(null); formattedPermissions: any[]; constructor( private util: UtilityService ) { } ngOnInit() { this.permissions.subscribe(permission => { this.formattedPermissions = this.formatPermissionsArray(); }); } private formatPermissionsArray(): any { return stuff } }
By using this approach, we ensure that the child component subscribes to the BehaviorSubject and updates its content whenever the BehaviorSubject’s value changes in the parent component. This eliminates the need for implementing OnChanges and ngOnChanges() methods, making your Angular components more efficient and easier to manage.
Wrapping Up
In this blog post, we have explored a common issue that developers face when working with Angular components: components not updating when input values change. By passing the observable to the child component and subscribing to it inside the child component, we can ensure that the child component is updated whenever the parent component’s value changes. This solution makes your Angular components more efficient and easier to maintain, ultimately leading to a better overall development experience.