angular - två barnkomponenter delar kontroll


Jag har två barnkomponenter som jag vill få åtkomst till en av dem från den andra. Jag har en sökinputkontroll på en av sidorna. På den andra har jag pagineringen som jag behöver åtkomst till värdet från. Finns det ett sätt att göra detta? Jag försökte använda detta searchItemsEvent: EventEmitter<any> men det fungerar inte.

Barnkomponent som använder inmatningskontroll

<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);
    }

}

Barnkomponent med styrning av paginering

<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&nbsp;&nbsp;</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">&nbsp;&nbsp;Refine</span>
                                    </td>
                                    <td align="right">
                                        <span style="font-weight:normal">Results:&nbsp;{{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>&nbsp;|&nbsp;<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 [];
        }
    
    }

Svar #1

Angular rekommenderar att dela data mellan icke föräldra-/barnrelationer genom tjänster. Du kan placera värden i denna tjänst med ett RxJS-rör som debouncer sökningen (detta ersätter din set/clearTimeout-logik):

Till exempel:

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) {}
}

I mallen kan du komma åt denna observerbara ström av värden med async-pipen, och i din kod kan du subscribe till strömmen. (Se RxJS-dokumentationen)

<div> The search value is {{ searchValue$ | async }} </div>
  this.searchValue$.subscribe(value => console.log("This is called whenever a new search value is emitted"))

Du kan komma åt detta sökvärde var som helst inom injektionsområdet för SearchService. På grund av @Injectable({providedIn: 'root'}) skulle detta vara samma tjänst i hela din app. Om denna kombination av component1/2 är upprepbar, och de båda behöver sin egen SearchService, ta då bort {providedIn: 'root'} och lägg istället till SearchService i en föräldrakomponents providers som bara innehåller 1 comp1/comp2-kombination:

@Component({ providers: [SearchService] })
class ParentOfBothComponent {}

Ett annat tips: Med en Subject spelar ordningen mellan .next och .subscribe roll, vanligtvis är detta inget problem, men om du ser att några första värden försvinner kan du försöka byta till en BehaviorSubject som kommer att spela upp det senaste värdet för sena prenumeranter:

private search = new BehaviorSubject<string>("");