Accessing child component's method when nested within another component - javascript

I'd like to be able to access the SearchResults component, (when it has been clicked), in the root component (AppComponent) as I'm looking to set different properties on the SearchResults component such as;
I'd like to set an attribute on the SearchResults component so that it shows the "close" text
Also, I'd to set the click event on the SearchResults to redirect elsewhere or actually enable it as a multi-select so that it stays selected until a user proceeds to the next step for example.
I'm trying to make the SearchResults and SearchResult components as re-usable as possible so we're able to state in the parent component which would include the <app-searchresults> selector what action we'd like our SearchResults components to actually be when they are clicked.
The only way I can really see doing this is using EventEmitter to pass the event up once through the SearchResult component then onto the parent component and then a Service to hold selected values but I'm still stuck around enabling the SearchResults component as either a component which redirects when clicked or stays selected? Is this actually possible or do I need to create a different SearchResults component for each different state I'd like?!
export class AppComponent {
#ViewChildren(SearchresultComponent) components: QueryList<SearchresultComponent>;
name = 'Angular';
ngAfterViewInit() {
this.components.changes.subscribe((r) => { console.log(r) });
}
}
SearchResults.ts
#Component({
selector: 'app-searchresults',
templateUrl: './searchresults.component.html',
styleUrls: ['./searchresults.component.css']
})
export class SearchresultsComponent implements OnInit {
#ViewChildren(SearchresultComponent) components: QueryList<SearchresultComponent>;
constructor() { }
ngOnInit() {
}
}
SearchResults.html
<h1>Search Results<h1>
<app-searchresult result ="first"></app-searchresult>
<app-searchresult result ="second"></app-searchresult>
<app-searchresult result ="third"></app-searchresult>
SearchResult.ts
#Component({
selector: 'app-searchresult',
templateUrl: './searchresult.component.html',
styleUrls: ['./searchresult.component.css']
})
export class SearchresultComponent implements OnInit {
#Input()
result: string;
isSelected: boolean;
constructor() { }
ngOnInit() {
}
toggleClickedState(){
if(!this.isSelected){
this.isSelected = !this.isSelected;
}
}
}
SearchResult.html
<div>
<p (click)=toggleClickedState() [ngClass]="isSelected? 'selected' : '' "> Search Result : {{result}}</p>
<p *ngIf="isSelected" class="cross" (click)="isSelected = false;">close</p>
<div>
I've included a link to structure of an app that references the above;
https://stackblitz.com/edit/angular-cjhovx

Related

Reloading multiple child component from parent in Angular 14

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.

How to pass data from child component to parent component when button clicked on parent component

