Reputation: 12972
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.
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 ...
Upvotes: 2
Views: 2201
Reputation: 12972
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.
Upvotes: 1
Reputation: 136124
For such cases, you should consider detach
your component from change detector tree (you could do it inside ngOnInit
hook or use ChangeDetectionStrategy.Detach
in component metadata) and add add ngDoCheck
life cycle hook which fires for every CD.
MyTable(this.cd);
ngOnInit() {
cd.detach();
}
In that hook you will be specifically checking your conditionSo whenever condition gets satisfied attach your change detector and detach it once again. So the next change detection cycle will take care of updating your component binding and calling ngOnChanges
method.
ngDoCheck() {
if(model.value == 'specific value') {
cd.reattach();
cd.detectChanges(); //run cd once again for enabled component
cd.detach(); //detached component
//or
//cd.markForCheck();
}
}
Upvotes: 3