73

I have a MasterComponent which loads header, footer, sidebar etc. On the header there is a dropdown whose options are set once the user is logged in. I want the header to be constant even if I navigate to different routes which loads different child component. Means that the selected option should not change and value of dropdown should be accessible in all the child component. Onchange of dropdown value, the current child component should be updated/reloaded.

How should I approach this problem? I want to have event-listener kind of functionality. Once the model from MasterComponent Changes, reload the current child component. On MasterComponent's variable update, ChildComponent will listen to the update and run some function or call some API again and reload the ChildComponent.

// routes
const appRoutes: Routes = [
    {
        path: '',
        redirectTo: 'login',
        pathMatch: 'full',
    },
    {   path: 'login', component: LoginComponent },
    {   path: 'logout', component: LogoutComponent },
    {
        path: '',
        component: MasterComponent,
        canActivate: [AuthGuard],
        children: [
            { path: 'record/create', component: RecordCreateComponent }, // create record for selectedRestaurant in MasterComponent
            { path: 'record/', component: RecordComponent }, // shows all record of current selectedRestaurant in MasterComponent
            { path: 'record/:id/update', component:RecordUpdateComponent }, // show form to edit record having id
            { path: 'record/:id', component: RecordComponent }, // show record details having id
        ]
    },
    { path: '**', redirectTo: 'login' }
];
// MasterComponent
@Component({
    selector: 'master',
    templateUrl: templateUrl,
    styleUrls:[styleUrl1]

})
export class MasterComponent implements AfterViewInit, OnInit{
    restaurants: Array<Restaurant> = [];
    user:User;
    selectedRestaurant: Restaurant;

    constructor(private router: Router, private storageHelper:StorageHelper){
    }
    ngAfterViewInit() {
    }
    ngOnInit(){
        this.user = JSON.parse(this.storageHelper.getItem('user'));
        this.restaurants = this.user.restaurants;
        this.selectedRestaurant = this.restaurants[0];
        this.router.navigate(['/record/' + this.selectedRestaurant.id]);
    }
    onRestaurantChange(){
        this.router.navigate(['/record/' + this.selectedRestaurant.id]);
    }
    createRecord(){
    }
}

enter image description here

2
  • 1
    The question's topic is misleading "Change child component without reloading parent component." You just need to set your router properly. It seems to not be a parent <-> children problem. Commented Oct 26, 2016 at 8:13
  • 1
    @jmachnik I want to access parent variables from child component and vice versa. And Parent components value should be consistent across all the routes. Commented Oct 26, 2016 at 8:23

7 Answers 7

74

Use @Input to pass your data to child components and then use ngOnChanges (https://angular.io/api/core/OnChanges) to see if that @Input changed on the fly.

Sign up to request clarification or add additional context in comments.

5 Comments

Does this work When I send the same value to the input??
@AbhishekEkaanth not sure, check it. But if it based on setters, I think "yes", but if you set it manually through reference, should not happen in template.
it is not based on setters.but i changed the value to zero and passed the right value sill it did not work
@AbhishekEkaanth then you do something wrong .. check examples, simplify your component and debug.
This interface OnChanges is completely BEAUTIFUL. thanks mate
36

update of @Vladimir Tolstikov's answer

Create a Child Component that use ngOnChanges.

ChildComponent.ts::

import { Component, OnChanges, Input } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'child',
  templateUrl: 'child.component.html',
})

export class ChildComponent implements OnChanges {
  @Input() child_id;

  constructor(private route: ActivatedRoute) { }

  ngOnChanges() {
    // create header using child_id
    console.log(this.child_id);
  }
}

now use it in MasterComponent's template and pass data to ChildComponent like:

<child [child_id]="child_id"></child>

2 Comments

Nice example, but now you have to pass as arguments of the method ngOnChanges(changes: SimpleChanges), than get the data like that changes['YOUR-PARAMETER-HERE'].currentValue. "YOUR-PARAMETER-HERE" should be the same text of your @Input parameter at the child component
Thanks for the example. Was not sure how to proceed after the confirmed answer. Called my formGroup initialization in this function and it worked!
15