I need to pass input's value from child component to parent component when user click on a submit button that exists in parent component.
childComp template
<input
type="password"
[(ngModel)]="userPasswordForm.inputId"
class="mr-password-field k-textbox"
/>
childComp TS file
export class PasswordInputComponent{
constructor() { }
#Output() inputValue = new EventEmitter<string>();
userPasswordForm:any={'input':''};
emitValue(value: string) {
this.inputValue.emit(value);
}
}
Parent Component Template
<child-component (inputValue)="" > </child-component>
<button (click)="getValueFromChild()"> </button>
Parent Component TS file
tempUserFormPasswords:any=[];
.
.
.
getValueFromChild(receivedVal){
this.tempUserFormPasswords.push(receivedVal);
}
It would easy to dio it if the button exists inside the child component. but in this case the value should be passed when the button in the parent component is clicked!
For single ChildComponent:
Use ViewChild
For multiple ChildComponent use: ViewChildren
Parent Component TS file
Single Child Component:
tempUserFormPasswords:any=[];
#ViewChild(ChildComponent) child: ChildComponent;
.
.
.
getValueFromChild(receivedVal){
var data = child.getData();
this.tempUserFormPasswords.push(data);
}
Multiple Child Component:
tempUserFormPasswords:any=[];
#ViewChildren(ChildComponent) child: ChildComponent;
#ViewChildren(ChildComponent) children: QueryList<ChildComponent>;
.
.
.
getValueFromChild(receivedVal){
let data;
children.forEach(child => (data = this.updateData(child.data));
this.tempUserFormPasswords.push(data);
}
Create a BehaviorSubject in service file
#Injectable()
export class dataService {
data: BehaviorSubject<any> = new BehaviorSubject<any>(null);
public setData(data: any){
this.data.next(data);
}
public getData(): Observable<any> {
return this.data.asObservable();
}
}
You need to subscribe the data in your child component
PasswordInputComponent
export class PasswordInputComponent{
constructor(private service: dataService) {
this.service.getData().subscribe((data) => {
//Emit the event here
this.inputValue.emit(value);
});
}
#Output() inputValue = new EventEmitter<string>();
userPasswordForm:any={'input':''};
emitValue(value: string) {
this.inputValue.emit(value);
}
}
ParentComponent.ts
tempUserFormPasswords:any=[];
.
.
.
constructor(private service: dataService) { }
getValueFromChild(receivedVal){
this.service.setData('');
this.tempUserFormPasswords.push(receivedVal);
}
When a button clicked on the parent component we are setting the data behaviour subject, when a new value added to that it will automatically subscribed in child component.so, on that time we need to emit a event.
I think this will help you..
Read about Input and Output decorators in angular!
documentation: sharing-data.
Examples: examples
You can do it with ViewChild as already said in the other answer from #Raz Ronen. But keep in mind that depending on the Angular version, you might need to wait for the AfterViewInit lifecycle hook to be executed to interact with the child (or the child won't be available since it's not initialized).
Also, you can do it with a BehaviorSubject, like #Msk Satheesh just answered, and it's perfectly fine too. But it might be considered a bit overkill for such a simple use case.
(this is what we usually do when we don't have a relation between the components e.g one component is not children of the other one)
What I suggest is I think the simplest of all (again, their answers are not bad by any means);
It is basically the same of #Msk Satheesh (but under the hood), just a bit more Angular styled: Output + EventEmitter:
Parent component:
import { Component } from '#angular/core';
#Component({
selector: 'app-parent',
template: `
Message: {{message}}
<app-child (messageEvent)="receiveMessage($event)"></app-child>
`,
styleUrls: ['./parent.component.css']
})
export class ParentComponent {
constructor() { }
message:string;
receiveMessage($event) {
this.message = $event
}
}
Children Component:
import { Component, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
template: `
<button (click)="sendMessage()">Send Message</button>
`,
styleUrls: ['./child.component.css']
})
export class ChildComponent {
message: string = "a string from child component"
#Output() messageEvent = new EventEmitter<string>();
constructor() { }
sendMessage() {
this.messageEvent.emit(this.message)
}
}
With the code, the parent will always be subscribed to the messageEvent that’s outputted by the child component, and it will run the function (the message function) after the child emits. Handling this with Angular has the advantage that we are sure that we don't have any memory leak in our app (e.g missing unsubscriptions).
When the component that is listening (the subscribed parent) gets destroyed, Angular will unsubscribe automatically to avoid potential memory leaks.

Unsure why data isn't passing correctly from child to parent component in Angular 4

