Update parent component property from child component in Angular 2
Asked Answered
A

4

95

I'm using @input to receive a property from parent component in order to activate a CSS class in one of child component's element.

I'm able to receive the property from parent and also activate the class. But this works only once. The property i'm receiving from parent is a boolean data typed and when I set the status of it to false from child component, it does not change in parent.

Plunkr: https://plnkr.co/edit/58xuZ1uzvToPhPtOING2?p=preview

app.ts

import {Component, NgModule} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { HeaderComponent } from './header';
import { SearchComponent } from './header/search';

@Component({
  selector: 'my-app',
  template: `
    <app-header></app-header>
  `,
})
export class App {
  name:string;
  constructor() {
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, HeaderComponent, SearchComponent ],
  bootstrap: [ App ]
})
export class AppModule {}

header.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-header',
  template: `<header>
              <app-search [getSearchStatus]="isSearchActive"></app-search>
              <button (click)="handleSearch()">Open Search</button>
            </header>`
})
export class HeaderComponent implements OnInit {
  isSearchActive = false;

  handleSearch() {
    this.isSearchActive = true
    console.log(this.isSearchActive)
  }

  constructor() { }
  ngOnInit() { }
}

header/search.ts

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-search',
  template: `<div id="search" [class.toggled]="getSearchStatus">
              search 
              <button  (click)="getSearchStatus = false" class="close">Close Search</button>
            </div>`
})
export class SearchComponent implements OnInit {
  @Input() getSearchStatus: boolean;

  constructor() { }

  ngOnInit() {

  }
}

Please check the above given plunker. The open search function works only once. After closing the search, it does not trigger again.

Is @input is the proper use case for this scenario? Please help me fix this. (Please update the plunker).

Absorbed answered 4/1, 2017 at 13:22 Comment(2)
Where do you set ` this.isSearchActive = false;` after it was set to true in handleSearch()?Bracing
in header/search.ts '<button (click)="getSearchStatus = false" class="close">'Absorbed
K
171

You need to use 2 way data-binding.

@Input() is one way data-binding. to enable 2 way data-binding you need to add an @Output() corresponding to the property, with a "Change" suffix

@Input() getSearchStatus: boolean;
@Output() getSearchStatusChange = new EventEmitter<boolean>();

when you want to publish the change made to your property to the parent, you need to notify the parent with:

this.getSearchStatusChange.emit(newValue)

and in the parent you need to use the banana-in-a-box notation for that property:

[(getSearchStatus)]="myBoundProperty"

you can also bind to the property and trigger a callback when it changes in child:

[getSearchStatus]="myBoundProperty" (getSearchStatusChange)="myCrazyCallback($event)"

see the plnkr

Kory answered 4/1, 2017 at 13:29 Comment(11)
Thank you for your reply. Can you please update my plunker?Absorbed
Thank you again. Is this the only way to handle this scenario? Is there any other ways without using input/output combo?Absorbed
That's the recommended way, the one which is used for [(ngModel)]Kory
there are other ways (like injecting parent into child) but they are useful only when you have a child component that couldn't leave without its parent (I think about a custom select/option combo, for example).Kory
What about using subscribing to subject instead and calling subject.next(value) ?Nguyetni
That will add extra work for the same behavior as using @Output(). That sounds like reinveting the wheel. @RobertVangorKory
When using ReactiveForms, even with my child property marked public, I could not use the "banana box" approach & got errors that it wasn't a known property of the component ... Binding only to the (propertyChanged)="setParentProp($event)" worked perfectly though.Propitiatory
@n00dl3, the above way of updating parent properties causes the below console error: "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked". Any inputs on how to fix that?Mcinerney
I can't answer to your question as it would deserve its own post. Your problem is that you are emitting change right after parent's change detection cycle. In other words you have managed to make change detection to trigger some changes which is not allowed by Angular. @manojKory
Couple of question => 1. is "Change" is mandatory ? 2. I mean Can't I use other suffix? 3. cant I write name like "get_Search_Status_Change" this?Esophagus
@Esophagus nope, to use banana box syntax you have to use change suffix. For custom outputs, you can name it whatever you want. Banana box is just a very stupid syntax sugar which many people misusePentane
C
10

Another approach: use rxjs/BehaviorSubject to pass status between different components.
Here's the plunkr.
I name subject with a suffix 'Rxx', so the BehaviorSubject for searchStatus will be searchStatusRxx.

  1. initialize it in parent component like searchStatusRxx = new BehaviorSubject(false);,
  2. pass it to child component using @Input
  3. in child template, you do async pipe.
  4. in both parent and child, you do searchStatusRxx.next(value) to change the latest value.
Celina answered 4/1, 2017 at 14:0 Comment(0)
K
4

Edited your code a little bit, it works and looks simplier imo. Tell me if you like it.

https://plnkr.co/edit/oJOjEZfAfx8iKZmzB3NY?p=preview

Kiley answered 4/1, 2017 at 13:43 Comment(2)
Thank you. i want to update the parent property from search component. That's my use case.Absorbed
@Absorbed Fine, but you should consider using the way I've modified your buttons and clicking mechanism, together with n00dl3 solution.Kiley
C
2

Yet another way. Plunkr. What we want is a single source of truth. We can put that in child this time.

  • Init in child: searchStatus = false
  • In parent template, get the instance of child as #as or whatever name.
  • Change searchStatus in parent using #as.searchStatus and in child this.searchStatus.
Celina answered 4/1, 2017 at 14:19 Comment(3)
This way is much better and simpler. Is this a valid way to proceed? Why Angular not promoting this method? I'm searching for a solution since a week and everywhere i found input/output cases.Absorbed
Let's call this as the "Template reference variables" way. It seems to be simple. But it may be a little bit harder to test and the parent/child components are coupling together which may not be a good thing. Personally, I prefer the BehaviorSubject way, which is easier to test and decouple and can be set as a property of complex object passing between parent and child. A sidenote: angular's eventEmitter is a Subject behind the scenes.Celina
The example above on Plunkr doesn't include a sample child component...Bushwa

© 2022 - 2024 — McMap. All rights reserved.