angular - 两个子组件共享控制权

标签 angular typescript input pagination eventemitter

我有两个子组件,我想从其中一个子组件访问另一个子组件。我在其中一个页面上有一个搜索输入控件。另一方面,我有需要访问该值的分页。有没有办法做到这一点?我尝试使用这个searchItemsEvent: EventEmitter<any>但它不起作用。

使用输入控件的子组件

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

}

具有控制分页的子组件

<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 [];
        }
    
    }

最佳答案

Angular 建议通过服务在非父/子关系之间共享数据。您可以使用 RxJS 管道将值放入此服务中,该管道可以消除搜索输入的抖动(这会替换您的 set/clearTimeout 逻辑):

例如:

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

在模板中,您可以使用async管道访问此可观察的值流,并且在代码中您可以订阅该流。 (参见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"))

您可以在 SearchService 注入(inject)范围内的任何位置访问此搜索值。由于 @Injectable({providedIn: 'root'}) 这将是整个应用程序中的相同服务。如果 component1/2 的这种组合是可重复的,并且它们每个都需要自己的 SearchService,则删除 {providedIn: 'root'} 并添加 SearchService 到仅包含 1 个 comp1/comp2 组合的父组件的 providers:

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

另一个提示:对于 Subject.next.subscribe 的顺序很重要,通常这不是问题,但如果您看到一些第一个值丢失,您可以尝试切换到 BehaviorSubject ,它将向后期订阅者重播最后一个值:

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

关于angular - 两个子组件共享控制权,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77114942/

相关文章:

重定向到带有 canActivate 的链接时,Angular2+ redirectTo 不起作用

javascript - Nodejs提示更多或更少

java - 我如何让我的 Java 扫描仪从该文件中读取这些字符串?

angular - angular 2中的多个自定义验证

angular - Angular Dart-从父路线检索参数

javascript - 以不可变的方式更新子组件中父组件的状态

c - 有没有办法让程序在等待输入时保持运行?

Angular2 如何在纯 JS 中定义输入属性

javascript - 在 Angular/ionic4 中的自定义组件中传递参数?

javascript - Angular - 方法不等待从 Firebase 获取值