I have two components and would like to pass data from the 'child' component that happens when a user has clicked an image from within that component.
I have two components (posting & gifpicker - the child component)
The function applyGif() is the function in the child component that I am using to pass data across - I want to pass this data to the parent component.
Note - the components have some code not required for this aspect removed from view in this post for extra clarity.
The HTML Component below currently shows nothing in the selectedGif for some reason in the view
-- Posting Component (Parent Component) --
/** long list of imports here **/
#Component({
selector: 'app-posting',
templateUrl: './posting.component.html',
styleUrls: ['./posting.component.scss'],
providers: [ GifpickerService ],
})
export class PostingComponent implements OnInit{
public selectedGif: any = '';
#ViewChild(GifpickerComponent) gifpicker: GifpickerComponent;
ngOnInit(): void {
}
constructor(#Inject(DOCUMENT) private document: any,
public gifpickerService: GifpickerService,
) {}
}
-- GifPickerComponent (Child Component) --
import {Component, OnInit} from '#angular/core';
import {FormControl} from '#angular/forms';
import {GifpickerService} from "./gifpicker.service";
#Component({
selector: 'app-gifpicker',
templateUrl: './gifpicker.component.html',
styleUrls: ['./gifpicker.component.scss'],
providers: [ GifpickerService ],
})
export class GifpickerComponent implements OnInit {
public selectedGif: any = {};
constructor(private gifpickerService: GifpickerService) {}
ngOnInit() {
}
applyGif(gif): any {
// this is an json object I want to use/see in the Posting HTML Component
let gifMedia = gif.media[0];
}
}
-- Posting Component HTML (want data from the gifPickerComponent applyGif() shown here --
<div>{{ selectedGif }}</div>
Have you tried using #Output() to pass the information from child to parent after applyGif() method ends.
In your GifPickerComponent declare:
#Output() gifSelected: EventEmitter<any> = new EventEmitter<any>(); // or whatever type your are sending
Once the GIF is selected in applyGif()
applyGif(gif): any {
this.gifPickerVisible = false;
this.uploadedGif = true;
let gifMedia = gif.media[0]; // this is an json object I want to use/see in the Posting HTML Component
this.gifSelected.emit(gifMedia);
}
In the PostingComponent HTML template file where you are using app-gifpicker:
<app-gifpicker (gifSelected)="onGifSelected($event)"></app-gifpicker>
Create onGifSelected in your posting.component.ts file and handle the result:
public onGifSelected(gif: any) {
// Do whatever you need to do.
this.selectedGif = gif;
}
In addition, your posting component is the parent and it hosts other components like your GIFPickerComponent, there is no need to provide the service in both components. It is enough to do it in the parent and it will be passed down to the child component. In other words, the same instance will be passed. With your current arrangement, both parent and child have two different instances of a service.

Is it possible to determine which modal / component I will show when I click on a component?

I'm working on small Angular 5 project, and I have a simple component which is representing an food product and it looks like this:
[![enter image description here][1]][1]
This component is nested component, it is part of my main component.
When I click on this component, quantity component/modal is shown:
<app-product-item (onInfoEdit)="InfoModal.show($event)"></app-product-item>
But now I would like to show another modal if some condition is satisfied, for example
If condition is satisfied then show this quantity modal (which is component itself) otherwise show some another modal (which is another component), so:
<app-product-item "if something is satisfied than this code on the right is ok otherwise lets display another modal with different method" (onInfoEdit)="InfoModal.show($event) (onSomethingElse)="AnotherModal.show($event)></app-product-item>
So I'm not sure if this is even possible because I need to show 2 different modals ( different components ) on same component's click, so basically if product has some property defined than show quantity info, otherwise show product info, and quantity info and product info are separated components..
Thanks guys
Cheers
You would be looking at something like this:
<app-product-item #productItem
(click)="productItem?.product?.property ? InfoModal.show($event) : AnotherModal.show($event)"
(onInfoEdit)="InfoModal.show($event)"
(onSomethingElse)="AnotherModal.show($event)">
</app-product-item>
where "property" would be a property of "product", which would be an object defined inside your Product Item Component.
Please also note that the conditional statements inside templates are not recommended and should be carried to the component.
You can do it by creating component which will conditionally render one of two components. One condition will load info component and other condition will load quantity component. Let's call this conditional modal something like: food-modal, inside food-modal.component.html template the code could look like:
<h1>CONDITIONAL MODAL CONTAINER</h1>
<ng-container *ngIf="product.name === 'Chicken'">
<app-info [product]="product"></app-info>
</ng-container>
<ng-container *ngIf="product.name === 'Cow'">
<app-quantity [product]="product"></app-quantity>
</ng-container>
And inside food-modal.component.ts the code could look like:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/Product.model';
#Component({
selector: 'app-food-modal',
templateUrl: './food-modal.component.html',
styleUrls: ['./food-modal.component.css']
})
export class FoodModalComponent implements OnInit {
#Input()
public product: Product;
constructor() {}
ngOnInit() {}
}
All you have to do now is call this component where you want it and pass in Product model. For sake of demonstration I'll put everything inside app component in order to load food-modal. The app.component.html could look like:
<app-food-modal [product]="chickenProduct">
</app-food-modal>
And inside app.component.ts code could be like this:
import { Component } from '#angular/core';
import { Product } from './models/Product.model';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
public chickenProduct: Product;
constructor() {
this.chickenProduct = {
id: 1,
quantity: 2,
name: 'Chicken'
};
}
}
Now app component will pass Product object named chickenProduct to food-modal component and bind it to modal's product property. After that the conditional rendering will happen.
Rest of the code could look like:
info.component.html:
<p>
This modal is for INFO. Product name is: {{ product.name }}
</p>
info.component.ts:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/Product.model';
#Component({
selector: 'app-info',
templateUrl: './info.component.html',
styleUrls: ['./info.component.css']
})
export class InfoComponent implements OnInit {
#Input()
public product: Product;
constructor() { }
ngOnInit() {
}
}
quantity.component.html:
<p>
This modal is for QUANTITY. Product name is: {{ product.name }}
</p>
quantity.component.ts:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/Product.model';
#Component({
selector: 'app-quantity',
templateUrl: './quantity.component.html',
styleUrls: ['./quantity.component.css']
})
export class QuantityComponent implements OnInit {
#Input()
public product: Product;
constructor() { }
ngOnInit() {
}
}
product.model.ts:
export interface Product {
id: number;
quantity: number;
name: string;
}
I've implemented solution like this and everything worked fine! Change name property of product to Cow and conditional fire will happen and load quantity component, take it back to Chicken and conditional fire will load info component.
I assume you just wanted to know how to trigger conditional firing, so that's why I've done this by hard coding string, checking whether name of product is Chicken or Cow.

