How can I receive output from another Angular component? - javascript

Watched angular tutorial, and just followed the steps but now I'm struggling with #Output. I can't receive the value from another component in my home component.
I tried to use Output with EventEmitter but somehow it's not working.
Home component
<mat-drawer-container [autosize]="true" class="min-h-full max-w-7xl mx-auto">
<mat-drawer mode="side" opened class="p-6">
<app-filters (showCategory)="onShowCategory($event)"></app-filters>
</mat-drawer>
<mat-drawer-content class="p-6"
><app-products-header (columnsCountChange)="onColumnsCountChange($event)">{{
category
}}</app-products-header></mat-drawer-content
>
</mat-drawer-container>
Home Component TS
import { Component } from '#angular/core';
#Component({
selector: 'app-home',
templateUrl: `home.component.html`,
})
export class HomeComponent {
cols = 3;
category: string | undefined;
onColumnsCountChange(colsNum: number): void {
this.cols = colsNum;
}
onShowCategory(newCategory: string): void {
this.category = newCategory;
}
}
<mat-expansion-panel *ngIf="categories">
<mat-expansion-panel-header>
<mat-panel-title>CATEGORIES</mat-panel-title>
</mat-expansion-panel-header>
<mat-selection-list [multiple]="false">
<mat-list-option *ngFor="let category of categories" [value]="category"
><button (click)="onShowCategory(category)">
{{ category }}
</button></mat-list-option
>
</mat-selection-list>
</mat-expansion-panel>
Category Filter TS
#Component({
selector: 'app-filters',
templateUrl: 'filters.component.html',
})
export class FiltersComponent {
#Output() showCategory = new EventEmitter<string>();
categories = ['shoes', 'sports'];
onShowCategory(category: string): void {
this.showCategory.emit(category);
}
}

Here you are sending data from parent component (Home Component) to a child component (Category Filter) so you should use #input decorator instead of #output. Check this article https://angular.io/guide/inputs-outputs.
#Component({
selector: 'app-filters',
templateUrl: 'filters.component.html',
})
export class FiltersComponent {
#Input() showCategory = new EventEmitter<string>();
categories = ['shoes', 'sports'];
onShowCategory(category: string): void {
this.showCategory.emit(category);
}
}

Maybe try
this.showCategory.next(category); //instead of emit.
I just realized I did this in my application with Angular 14. I know before it I used emit method so not sur if there is a difference but your code look good.
Other thing: you want to display in an other child (app-products-header) component so I think you need to use
<ng-template></ng-template>
in the html child
But you can pass the value through #Input to app-products-header too and display it with interpolation like any other variables

Related

How to update parent component view when data is fetched from child component?

