Force Angular to not do change detection on certain @Input

Issue

I’ve got an Angular component which does some fairly heavy calculations upon detecting changes.

@Component (
    selector: 'my-table',
    ... 400+ lines of angular template ...
)
class MyTable implements OnDestroy, AfterContentInit, OnChanges {
    ...
    @override
    ngOnChanges(Map<String, SimpleChange> changes) {
        log.info("ngOnChanges" + changes.keys.toString());
        _buildTableContent();
    }
    ...
}

This works beautifully when all the inputs are String, int, bool; in other words, ngOnChanges only triggers once these properties actually change.

I now need to add a custom renderer for one of the fields in order to render data that is not just a simple String and I do it using

@Input("customRenderer") Function customRenderer;

The customRenderer function will now decide if it should return the value as is or if the value is an object / list, extract certain values out of it and return a readable String instead of just Instance of ___;

As soon as I add this @Input("customRenderer"), ngOnChanges fires the whole time even though that function reference hasn’t changed.

enter image description here

Is there a way I can tell Angular to not trigger change detection on certain fields after the initial value is set?

A quick hack would be to just have an if-statement in the ngOnChanges function that checks if customRenderer is the only change, but change detection will continue to trigger which feels inefficient.

Does Angular have a hook I can override that will basically say, if field is customRenderer, do not trigger change detection, otherwise do normal change detection?

Update based on @pankaj-parkar’s answer:

@Component (
    selector: 'my-table',
    ... 400+ lines of angular template ...
)
class MyTable implements OnDestroy, AfterContentInit, OnChanges, DoCheck {

    ...

    final ChangeDetectorRef cdr;

    int renderOldValue = 0;
    @Input() int render = 0;

    MyTable(this.cdr);

    @override
    ngOnChanges(Map<String, SimpleChange> changes) {
        log.info("ngOnChanges" + changes.keys.toString());
        _buildTableContent();
    }

    @override
    ngDoCheck() {
        if (renderOldValue != render) {
            cdr.reattach();
            cdr.detectChanges();
            cdr.detach();
            renderOldValue = render;
        }
    }

    @override
    ngAfterContentInit() {

        // detach table from angular change detection;
        cdr.detach();

       ...

    }

    ...

}

Now the idea is to call render++ to manually trigger change detection

    <my-table 
        (change)="change(\$event)"
        (click2)="editResource(\$event)"
        [custom]="['tags', 'attributes']"
        [customRenderer]="customRenderer"
        [data]="data ?? []"
        [debug]="true"
        [editable]="enableQuickEdit ?? false"
        [loading]="loading ?? true"
        [render]="render ?? 0"
        [rowDropdownItems]="rowDropdownItems"
        [tableDropdownItems]="tableDropdownItems ?? []">
        <column *ngFor="let column of visibleColumns ?? []"
            [editable]="column.editable"
            [field]="column.field"
            [title]="column.title">
        </column>
    </my-table>

Doesn’t make a difference though …

Solution

Found a workaround for now that works.
By wrapping these functions inside a map, change detection seems to behave correctly:

final Map renderers = {
    "tags": (List<TagVO> tags) {
        final List<String> tagStrings = [];
        tags.forEach((tag) => tagStrings.add(tag.name));
        return tagStrings.join(", ");
    },
    "attributes": (List<Attribute> attributes) {
        return "ATTRIBUTES!!!";
    }
};

And passing it in just works for some reason:

<my-table 
    ...
    [render]="render ?? 0"
    [renderers]="renderers"
    ...
    <column *ngFor="let column of visibleColumns ?? []"
        [editable]="column.editable"
        [field]="column.field"
        [title]="column.title">
    </column>
</my-table>

And the @Input:

@Input("renderers") Map<String, Function> renderers;

Will see if I can reproduce the issue at some point in a standalone project and log a bug for it.

Answered By – Jan Vladimir Mostert

Answer Checked By – Terry (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *