I am trying to pass an array to a child component. The array is being defined in the subscribe method of a parent component's onInit lifecycle hook.
Parent component:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'selector-parent',
templateUrl: 'parent.component.html'
})
export class ParentComponent implements OnInit {
array: [];
constructor() { }
ngOnInit() {
this.appDataService.getValues()
.subscribe(
value => {
value.item
.filter(loaded => !this.array.some(existing => existing.key === loaded.key))
.forEach(loaded => { this.array.push(loaded) })
}
)
}
}
Child component:
import { Component, OnInit } from '#angular/core';
#Component({
selector: '[selector-child]',
templateUrl: 'child.component.html'
})
export class ChildComponent implements OnInit {
#Input() inputArray: [];
constructor() { }
ngOnInit() {
console.log(this.inputArray)
}
}
Binding is: <tr selector-child [inputArray]="array"></tr>
Unfortunately, the console log on the inputArray is turning up undefined. I've tried using ngOnChanges in the child component but for some reason, it won't recognize the change in the parent. Thinking of using a service to pass the data if there isn't a simpler way to solve the problem.
You have to initialize the array in your parent component:
array: [] = [];
I don't see ngOnChanges in your child component. Also, make sure you do it as how it specified in the angular docs. https://angular.io/guide/component-interaction#intercept-input-property-changes-with-ngonchanges
Related
I have a problem with reloading my child component from parent component in Angular.
Here is an example of what I want to do.
This is my child component
import { Component } from "#angular/core";
#Component({
selector: "app-child",
template: `<p> {{ticks}} </p>`,
styleUrls: ["./child.component.css"]
})
export class ChildComponent {
ticks = Date.now().valueOf();
constructor() {}
update(): void {
this.ticks = Date.now().valueOf();
}
}
And here is my parent component:
import { Component, OnInit, ViewChild } from '#angular/core';
import { ChildComponent } from './../child/child.component';
#Component({
selector: 'app-parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.css'],
})
export class ParentComponent implements OnInit {
#ViewChild(ChildComponent, { static: false }) childC: ChildComponent;
showChild: boolean = true;
constructor() {}
ngOnInit() {}
onUpdateChild() {
this.childC.update();
}
}
Parent Component HTML :
<p>
parent works!
<button (click)="onUpdateChild()">update</button>
<app-child *ngIf="showChild"></app-child>
<app-child *ngIf="showChild"></app-child>
</p>
The main problem is that if I use my child component multiple time, and trying to click on “update” button, it just updates one of my child component that is used in parent component, but I want to update all same child component in parent component, here is what happens when you click on “update” button, only first value will change, not both.
How can I fix it ?!?
You can use #ViewChildren and QueryList to do what you are looking for.
#ViewChildren(ChildComponent) childrenC!: QueryList<ChildComponent>;
And your function would look like:
onUpdateChild() { //I would rename it to onUpdateChildren
if(this.childrenC) {
this.childrenC.forEach((childC: ChildComponent) => {
this.childC.update();
});
}
}
If you wanted your child component to update on it's own - as stated by a comment to your original post - this is an example of what you could do so you wouldn't even need the update button:
import { Component, OnInit } from "#angular/core";
#Component({
selector: "app-child",
template: `<p> {{ticks}} </p>`,
styleUrls: ["./child.component.css"]
})
export class ChildComponent implements OnInit {
ticks = Date.now().valueOf();
constructor() {}
ngOnInit(): void {
setInterval(() => {
this.ticks = Date.now().valueOf();
}, 1000); //updates every 1 second
}
}
Another option you can do without using a button is to use #Input with the ticks property that updates the value from the parent through an input.
I've implemented a child component to render a table based on a list provided via #Input(). The data is loaded via http, however the UI (child component) is not updated unless I wave my mouse over the screen. I've seen people post about implementing ngOnChanges() in my child, but I thought Angular was supposed to do this by default? Am I missing something? Why would the UI not update with this?
Child code looks something like this:
child.component.ts
#Component({
selector: 'child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.scss'],
})
export class ChildComponent implements {
#Input() data: any[] = [];
constructor() {}
}
child.component.html
<table>
<tr *ngFor="let item of data"><td>{{ item }}</td></tr>
</table>
Parent code that uses the component looks something like this:
parent.component.ts
#Component({
selector: 'parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.scss'],
})
export class ParentComponent implements OnInit {
data: string[] = [];
constructor(private endpointService: EndpointService) {}
ngOnInit() {
// response is a string array like: ['hello', 'world']
this.endpointService.loadData().subscribe((response) => {
this.data = response;
});
}
}
parent.component.html
<child [data]="data"></child>
============================= EDIT ==================================
I verified that it only fails to load when updating inside of the subscribe callback (if I set a static array, it loads just fine).
So it looks like I'm able to resolve this by running changeDetectorRef.detectChanges() in the parent component, but this feels hackish like I shouldn't have to do this. Is this a good way to resolve this? Or does this indicate something wrong with my implementation?
parent.component.ts
#Component({
selector: 'parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.scss'],
})
export class ParentComponent implements OnInit {
data: string[] = [];
constructor(private endpointService: EndpointService,
private changeDetectorRef: ChangeDetectorRef) {}
ngOnInit() {
// response is a string array like: ['hello', 'world']
this.endpointService.loadData().subscribe((response) => {
this.data = response;
this.changeDetectorRef.detectChanges();
});
}
}
You can also try to force change detection by forcing the value reference update via, for example, the spread operator:
this.endpointService.loadData().subscribe((response) => {
this.data = [...response];
});
hummm well .. when the component is rendered as first time it will show with the empty array becouse the api call stills happening and needs the onchanges method in child component in order to listen the complete api call and the list will re render
Seems that you have some other errors in template expressions which force the whole template to fail. Here's a stackblitz I've created and everything works: https://stackblitz.com/edit/angular-ivy-w2ptbb?file=src%2Fapp%2Fhello.component.ts
Do you have maybe some errors in console?
I replaced the service with a static string array and it worked well. I think there is problem with the observable subscription.
child.component.ts
import { Component, OnInit,Input } from '#angular/core';
#Component({
selector: 'child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() data: any[] = [];
constructor() { }
ngOnInit() {
}
}
parent.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.css'],
})
export class ParentComponent implements OnInit {
data: string[] = [];
constructor() {}
ngOnInit() {
this.data = ['hello', 'world','aaa','ddd'];
}
}
I defined a property here in my function
evs: string
...
openArticle(url){
this.evs = url
console.log(this.evs)
this.navCtrl.navigateForward('/url-page')
}
And I a trying to pass the value of 'this.evs' to another ts file and use its value but I do not know how to do this. I tried exporting it like this.
export const webpage = this.evs
but this.evs has no value until someone performs the openArticle function ad so I keep getting the error. "Cannot read property 'evs' of undefined"
What i need to do is tranfer the variable to the 'url-page' page and use the value of this.evs only after the openArticle function has bee called. How do I go about this?
As per my understanding you are trying to share data between two components.
So choose one of them as per your requirements.
Parent to Child: Sharing Data via Input().
Child to Parent: Sharing Data via Output() and EventEmitter.
Unrelated Components: Sharing Data with a Service.
This link will be helpful.
If the components have a parent/child relationship, You can share data between them via #Inpput() and #Output() decorators.
Sharing data from Parent to Child using #Input() :
<h3>Parent Component</h3>
<label>Parent Component</label>c
<input type="number" [(ngModel)]='parentValue'/>
<p>Value of child component is: </p>
<app-child [value]='parentValue'></app-child>
And in the child component, the 'parentValue' can be received as :
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() value: number;
constructor() { }
ngOnInit() {
}
}
Now, in the case of sending data from Child to Parent, we can use an #Output() event emitter. So the parent would have a function to receive the emitted data from child as :
parent-app.component.html
<app-child [value]="parentValue" (childEvent)="childEvent($event)"></app-child>
parent-app.component.ts
childEvent(event) {
console.log(event);
}
And, the child.component.ts would look like :
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() PData: number;
#Output() childEvent = new EventEmitter();
constructor() { }
onChange(value) {
this.childEvent.emit(value);
}
ngOnInit() {
}
}
If the components do not have a parent/child relationship, a shared service can be used, say, SharedService which has a BehavioralSubject, that emits value from either component, and the other component can then catch the changed value.
Eg:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
#Injectable()
export class SharedService {
comp1Val: string;
_comp1ValueBS = new BehaviorSubject<string>('');
comp2Val: string;
_comp2ValueBS = new BehaviorSubject<string>('');
constructor() {
this.comp1Val;
this.comp2Val;
this._comp1ValueBS.next(this.comp1Val);
this._comp2ValueBS.next(this.comp2Val);
}
updateComp1Val(val) {
this.comp1Val = val;
this._comp1ValueBS.next(this.comp1Val);
}
updateComp2Val(val) {
this.comp2Val = val;
this._comp2ValueBS.next(this.comp2Val);
}
And component1 as follows :
import { Injectable } from '#angular/core';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
#Injectable()
export class SharedService {
comp1Val: string;
_comp1ValueBS = new BehaviorSubject<string>('');
comp2Val: string;
_comp2ValueBS = new BehaviorSubject<string>('');
constructor() {
this.comp1Val;
this.comp2Val;
this._comp1ValueBS.next(this.comp1Val);
this._comp2ValueBS.next(this.comp2Val);
}
updateComp1Val(val) {
this.comp1Val = val;
this._comp1ValueBS.next(this.comp1Val);
}
updateComp2Val(val) {
this.comp2Val = val;
this._comp2ValueBS.next(this.comp2Val);
}
Component 2 :
import { Component, AfterContentChecked } from '#angular/core';
import { SharedService } from "../../common/shared.service";
#Component({
selector: 'app-component2',
templateUrl: './component2.component.html',
styleUrls: ['./component2.component.css']
})
export class Component2Component implements AfterContentChecked {
comp1Val: string;
comp2Val: string;
constructor(private sharedService: SharedService) {
this.sharedService.comp2Val = "Component 2 initial value";
}
ngAfterContentChecked() {
this.comp1Val = this.sharedService.comp1Val;
}
addValue(str) {
this.sharedService.updateComp2Val(str);
}
}
You can find more on different types of subjects here
I have the following scenario in my Angular app:
A component MainDashboardComponent that is visible when I have the route /. Obviously I have the <router-outlet> tag in my app.component.html file, which looks like this:
<app-side-menu></app-side-menu>
<div class="main-container">
<div class="content">
<router-outlet></router-outlet>
</div>
</div>
As you can see I have a SideMenuComponent I use to have a side menu on all my routes. In MainDashboardComponent I have a method that for some reason needs to toggle a chat element that is situated on the side menu.
Inside the SideMenuComponent I have a method that handles the visibility toggle for the chat element and it works as expected. How can I call this method from my MainDashboardComponent and toggle the chat element from there?
What I tried with no success
I tried to inject the SideMenuComponent inside my MainDashboardComponent but, though the method toggleChat() is called, the element doesn't change it's visibility. Looks like I have a kind of multiple instance of the same component I guess...
Can you please help me with this? Thank you!
MainDashboardComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-main-dashboard',
templateUrl: './main-dashboard.component.html',
styleUrls: ['./main-dashboard.component.scss']
})
export class MainDashboardComponent implements OnInit {
constructor() { }
ngOnInit() {}
setFocus(id) {
// here I'd like to call SideMenuComponent togglechat() ...
}
}
SideMenuComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss']
})
export class SideMenuComponent implements OnInit {
showChat: boolean;
constructor() {
this.showChat = false;
}
ngOnInit() {
}
toggleChat() {
this.showChat = !this.showChat;
}
}
To communicate between different components, there are different ways.
If you want to communicate between parent and child component, you can use EventEmitter to emit event from child component and handle the event in your parent component
If you want to communicate between any components, you can use Service and implement communication with the help of EventEmitter or Subject/BehaviorSubject
In your case, we can create a service, myService.ts and declare and eventEmitter
.service.ts
#Injectable()
export class AppCommonService {
toggle : EventEmitter<boolean> = new EventEmitter<boolean>()
}
mainDashboard.component.ts
constructor(private myService : myService){}
chatStatus : boolean = false;
ngOnInit(){
this.myService.toggle.subscribe(status=>this.chatStatus = status);
}
toggleChat(){
this.myService.toggle.emit(!this.chatStatus);
}
sideMenu.component.ts
constructor(private myService : myService){}
chatStatus : boolean = false;
ngOnInit(){
this.myService.toggle.subscribe(status=>this.chatStatus = status);
}
Generally this is the domain of a service!
Just create a service and add the "showCat" property.
Inject the service into both components
Alter SideMenuComponent to:
toggleChat() {
this.myService.showChat = !this.myService.showChat;
}
Alter MainDashboardComponent, also use this.myService.showChat to show / hide your chat window
Service TS
#Injectable()
export class MyService{
showCat:boolean = true
}
MainDashboardComponent
toggleChat() {
this.myService.showChat = !this.myService.showChat;
}
SideMenuComponent
chatVisiblity = this.myService.showCat //<-- bind this to the element attribute
You could efficiently use child to parent communication in this scenario. You'll need to create a custom event using angular's EventEmitter in your SideMenuComponent and use it in your MainDashboardComponent.
So, here is some code that may help you -
// SideMenuComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss']
})
export class SideMenuComponent implements OnInit {
#Output() valueChange = new EventEmitter();
showChat: boolean;
constructor() {
this.showChat = false;
}
ngOnInit() {
}
toggleChat() {
this.showChat = !this.showChat;
this.valueChange.emit(this.showChat);
}
}
// MainDashboardComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-main-dashboard',
template: `<app-side-menu (valueChange)='setFocus($event)'></app-side-menu>`
styleUrls: ['./main-dashboard.component.scss']
})
export class MainDashboardComponent implements OnInit {
constructor() { }
ngOnInit() { }
setFocus(event) {
// check for required input value
console.log(event);
}
}
Refer these tutorials if required -
https://dzone.com/articles/understanding-output-and-eventemitter-in-angular,
https://angular-2-training-book.rangle.io/handout/components/app_structure/responding_to_component_events.html
I've html structure like this :
Parent Component where both Comp1 and Comp2 resides :
now in comp1 I've some elements if that changes then I've to reflect values in comp2 but there's no connection between them.
Comp1 :
import { Component } from '#angular/core';
import { Comp2Component } from 'comp2.component';
#Component({
selector: 'comp1',
templateUrl: './comp1.html'
})
export class Comp1Component {
sortBy(value)
{
this.FeedSortBy = value;
this.SortByLabel = this.SortByLabelList[value];
Comp2Component.filterActivities();
}
}
Comp2
import { Component } from '#angular/core';
#Component({
selector: 'comp2',
templateUrl: './comp2.html'
})
export class Comp2Component {
filterActivities()
{
//call this function on comp1 sort function call so it will update value of comp2
}
}
As per Rahul and Sajit's answer I try using with EventEmitter and change my structure to parent child :
In my parent component I use :
import { Component,EventEmitter, Input, Output, OnInit } from '#angular/core';
import { FeedsComponent } from '../feeds/feeds.component';
#Component({
selector: 'my-desk',
styleUrls: ['../../assets/css/style.min.css'],
templateUrl: './my-desk.component.html'
})
export class MyDeskComponent {
#Output() FeedSortBy = new EventEmitter<string>();
sortBy(value)
{
this.FeedSortBy.emit(value);
}
}
and in my child component I use :
import { Component, OnInit, Input, Output } from '#angular/core';
import { DataService } from '../data.service';
declare var $: any;
#Component({
selector: 'feeds',
styleUrls: ['../../assets/css/style.min.css'],
templateUrl: './feeds.component.html'
})
export class FeedsComponent {
constructor(private dataService:DataService)
{
}
#Input() FeedSortBy:number = 2;
}
Child component HTML :
{{FeedSortBy}}
But it always output 2 it doesn't change can I get any trigger as well to know if value is change so I call function there
You cannot do that, There are two possible ways you could achieve this,
use angular service to pass the data between two components
use Event Emitters to pass the value among the components.
You can call method of another component from a different component but it will not update the value of the calling component without some tweaking like
Event Emitters if they have a parent child relationship or Shared Services or using ngrx redux pattern
How to Call a different component method be like
Component1
test(){
console.log("Test");
}
Component 2
working(){
let component = new Component1();
component.test();
}
Now to update the value in component 2 you might have to use any of the above.
For Event Emitters follow this link
For Shared services follow this link
For ngrx follow this link