I have a parent and a child component, the child component is actually a modal where we can enter emails and click on 'Add to List'.
The entered emails are then fetched from parent component using #ViewChild property. The array details are obtained in parent because I can console.log() and see them from parent.
But how do I update a part of view in Parent with this new data is received ?
The code and explanations are as below :
Child component :
"emails" is an array with email ids.
I am fetching this array from child.
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
#Component({
selector: 'app-add-student-popup',
templateUrl: './add-student-popup.component.html',
styleUrls: ['./add-student-popup.component.scss']
})
export class AddStudentPopupComponent implements OnInit {
emails: string[] = [];
constructor() { }
ngOnInit(): void {
}
**** Some other code that adds user entered emails to an array 'emails' ****
----
----
sendData() {
console.log(this.emails);
}
}
Parent
import { AfterViewInit, Component, OnInit, ViewChild } from '#angular/core';
// Add students modal popup
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal/';
import { AddStudentPopupComponent } from './add-student-popup/add-student-popup.component';
#Component({
selector: 'app-create-course',
templateUrl: './create-course.component.html',
styleUrls: ['./create-course.component.scss']
})
export class CreateCourseComponent implements OnInit, AfterViewInit {
bsModalRef!: BsModalRef;
emails: string[] = [];
isEmpty: boolean = true;
#ViewChild(AddStudentPopupComponent) addStudent!: AddStudentPopupComponent;
constructor(private modalService: BsModalService) { }
ngOnInit(): any {}
ngAfterViewInit(): void {
if (this.addStudent) {
this.emails = this.addStudent.emails;
isEmpty = false;
}
}
openAddStudentModal() {
this.bsModalRef = this.modalService.show(AddStudentPopupComponent, {class: 'modal-lg'});
this.bsModalRef.content.closeBtnName = 'Close';
}
}
I want to use the updated "emails" array in my front end view. The view part using this array should be updated.
View part
When the popup is filled and submitted the emails array is populated and it should update the this view part.
<div class="multipleInput-container" *ngIf="!isEmpty;else noEmails">
<div class="col-lg-12">
<ul *ngFor="let email of emails">
<li class="multipleInput-email">{{ email }}</li>
</ul>
</div>
</div>
<ng-template #noEmails>
<div class="col-lg-3">
<input type="text" class="form-control form-control-sm"
placeholder="No Students Added" aria-label="No Students Added">
</div>
</ng-template>
Thanks in Advance
You should use #Output
In AddStudentPopupComponent
emails: string[] = [];
#Output() sendEmail = new EventEmitter();
sendData() {
this.sendEmail.emit(this.emails);
}
then in create-course.component.html file
<app-add-student-popup (sendEmail)="emails = $event"></app-add-student-popup>

Angular material tabs using ngComponentOutlet

I am using Angular Material Tabs to navigate different sections of a document. I've defined each tab in a TabItem class which looks like this:
class TabItem {
constructor(
public component: Type<any>,
public data: TabData,
public active: boolean
) {}
}
In the view, I loop through each TabItem and use *ngComponentOutlet to render the component of each TabItem.
<mat-tab-group>
<ng-container *ngFor="let tab of tabs">
<mat-tab>
<ng-template mat-tab-label>
<div class="mat-label-text" (click)="setActiveTab(tab)">{{ tab.data.label }}</div>
</ng-template>
<ng-container *ngComponentOutlet="tab.component"></ng-container>
</mat-tab>
</ng-container>
</mat-tab-group>
Everything works well... except I need access to the current TabItem in each of the resolved components to access its id, label, etc. The reason I am having trouble is because the examples online only show how to use ngComponentOutlet as a dynamic component. My components arent dynamic though... they are fixed, but created on the fly.
I dont know how I can use an injector since I am in a for loop... unless I create an injector for each individual item. I also dont want to subscribe to a service in every component... thats just ridiculous.
Here is a stackblitz of what I am trying to accomplish.
You can create a directive that will port desired data to your components:
data-provider.directive.ts
import { Directive, Input } from "#angular/core";
#Directive({
selector: '[dataProvider]'
})
export class DataProviderDirective {
#Input('dataProvider') data: any;
}
tabs.html
<ng-container *ngFor="let tab of tabs">
<mat-tab [dataProvider]="tab">
Now your dynamically generated component can read data from that directive:
tab-one.component.ts
import { Component, OnInit } from '#angular/core';
import { DataProviderDirective } from './data-provider.directive';
#Component({
selector: 'app-tab-one',
template: `
<p>I am tab one!</p>
<p>How can I access my respective TabItem?</p>
<pre>{{ dataProvider.data | json }}</pre>
`,
})
export class TabOneComponent implements OnInit {
constructor(public dataProvider: DataProviderDirective) { }
ngOnInit() {
console.log(this.dataProvider.data)
}
}
Forked Stackblitz

Angular: How to get value from one component's frontend (app.compont.html) to another component's backend (other.component.ts)

