I'm part way through upgrading a large application to Angular 9 with Ivy compilation enabled, and have come across what seems to be a change in behaviour with NgModel injected into a custom directive
When using this directive, the NgModel that is injected is different between version 7 and version 8/9 when IVY compilation is used.
This does NOT happen when Ivy compilation is disabled.
The Directive (tabSelect)
import { Directive, AfterViewInit, OnDestroy, Optional } from '@angular/core';
import { NgModel } from "@angular/forms";
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
@Directive({
selector: '[tabSelect]',
providers: [NgModel]
})
export class TabSelectDirective implements AfterViewInit, OnDestroy {
observable: any;
onChange: any;
constructor(@Optional()
private autoTrigger: MatAutocompleteTrigger,
private ngModel: NgModel) { }
ngAfterViewInit() {
this.observable = this.autoTrigger.panelClosingActions.subscribe(x => {
if (this.autoTrigger.activeOption && this.autoTrigger.activeOption.value) {
if (this.ngModel.model !== this.autoTrigger.activeOption.value) {
this.ngModel.update.emit(this.autoTrigger.activeOption.value);
this.autoTrigger.autocomplete.optionSelected.emit();
}
} else {
console.log(this.ngModel);
console.log(this.autoTrigger);
if (!this.ngModel.viewModel || this.ngModel.viewModel === "")
this.ngModel.update.emit(null);
};
});
}
ngOnDestroy() {
this.observable.unsubscribe();
}
};
Using the Directive in the HTML
<mat-form-field appearance="outline" color="primary" class="mat-input-no-validation pull-right"
style="width: 130px; margin-left: 10px">
<mat-label>Document Group</mat-label>
<input matInput
placeholder="Search.."
name="documentGroupInput"
spellcheck="false"
[(ngModel)]="documentNode.documentGroup"
[matAutocomplete]="documentGroupAuto"
#documentGroupCtrl="ngModel"
tabSelect />
<div class="small-progress-spinner-container" [hidden]="!documentGroupSearching">
<mat-progress-spinner [diameter]="15" [mode]="'indeterminate'"></mat-progress-spinner>
</div>
<mat-autocomplete #documentGroupAuto="matAutocomplete">
<mat-option *ngFor="let documentGroup of documentGroupResults; trackBy:formatters.trackIndex" [value]="documentGroup">
<div class="autocomplete-option">
{{ documentGroup }}
</div>
</mat-option>
</mat-autocomplete>
</mat-form-field>
When inspecting the NgModel that is injected into the Directive, in Angular 7 we see the Control name
NgModel {_parent: NgForm, name: "documentGroupInput", valueAccessor: MatAutocompleteTrigger, _rawValidators: Array(0), _rawAsyncValidators: Array(0), …}
Wheras in Angular 9 we do not
NgModel {_parent: NgForm, name: null, valueAccessor: MatAutocompleteTrigger, _rawValidators: Array(0), _rawAsyncValidators: Array(0), …}
This leads me to believe that the injected NgModel is not actually the model of the control itself in Ivy, and this is supported by the method of assigning the value and emitting the changed event no longer working
Any ideas what has changed and how I can resolve this?
Thanks
EDIT: - Fixed code
import { Directive, AfterViewInit, OnDestroy, Optional } from '@angular/core';
import { NgModel } from "@angular/forms";
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
@Directive({
selector: '[tabSelect][ngModel]',
providers: []
})
export class TabSelectDirective implements AfterViewInit, OnDestroy {
observable: any;
onChange: any;
constructor(@Optional()
private autoTrigger: MatAutocompleteTrigger,
private ngModel: NgModel) { }
ngAfterViewInit() {
console.log(this.ngModel);
this.observable = this.autoTrigger.panelClosingActions.subscribe(x => {
if (this.autoTrigger.activeOption && this.autoTrigger.activeOption.value) {
if (this.ngModel.model !== this.autoTrigger.activeOption.value) {
this.ngModel.update.emit(this.autoTrigger.activeOption.value);
this.autoTrigger.autocomplete.optionSelected.emit();
}
} else {
console.log(this.ngModel);
console.log(this.autoTrigger);
if (!this.ngModel.viewModel || this.ngModel.viewModel === "")
this.ngModel.update.emit(null);
};
});
}
ngOnDestroy() {
this.observable.unsubscribe();
}
};
Removing providers: [NgModel]
from the @Directive
attribute should fix the problem.
See also github issue 35594