6

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?

3 Answers 3

21

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.

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

1 Comment

Wow, such a simple solution for such a complicated problem. I had tried using Object.assign but on the parent component not on the original component.
1

This is related to the change detection of your lifecycle. Without a Minimal, Complete and Verifiable Example I can't really tell you how to solve it, but i can definitely tell you how to work around it.

Start by giving you second component a template variable, and tell your output to update the data in it :

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

In your second component, instead of using a pipe, update your value in the function :

updateData(event) {
  // original data is the unfiltered array of data
  this.findings = this.originalData.filter(...);
}

And in your template it becomes

<app-finding
    *ngFor="let finding of findings; let i = index"
    [finding]="finding"></app-finding>

Comments

1

The problem is when you pass the filter for the first time, you gave it the a reference. So, every time you change it, the change detection of angular will never detect it because you did not change the filter object or reference.

Solution: I guess you have two solutions: 1. Every time you update the filter change its reference by copy it. 2. Implement ngDoCheck in your pipe, if i am not wrong it will detect this change even if your reference still the same.

Comments

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.