Consider a simple crud scenario. I have a lot of input fields and buttons in app.component.html. When i press a button from app.component.html, it will send html field value to 'other.component.ts' component and will display the result back in app.component.html after processing (like add, subtract or other).
Here is app.component.html
<a routerLink="posts/">Show Posts</a>
<input type="number" [(ngModel)]="get-one-post-id">
<a routerLink="/post-by-id">Show One Posts</a>
<router-outlet>
</router-outlet>
post-by-id-component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from '../data.service';
import { Observable } from 'rxjs';
#Component({
selector: 'app-post-by-id',
templateUrl: './post-by-id.component.html',
styleUrls: ['./post-by-id.component.css']
})
export class PostByIdComponent implements OnInit {
posts: object;
constructor(private dataService: DataService) { }
ngOnInit(): void {
// const id = ??
this.GetPost(1);
}
async GetPost(id: number)
{
const response = await this.dataService.Get_A_Post(id);
const dataService = await response.json();
this.posts = dataService;
}
}
post-by-id-component.html
<div *ngFor="let post of posts">
<h3>{{post.title}}</h3>
<p>{{post.body}}</p>
</div>
I just want to get value from the field called get-one-post-id from app.component.html to post-by-id-component.ts [where I commented // const id = ??]. But i can't find a way to import it.
To share Data between Angular Components exists 4 different ways:
Parent to Child: Sharing Data via Input
Child to Parent: Sharing Data via ViewChild
Child to Parent: Sharing Data via Output() and EventEmitter
Unrelated Components: Sharing Data with a Service
You can read this useful article to see how it works.

How to make child component detects object (#Input()) from parent component has changed in Angular

I have an object from parent component also received in a child component similar to this:
{
attribute: 'aaaa',
attribute2: [
{
value
},
{
value
},
{
value
},
]
}
This object is an #Input from a parent component. When I make changes to the objects inside the attribute2 array, I would like the child component detect that changes were made and then gets updated. As this is an object, I could'nt make it work, so I clone the entire object (this.objet = _.cloneDeep(this.object) in the parent component so then the child component detects that changes happened.
Is there any other way of doing this that does not clone the entire object? Thanks in advance
EDIT:
Child Component
export class ChildComponent implements OnInit, OnChanges {
#Input() public object: any;
}
html
<div>
<span>{{object.attribute}}</span>
<div *ngFor="let items of object.attribute2">{{item.value}}</div>
</div>
Parent Component
export class ParentComponent implements OnInit {
public object: any;
updateObject() {
this.object.attribute2[1] = 'Changed value';
this.object = _.cloneDeep(this.object);
}
}
html
<div>
<child-component [object]="object"></child-component>
</div>
An efficient way is to use EventEmitter and service communication to
trigger changes in the child component.
On way as mentioned by #Tony is to use ngOnChanges(). It is a good shortcut for detecting bounded properties change but as you add more and more bindings, using this hook will affect you application in the long run because it will run every time any of the bound property changes whether or not you desire it all the calls.
So for Service based communication, I've created an example on
Stackblitz:
https://stackblitz.com/edit/angular-fgut7t
Gist: https://gist.github.com/stupidly-logical/a34e272156b498513505127967aec851
In this example, I am binding an Array to the child component using #Input() an on addition of new data, the array is updated by the parent and the latest value is passed on the service which then emits this value. The child component subscribes to this value and the relevant code is executed.
The Service:
import { Injectable, EventEmitter } from '#angular/core';
#Injectable({
providedIn: "root"
})
export class DataService {
dataUpdated:EventEmitter<any> = new EventEmitter();
constructor() { }
setLatestData(data) {
this.dataUpdated.emit(data);
}
}
Child Component TS
import { Component, OnInit, Input } from '#angular/core';
import { DataService } from '../data-service.service';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() allData: [];
latestData: any;
constructor(private dataService: DataService) { }
ngOnInit() {
this.dataService.dataUpdated.subscribe((data) => {
this.latestData = data;
});
}
}
Child Component HTML
<p>
Latest Data: {{ latestData }}
</p>
<h3>List:</h3>
<li *ngFor="let data of allData">
{{ data }}
</li>
Parent Component TS
import { Component } from '#angular/core';
import { DataService } from './data-service.service'
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
dataArr = [];
constructor(private dataService: DataService){}
onAddTimestamp() {
let timestamp = new Date();
this.dataArr.push(timestamp);
this.dataService.setLatestData(timestamp);
}
}
Parent Component HTML
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happen :)
</p>
<button
(click)="onAddTimestamp()"
>
Add Timestamp
</button>
<app-child
[allData] = "dataArr"
></app-child>
Use the ngOnChanges() lifecycle method in your component.
ngOnChanges is called right after the data-bound properties have been
checked and before view and content children are checked if at least
one of them has changed.
Some like this
#Input() object: string;
ngOnChanges(changes: SimpleChanges) {
console.log(changes.object.currentValue);
// You can also use object.previousValue and
// object.firstChange for comparing old and new values
}