On Angular to update a component including its template, there is a straight forward solution to this, having an @Input property on your ChildComponent and add to your @Component decorator changeDetection: ChangeDetectionStrategy.OnPush as follows:

import { ChangeDetectionStrategy } from '@angular/core';

@Component({
    selector: 'master',
    templateUrl: templateUrl,
    styleUrls:[styleUrl1],
    changeDetection: ChangeDetectionStrategy.OnPush    
})

export class ChildComponent{
  @Input() data: MyData;
}

This will do all the work of check if Input data have changed and re-render the component

4 Comments

Its working fine. but if want to reload the child component how to do that, i have to recall the ngOnInit method?
From the component using the ChangeDetectionStrategy.OnPush , all childs dependant on @Input() will update
@CarlosE you are just deactivating change detection. How is that supposed to reload the child component? Afaik the only working solution is to manually update the child component by implementing onChanges.
it didn't work for me even with the OnChanges. It is rendering just once
7

In case, when we have no control over child component, like a 3rd party library component.

We can use *ngIf and setTimeout to reset the child component from parent without making any change in child component.

.template:

<child-component *ngIf="show"></child-component>
<button (click)="resetChildForm()"></button>

.ts:

show:boolean = true

resetChildForm(){
   this.show = false;

   setTimeout(() => {
      this.show = true
    }, 100);
}

Comments

2

You can use @input with ngOnChanges, to see the changes when it happened.

reference: https://angular.io/api/core/OnChanges

(or)

If you want to pass data between multiple component or routes then go with Rxjs way.

Service.ts

import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class MessageService {
  private subject = new Subject<any>();

  sendMessage(message: string) {
    this.subject.next({ text: message });
  }

  clearMessages() {
    this.subject.next();
  }

  getMessage(): Observable<any> {
    return this.subject.asObservable();
  }
}

Component.ts

import { Component, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';

import { MessageService } from './_services/index';

@Component({
  selector: 'app',
  templateUrl: 'app.component.html'
})

export class AppComponent implements OnDestroy {
  messages: any[] = [];
  subscription: Subscription;

  constructor(private messageService: MessageService) {
    // subscribe to home component messages
    this.subscription = this.messageService.getMessage().subscribe(message => {
      if (message) {
        this.messages.push(message);
      } else {
        // clear messages when empty message received
        this.messages = [];
      }
    });
  }

  ngOnDestroy() {
    // unsubscribe to ensure no memory leaks
    this.subscription.unsubscribe();
  }
}

Reference: http://jasonwatmore.com/post/2019/02/07/angular-7-communicating-between-components-with-observable-subject

Comments

1

To trigger LIST/ARRAY ngOnChanges use this.arr = [].concat(this.arr);

Comments

0

In Angular 18, you can use Signals to transfer data between components, allowing you to get the latest data in any component without needing to refresh the page. By utilizing the effect function, components will automatically react to changes in the Signal, ensuring that they always display the current data.

Service

import { Injectable, signal } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  // Create a Signal to hold the data
  private dataSignal = signal<string>('Initial Data');

  // Method to get the current value of the Signal
  getData() {
    return this.dataSignal;
  }

  // Method to update the Signal
  updateData(newData: string) {
    this.dataSignal.set(newData);
  }
}

Inside component using effect method

import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
import { effect } from '@angular/core';

@Component({
  selector: 'app-example',
  template: `
    <div>
      <h1>Signal Example</h1>
      <p>Current Data: {{ currentData }}</p>
      <input [(ngModel)]="newData" placeholder="Enter new data" />
      <button (click)="updateData()">Update Data</button>
    </div>
  `,
  styles: []
})
export class ExampleComponent implements OnInit {
  currentData: string = '';
  newData: string = '';

  constructor(private dataService: DataService) {}

  ngOnInit(): void {
    // Use effect to reactively update currentData when the Signal changes
    effect(() => {
      this.currentData = this.dataService.getData()();
    });
  }

  updateData() {
    this.dataService.updateData(this.newData);
    this.newData = ''; // Clear input after updating
  }
}

For more details

https://angular.dev/guide/signals

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.