#Input() Not Passing As Expected Between Parent-Child Components in Angular 2 App

I am trying to abstract out a tabular-data display to make it a child component that can be loaded into various parent components. I'm doing this to make the overall app "dryer". Before I was using an observable to subscribe to a service and make API calls and then printing directly to each component view (each of which had the tabular layout). Now I want to make the tabular data area a child component, and just bind the results of the observable for each of the parent components. For whatever reason, this is not working as expected.
Here is what I have in the parent component view:
<div class="page-view">
<div class="page-view-left">
<admin-left-panel></admin-left-panel>
</div>
<div class="page-view-right">
<div class="page-content">
<admin-tabs></admin-tabs>
<table-display [records]="records"></table-display>
</div>
</div>
</div>
And the component file looks like this:
import { API } from './../../../data/api.service';
import { AccountService } from './../../../data/account.service';
import { Component, OnInit, Input } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { TableDisplayComponent } from './../table-display/table-display.component';
#Component({
selector: 'account-comp',
templateUrl: 'app/views/account/account.component.html',
styleUrls: ['app/styles/app.styles.css']
})
export class AccountComponent extends TabPage implements OnInit {
private section: string;
records = [];
errorMsg: string;
constructor(private accountService: AccountService,
router: Router,
route: ActivatedRoute) {
}
ngOnInit() {
this.accountService.getAccount()
.subscribe(resRecordsData => this.records = resRecordsData,
responseRecordsError => this.errorMsg = responseRecordsError);
}
}
Then, in the child component (the one that contains the table-display view), I am including an #Input() for "records" - which is what the result of my observable is assigned to in the parent component. So in the child (table-display) component, I have this:
import { AccountService } from './../../../data/account.service';
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'table-display',
templateUrl: './table-display.component.html',
styleUrls: ['./table-display.component.less']
})
export class TableDisplayComponent {
#Input() records;
constructor() {
}
}
Lastly, here's some of the relevant code from my table-display view:
<tr *ngFor="let record of records; let i = index;">
<td>{{record.name.first}} {{record.name.last}}</td>
<td>{{record.startDate | date:"MM/dd/yy"}}</td>
<td><a class="bluelink" [routerLink]="['/client', record._id ]">{{record.name.first}} {{record.name.last}}</a></td>
When I use it with this configuration, I get "undefined" errors for the "records" properties I'm pulling in via the API/database. I wasn't getting these errors when I had both the table display and the service call within the same component. So all I've done here is abstract out the table-display so I can use it nested within several parent components, rather than having that same table-display show up in full in every parent component that needs it.
What am I missing here? What looks wrong in this configuration?
You need to protect against record being null until it comes in to your child component (and therefore it's view).
Use Elvis operators to protect your template:
<tr *ngFor="let record of records; let i = index;">
<td>{{record?.name?.first}} {{record?.name?.last}}</td>
<td>{{record?.startDate | date:"MM/dd/yy"}}</td>
<td><a class="bluelink" [routerLink]="['/client', record?._id ]"> {{record?.name?.first}} {{record?.name?.last}}</a></td>
You can also assign your input to an empty array to help with this issue:
#Input() records = [];

Categories