Angular 2 eventEmitter dosen't work

I need to make something simple, I want to display a dialog when I click on an help icon.
I have a parent component:
#Component({
selector: 'app-quotation',
templateUrl: './quotation.component.html'
})
export class QuotationComponent implements OnInit {
public quotation: any = {};
public device: string;
public isDataAvailable = false;
#Output() showPopin: EventEmitter<string> = new EventEmitter<string>();
constructor(private quotationService: QuotationService,
private httpErrors: HttpErrorsService,
private popinService: PopinService) {}
moreInfo(content: string) {
console.log('here');
this.showPopin.emit('bla');
}
}
And his html:
<ul>
<li *ngFor="let item of quotation.HH_Summary_TariffPageDisplay[0].content">
<label></label>
<i class="quotation-popin" (click)="moreInfo()"></i>
<div class="separator"></div>
</li>
</ul>
My popin component:
#Component({
selector: 'app-popin',
templateUrl: './popin.component.html',
styleUrls: ['./popin.component.scss']
})
export class PopinComponent implements OnInit {
public popinTitle: string;
public popinContent: string;
public hidden: boolean = true;
constructor() { }
openPopin(event):void {
console.log("here");
this.hidden = false;
}
}
His HTML:
<div class="card-block popin-container" (showPopin)="openPopin($event)" *ngIf="!hidden">
<div class="card">
<div class="popin-title">
{{ popinTitle }}
<i class="icon icon-azf-cancel"></i>
</div>
<div class="popin-content">
{{ popinContent }}
</div>
</div>
</div>
My parent component is loaded in a router-outlet and my popin is loaded on the same level than the router-outlet, like this:
<app-nav-bar></app-nav-bar>
<app-breadcrumb></app-breadcrumb>
<div class="container">
<router-outlet></router-outlet>
</div>
<app-popin></app-popin>
My problem is the eventEmitter doesn't work and i don't know why, someone can explain me ?
thx,
regards
EventEmitters only work for direct Parent-Child component relationships. You do not have this relationship with the components you are describing here.
In a parent-child relatonship, we will see the child's component element within the parent's template. We do not see this in your example.
You have two options:
Refactor to use a parent-child relationship
Use a service for communication
If you go with option 2, the service should just contain an observable that one component calls next on, and the other component subscribes to.
#Injectable()
export class PopinService {
showPopin = new ReplaySubject<string>(1);
}
Inject this in QuotationComponent and modify moreInfo
moreInfo(content: string): void {
this.popinService.showPopin.next('bla' + content);
}
In PopinComponent inject the popinService and add the following:
ngOnInit() {
this.popinService.showPopin.subscribe(content => {
this.hidden = false;
this.popinContent = content;
});
}
It's because you misuse it.
In your popin component, you just call the function and do a log, and set a variable to false.
And nowhere I can see that you use the app-quotation selector, so you don't really use it, do you ?
Looks like you are sending an output to a child component (popin) . Ideally if you give output that means it should be from child to parent and from parent to child, it is Input.

Categories

Resources