0

I have a child component that is a search box. A user types in some input and presses a search button. This emits a 'search' event that the parent component then calls a search service with the search parameters and assigns the result to an Observable input property of the child component. In the setter of the child component input property I subscribe to the Observable and then map the results. My issue is that the UI will not refresh when the results are set until an event such as a mouse move or button click occurs. I think I need to call the subscribe using ngZone my question is why do I have to do this? In an alternate flavor of my search component the parent component is responsible for calling subscribe and then assigns the results to an input property of the child component and this works as expected. Would really appreciate some explanation as to what is different with these two approaches and the best way to resolve.

Below is my search box (child) component.

import {
    AfterViewInit, ChangeDetectorRef, ChangeDetectionStrategy, Component,
    ElementRef, EventEmitter, Input, OnChanges,
    Output, SimpleChanges, ViewChild }                                  from '@angular/core';
import { ControlValueAccessor }                                         from '@angular/forms';
import {
    ApplicationService, PromptActions, PromptType,
    SearchItem, SearchItemType, SearchType, SearchArgs,
    UserPromptRequest
} from '../../../core';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import { PanelBarItemModel }                                            from '@progress/kendo-angular-layout';

@Component({
    moduleId: module.id,
    selector: 'search-box',
    templateUrl: 'search-box.component.html',
    styleUrls: ['search-box.component.css'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class SearchBoxComponent implements ControlValueAccessor {

    constructor(private cd: ChangeDetectorRef, private applicationService: ApplicationService) { }

    @Input() searchText: string;

    @Output() search: EventEmitter<SearchArgs> = new EventEmitter();

    private _searchObservable: Observable<SearchItemType[]>;
    @Input() 
    set searchObservable(val: Observable<SearchItemType[]>) {
        this._searchObservable = val;
        if (this._searchObservable) {
            this.searchSubscription = this._searchObservable.subscribe(r => this.resp(r));
        }
    }

    private _selectedItem: SearchItem;
    @Input()
    set selectedItem(val: SearchItem) {
        this._selectedItem = val;
        this.propagateChange(this._selectedItem);
    }

    get selectedItem(): SearchItem {
        return this._selectedItem;
    }

    searchResultsModel: PanelBarItemModel[];

    private _searchResults: SearchItemType[];
    @Input()
    set searchResults(val: SearchItemType[]) {
        this._searchResults = val;
        this.setUpPanelBarModel();
    }
    get searchResults(): SearchItemType[] {
        return this._searchResults;
    }

    private setUpPanelBarModel() {
        this.isSearching = false;
        this.cd.markForCheck();
    }   

    onSearch() {
        this.isResultsPopupVisible = true;
        this.searchResults = undefined;
        let searchArgs = new SearchArgs(this.searchText, this.selectedSearchTypeValue);
        this.search.emit(searchArgs);
        this.isSearching = true;
        this.cd.markForCheck();
    }

    resp(r: any) {
        this.searchResults = r;
        this.cd.markForCheck();
    }

    writeValue(obj: any): void {
        this.selectedItem = obj;
    }

    private propagateChange = (_: any) => { };

    registerOnChange(fn: any): void {
        this.propagateChange = fn;
    }        

    registerOnTouched(fn: any): void { } // Don't need

}

Below is the Parent component code. If I swap round the commented and uncommented line in onSearch my results display instantly. Otherwise I need to trigger another Angular UI tick by moving the mouse, clicking or pressing a key. Many thanks for any help!

import { ChangeDetectorRef, ChangeDetectionStrategy, Component }    from '@angular/core';
import { Observable }                                               from 'rxjs/Observable';
import { SettingsEntityListComponentBase }                          from '../../component-bases';
import { ClientType }                                               from '../../models/ClientType';
import { ClientTypeService }                                        from '../../services/client-type.service'
import {
    ApplicationService, NotificationContext,
    Permission, UserService, WindowType, SearchArgs,
    SearchItem, SearchItemType, SearchTypeValues
}                                                                   from '../../../core';

@Component({
    moduleId: module.id,
    selector: 'client-type-list',
    templateUrl: 'client-type-list.component.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ClientTypeListComponent extends SettingsEntityListComponentBase<ClientType> {
    constructor(
        changeDetector: ChangeDetectorRef,
        appService: ApplicationService,
        clientTypeService: ClientTypeService,
        userService: UserService) {
        super(changeDetector, appService, clientTypeService, userService, Permission.SettingsClientTypes,
            Permission.GlobalClientTypes, WindowType.ClientTypeDetail, NotificationContext.ClientTypeChanged);
    }

    searchText: string = '4455';

    selectedSecurity: SearchItem;

    searchObservable: Observable<SearchItemType[]>;

    searchResults: SearchItemType[];

    onSearch(args: SearchArgs): void {
        this.searchObservable = this.appService.search(args.searchText, SearchTypeValues.Securities);
        //this.appService.search(args.searchText, SearchTypeValues.Securities).subscribe(r => this.resp(r));
    }
    resp(r: any) {
        this.searchResults = r;
        this.changeDetector.markForCheck();
    }
}

1 Answer 1

0

By using ChangeDetectionStrategy.OnPush you're limited to change detection via @Input and @Output properties. Otherwise you have to notify Angular about the change manually.

Try connecting search results from the service to an @Input property you are using to display results (ClientTypeListComponent.searchResults ??). Also, I'm not sure how/if change detection works with getter/setter from SearchBoxComponent.searchResults; you can try rewriting it as normal @Input property.

Sign up to request clarification or add additional context in comments.

1 Comment

How did you fix your issue? i have the same problem. when I change the observable by any action the observable keeps track just wirth the first observable that I set. And I do Change detection without any success.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.