I have two Child Components that I want to get access to one of them from the other. I have a search input control on one of the pages. On the other, I have the pagination that I need access to the value. Is there a way to do this? I tried to use this searchItemsEvent: EventEmitter<any>
but it's not working.
Child Component using input control
<div class="indication-grid-search-form-container">
<div class="form-inline">
<div class="form-group">
<label for="gridSearchPatternId">Indication description</label>
<div class="input-group">
<input type="text" class="form-control" id="gridSearchPatternId" placeholder="Search on indication" aria-describedby="gridSearchPatternAddonId" [(ngModel)]="searchString" (ngModelChange)="onSearchStringChanged()">
<span class="input-group-addon" id="gridSearchPatternAddonId"><span class="glyphicon glyphicon-search"></span></span>
</div>
</div>
<button *ngIf="allowEdit" type="button" class="btn btn-primary add-new-grid-button" (click)="onAddNewItem()">
<span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span> New Indication
</button>
</div>
</div>
@Component({
selector: ".indication-grid-filter-container",
templateUrl: "/Views/Components/Indication/IndicationGridFilter.html"
})
export class IndicationGridFilterComponent {
private searchTimeout;
@Input() allowEdit: boolean;
@Output() addNewItemEvent = new EventEmitter();
@Output() searchItemsEvent: EventEmitter<any> = new EventEmitter();
searchString: string;
onSearchStringChanged() {
clearTimeout(this.searchTimeout);
this.searchTimeout = setTimeout(() => {
this.searchItemsEvent.next(this.searchString.toLowerCase());
}, 500);
}
onAddNewItem() {
this.addNewItemEvent.next(null);
}
}
Child Component with control pagination
<table class="table table-hover grid-table" width="100%">
<thead class="grid-header">
<tr style="vertical-align:top;" class="grid-header-title">
<th width="40%">
<div>
<span>Indication description</span>
</div>
</th>
<th width="40%">
<div class="dropdown indications-category-filter">
<span>Category </span>
<span>
<img src="/Content/images/filterActive.png" style="width: 16px" *ngIf="currentIndicationTypeId === 0" />
<img src="/Content/images/filterSelected.png" style="width: 16px" *ngIf="currentIndicationTypeId > 0" />
</span>
<a href="#" class="dropdown-toggle" id="dropdownMenuCategoryActive" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"><span class="caret"></span></a>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuCategoryActive">
<li class="divider-bottom">
<table style="width:180px;" cellpadding="5" cellspacing="5">
<tr>
<td>
<span style="font-weight:bolder"> Refine</span>
</td>
<td align="right">
<span style="font-weight:normal">Results: {{itemsCount}}</span>
</td>
</tr>
</table>
</li>
<li class="divider-bottom">
<a href="#" (click)="selectIndicationType(0)" class="indications-category-filter-item">
<span class="glyphicon glyphicon-none" aria-hidden="true"></span>
Show All
<span class="badge">
{{totalCount}}
</span>
</a>
</li>
<li *ngFor="let item of stats">
<a href="#" (click)="selectIndicationType(item.Id)" class="indications-category-filter-item">
<span class="glyphicon glyphicon-ok" aria-hidden="true"
*ngIf="currentIndicationTypeId == item.Id"></span>
<span class="glyphicon glyphicon-none" aria-hidden="true"
*ngIf="currentIndicationTypeId != item.Id"></span>
{{item.Name}}
<span class="badge">
{{item.Count}}
</span>
</a>
</li>
</ul>
</div>
</th>
<th width="10%">
<div>
<span>#Templates</span>
</div>
</th>
<th width="10%"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of items | paginate: { id: tabId, itemsPerPage: pageSize, currentPage: currentPage, totalItems: itemsCount }" class="indication-data-row">
<td>
{{item.ValueName}}
</td>
<td>
<span *ngFor="let category of getIndicationCategories(item)">
<span>{{category.Name}}</span>
</span>
</td>
<td>
{{item.TemplateCount}}
</td>
<td>
<div *ngIf="allowEdit" class="indications-grid-indication-operations">
<span style="display: inline">
<a (click)="editItem(item)">Edit</a> | <a (click)="changeArchiveStatus(item)">{{archiveOperationLabel}}</a>
</span>
</div>
</td>
</tr>
<tr *ngIf="items.length == 0">
<td colspan="3">
<table width="100%">
<tr style="height:150px">
<td></td>
</tr>
<tr>
<td align="center">
<span class="middle-size-information-label">No such indication exists.</span>
</td>
</tr>
<tr style="height:150px">
<td></td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<div>
<div class="pagination-controls" (pageChange)="getPage($event)" id="{{tabId}}"></div>
</div>
import IndicationModel = OrderTemplateTool.Web.Models.Indications.IndicationModel;
import SentencePartModelBase = OrderTemplateTool.Core.Sentences.Indications.MultiselectSentencePartModel;
import { IndicationGridFilterComponent } from "../Indication/IndicationGridFilter";
import { Component, Input, Output, NgZone, AfterViewInit, Inject, EventEmitter, ViewChild, OnInit, ViewChildren, OnChanges, QueryList } from "@angular/core";
import { DictionaryItem } from "../../Models/dictionaryItem";
import { PaginationComponent } from "../Common/Pagination";
import { IndicationManager } from "../../Bll/IndicationManager";
import {BaseComponent} from "../baseComponent";
import { Dictionaries } from "../../Models/DynamicDictionaries";
import IndicationDictionaryModel = OrderTemplateTool.Web.Models.Indications.IndicationDictionaryModel;
import { Observable } from "rxjs/Observable";
import { Subject } from "rxjs";
@Component({
selector: "div.indications-grid-tab",
templateUrl: "/Views/Components/Indication/IndicationsGridTab.html"
})
export class IndicationsGridTabComponent implements OnInit {
@Input() allowEdit: boolean;
@Input() isActive = true;
@Output() onEditItem = new EventEmitter();
@Output() onChangeArchiveStatus = new EventEmitter();
@Output() searchFilter: EventEmitter<any> = new EventEmitter();
indicationTypes: DictionaryItem[];
currentIndicationTypeId = 0;
archiveOperationLabel: string;
constructor(@Inject(IndicationManager) private indicationManager: IndicationManager, private indicationGridFilterComponent: IndicationGridFilterComponent) {
this.indicationTypes = this.indicationManager.getIndicationTypes();
}
tabId: string;
totalCount: number;
items: IndicationDictionaryModel[] = [];
itemsCount: number;
stats: any[];
pageSize = IndicationManager.indicationsGridItemsPerPage;
currentPage: number;
ngOnInit() {
this.tabId = this.isActive ? "indications_active" : "indications_archive";
this.loadInitialState(1);
this.archiveOperationLabel = this.isActive ? "Archive" : "Restore";
}
loadInitialState(pageNumber: number, searchString?: string) {
return this.indicationManager.getIndicationsGridInitialState(pageNumber, this.isActive, (response) => {
this.stats = response.stats;
this.totalCount = response.totalCount;
this.itemsCount = response.itemsCount;
this.items = response.items;
this.currentPage = pageNumber;
}, searchString);
}
getPage(pageNumber: number) {
console.log('getPage');
const SearchPattern = $("#gridSearchPatternId");
console.log(SearchPattern.val());
//console.log(this.searchItemsEvent);
this.searchFilter.emit;
this.indicationManager.getIndicationsPageByActivity(
pageNumber,
this.isActive,
this.currentIndicationTypeId,
(response) => {
this.itemsCount = response.ItemsCount;
this.items = response.Indications;
this.currentPage = pageNumber;
},
SearchPattern.val()
);
}
getStats() {
this.indicationManager.getIndicationCategoryStatsByActivity(
this.isActive,
(response) => {
if (response.success) {
this.stats = response.stats;
this.totalCount = response.totalCount;
}
}
);
}
selectIndicationType(id: number) {
this.currentIndicationTypeId = id;
this.getPage(1);
}
editItem(item) {
this.onEditItem.next(item);
}
changeArchiveStatus(item) {
this.onChangeArchiveStatus.next(item);
}
private getIndicationDescription(item: IndicationModel): string {
return item.Sentence != null ?
item.Sentence.CompiledText :
"";
}
private getIndicationCategories(item: OrderTemplateTool.Web.Models.Indications.IndicationDictionaryModel): DictionaryItem[] {
var result: DictionaryItem[] = [];
var d: Dictionaries = new Dictionaries();
for (var i = 0; i < d.IndicationType.length; i++) {
if (d.IndicationType[i].Id == parseInt(item.DictionaryEnumValue.toString())) {
return [d.IndicationType[i]];
}
}
return [];
}
}
Angular recommends sharing data between non parent/child relationships through services. You can place values into this service with an RxJS pipe that debounces the search input (this replaces your set/clearTimeout
logic):
For example:
component1.ts
class Component1 {
constructor(private readonly searchService: SearchService) {}
onSearchStringChanged(searchString: string) {
this.searchService.searchNext(searchString);
}
}
search.service.ts
@Injectable({providedIn: 'root'})
class SearchService {
private search = new Subject<string>();
searchNext = (searchString: string) => this.search.next(string);
searchValue$ = this.search.pipe(
map(value => value.toLowerCase()),
debounceTime(500),
);
}
component2.ts
class Component2 {
searchValue$ = this.searchService.searchValue$;
constructor(private searchService: SearchService) {}
}
In the template you can access this observable stream of values with the async
pipe, and in your code you can subscribe
to the stream. (See the RxJS docs)
<div> The search value is {{ searchValue$ | async }} </div>
this.searchValue$.subscribe(value => console.log("This is called whenever a new search value is emitted"))
You can access this search value anywhere in the injection scope of SearchService
. Because of @Injectable({providedIn: 'root'})
this would be the same service in your entire app. If this combination of component1/2 is repeatable, and they each need their own SearchService
, then remove {providedIn: 'root'}
and instead add SearchService
to a parent component's providers
that contains only 1 comp1/comp2 combo:
@Component({ providers: [SearchService] })
class ParentOfBothComponent {}
Another tip: With a Subject
the order of .next
and .subscribe
matters, usually this is not a problem, but if you see some first values going missing, you can try to switch to a BehaviorSubject
which will replay the last value to late subscribers:
private search = new BehaviorSubject<string>("");