Angular input setter triggering only once

Tudor Merlas picture Tudor Merlas · Jul 10, 2018 · Viewed 7.5k times · Source

I want to preface the question saying I have already read and understood other potentially duplicate questions (such as this one) and have been unable to make this work leading me to believe that the problem is a little different in nature or I misunderstood something.

The context: I have a parent component with two child components (a side menu and a main view). The side menu has some filters in the form of checkboxes bound to an object via [(ngModel)] and trigger an event using (ngModelChange)="changeFilter()" within the template.

In the parent component I catch the event: (filter)="updateFilters($event)". This function changes a public filter variable which I then pass along to the other child component like this: [filterObject]="filters"

In the main view component I feed the filterObject variable to a pipe in order to filter an array of objects.

The problem: The pipe gets called only once, after I changed the filter for the first time. The filterObject variable inside the main view shows up correctly updated when shown like this {{filterObject | json}} but the setter is never called. Console logs put in the setter and getter show that only the getter is getting access (2-4)

Solutions I've tried:

  • Adding an additional parameter to the pipe to trigger it. The problem with this approach is that setting a new date needs to be done from the setter for filterObject which doesn't get called - as stated above

  • Making the pipe impure (works but not a really good solution as it makes it process very big arrays of complex objects 2-4 times per click)

  • Making the parent update the filter variable, not through an assignment operator, but through Object.assign in order to change the reference and potentially trigger the setter

Solutions I'm considering: If I don't manage to figure out what the problem is I'm thinking of making a service to communicate between the two components via observers. Otherwise I might have to settle for an impure pipe with some custom change detection embedded (I'm thinking of converting the filter object to a JSON string and checking the length).

side-menu-component.html

<input type="checkbox" id="filter-type-2" name="filter-type-2" [(ngModel)]="filterObject.type.me" (ngModelChange)="changeFilter()">

side-menu-component.ts

 @Output() filter = new EventEmitter();
 public filterObject = {
    type: {
        mo: true,
        me: true,
        ar: true,
        cto: true,
        gl: true,
        dl: true
    },
    status: {
        unreviewed: true,
        approved: true,
        other: true
    }
};

// ...

changeFilter() {
    this.filter.emit(this.filterObject);
}

parent-component.html

<aside>
     <app-article-view-aside [article]="article" (close)="closeModal($event)" (filter)="updateFilters($event)"></app-article-view-aside>
</aside>
<div class="articleContainerMainView">
     <app-article-view-main [article]="article" [filterObject]="filters"></app-article-view-main>
</div>

parent-component.ts

updateFilters(newFilter) {
    this.filters = newFilter;
}

main-view-component.html

<app-finding
        *ngFor="let finding of findings | findingsFilter:filterDate:filterObject; let i = index"
        [finding]="finding"></app-finding>

main-view-component.ts

@Input() set filterObject(filterObject) {
    console.log('setter');
    this._filterObject = filterObject;
    this.filterDate = Date.now();
}

get filterObject() {
    console.log('getter');
    return this._filterObject;
}

private _filterObject = {};

public filterDate = Date.now();

console output when first displaying the page:

setter
getter
getter
getter
getter

I really want to do it "The Angular Way" so my question to you is what am I missing here?

Answer

Sergey P. aka azure picture Sergey P. aka azure · Jul 10, 2018

It may be that your side menu component emits the same object. Its content may be altered, but the emitted object is still the same. Consider emitting a copy of the object instead. Object.assign({}, this.filterObject) may help you.