diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts index 4d3f8f6fca..7ce0dbcd74 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -559,87 +559,6 @@ describe('compiler compliance', () => { expectEmit(source, OtherDirectiveDefinition, 'Incorrect OtherDirective.ngDirectiveDef'); }); - it('should support host bindings', () => { - const files = { - app: { - 'spec.ts': ` - import {Directive, HostBinding, NgModule} from '@angular/core'; - - @Directive({selector: '[hostBindingDir]'}) - export class HostBindingDir { - @HostBinding('id') dirId = 'some id'; - } - - @NgModule({declarations: [HostBindingDir]}) - export class MyModule {} - ` - } - }; - - const HostBindingDirDeclaration = ` - HostBindingDir.ngDirectiveDef = $r3$.ɵdefineDirective({ - type: HostBindingDir, - selectors: [["", "hostBindingDir", ""]], - factory: function HostBindingDir_Factory(t) { return new (t || HostBindingDir)(); }, - hostBindings: function HostBindingDir_HostBindings(dirIndex, elIndex) { - $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵload(dirIndex).dirId)); - }, - hostVars: 1 - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, HostBindingDirDeclaration, 'Invalid host binding code'); - }); - - it('should support host bindings with pure functions', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'host-binding-comp', - host: { - '[id]': '["red", id]' - }, - template: '' - }) - export class HostBindingComp { - id = 'some id'; - } - - @NgModule({declarations: [HostBindingComp]}) - export class MyModule {} - ` - } - }; - - const HostBindingCompDeclaration = ` - const $ff$ = function ($v$) { return ["red", $v$]; }; - … - HostBindingComp.ngComponentDef = $r3$.ɵdefineComponent({ - type: HostBindingComp, - selectors: [["host-binding-comp"]], - factory: function HostBindingComp_Factory(t) { return new (t || HostBindingComp)(); }, - hostBindings: function HostBindingComp_HostBindings(dirIndex, elIndex) { - $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, $r3$.ɵload(dirIndex).id))); - }, - hostVars: 3, - consts: 0, - vars: 0, - template: function HostBindingComp_Template(rf, ctx) {} - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, HostBindingCompDeclaration, 'Invalid host binding code'); - }); - it('should not treat ElementRef, ViewContainerRef, or ChangeDetectorRef specially when injecting', () => { const files = { diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts index a1e9fa80be..cc75f4127b 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts @@ -122,6 +122,166 @@ describe('compiler compliance: bindings', () => { }); }); + describe('host bindings', () => { + it('should support host bindings', () => { + const files = { + app: { + 'spec.ts': ` + import {Directive, HostBinding, NgModule} from '@angular/core'; + + @Directive({selector: '[hostBindingDir]'}) + export class HostBindingDir { + @HostBinding('id') dirId = 'some id'; + } + + @NgModule({declarations: [HostBindingDir]}) + export class MyModule {} + ` + } + }; + + const HostBindingDirDeclaration = ` + HostBindingDir.ngDirectiveDef = $r3$.ɵdefineDirective({ + type: HostBindingDir, + selectors: [["", "hostBindingDir", ""]], + factory: function HostBindingDir_Factory(t) { return new (t || HostBindingDir)(); }, + hostBindings: function HostBindingDir_HostBindings(dirIndex, elIndex) { + $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵload(dirIndex).dirId)); + }, + hostVars: 1 + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, HostBindingDirDeclaration, 'Invalid host binding code'); + }); + + it('should support host bindings with pure functions', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'host-binding-comp', + host: { + '[id]': '["red", id]' + }, + template: '' + }) + export class HostBindingComp { + id = 'some id'; + } + + @NgModule({declarations: [HostBindingComp]}) + export class MyModule {} + ` + } + }; + + const HostBindingCompDeclaration = ` + const $ff$ = function ($v$) { return ["red", $v$]; }; + … + HostBindingComp.ngComponentDef = $r3$.ɵdefineComponent({ + type: HostBindingComp, + selectors: [["host-binding-comp"]], + factory: function HostBindingComp_Factory(t) { return new (t || HostBindingComp)(); }, + hostBindings: function HostBindingComp_HostBindings(dirIndex, elIndex) { + $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, $r3$.ɵload(dirIndex).id))); + }, + hostVars: 3, + consts: 0, + vars: 0, + template: function HostBindingComp_Template(rf, ctx) {} + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, HostBindingCompDeclaration, 'Invalid host binding code'); + }); + + it('should support host attribute bindings', () => { + const files = { + app: { + 'spec.ts': ` + import {Directive, NgModule} from '@angular/core'; + + @Directive({ + selector: '[hostAttributeDir]', + host: { + '[attr.required]': 'required' + } + }) + export class HostAttributeDir { + required = true; + } + + @NgModule({declarations: [HostAttributeDir]}) + export class MyModule {} + ` + } + }; + + const HostAttributeDirDeclaration = ` + HostAttributeDir.ngDirectiveDef = $r3$.ɵdefineDirective({ + type: HostAttributeDir, + selectors: [["", "hostAttributeDir", ""]], + factory: function HostAttributeDir_Factory(t) { return new (t || HostAttributeDir)(); }, + hostBindings: function HostAttributeDir_HostBindings(dirIndex, elIndex) { + $r3$.ɵelementAttribute(elIndex, "required", $r3$.ɵbind($r3$.ɵload(dirIndex).required)); + }, + hostVars: 1 + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, HostAttributeDirDeclaration, 'Invalid host attribute code'); + }); + + it('should support host attributes', () => { + const files = { + app: { + 'spec.ts': ` + import {Directive, NgModule} from '@angular/core'; + + @Directive({ + selector: '[hostAttributeDir]', + host: { + 'aria-label': 'label' + } + }) + export class HostAttributeDir { + } + + @NgModule({declarations: [HostAttributeDir]}) + export class MyModule {} + ` + } + }; + + const HostAttributeDirDeclaration = ` + HostAttributeDir.ngDirectiveDef = $r3$.ɵdefineDirective({ + type: HostAttributeDir, + selectors: [["", "hostAttributeDir", ""]], + factory: function HostAttributeDir_Factory(t) { return new (t || HostAttributeDir)(); }, + attributes: ["aria-label", "label"] + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, HostAttributeDirDeclaration, 'Invalid host attribute code'); + }); + + }); + describe('non bindable behavior', () => { const getAppFiles = (template: string = ''): MockDirectory => ({ app: { diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index f8aeeffc89..873fd03d67 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -477,7 +477,7 @@ describe('ngtsc behavioral tests', () => { env.driveMain(); const jsContents = env.getContents('test.js'); expect(jsContents) - .toContain(`i0.ɵelementProperty(elIndex, "attr.hello", i0.ɵbind(i0.ɵload(dirIndex).foo));`); + .toContain(`i0.ɵelementAttribute(elIndex, "hello", i0.ɵbind(i0.ɵload(dirIndex).foo));`); expect(jsContents) .toContain(`i0.ɵelementProperty(elIndex, "prop", i0.ɵbind(i0.ɵload(dirIndex).bar));`); expect(jsContents) diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index fd7581c572..2bc8da4f7f 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -32,6 +32,10 @@ import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, co const EMPTY_ARRAY: any[] = []; +// This regex matches any binding names that contain the "attr." prefix, e.g. "attr.required" +// If there is a match, the first matching group will contain the attribute name to bind. +const ATTR_REGEX = /attr\.([^\]]+)/; + function baseDirectiveFields( meta: R3DirectiveMetadata, constantPool: ConstantPool, bindingParser: BindingParser): {definitionMap: DefinitionMap, statements: o.Statement[]} { @@ -625,11 +629,14 @@ function createHostBindingsFunction( const bindingExpr = convertPropertyBinding( null, bindingContext, value, 'b', BindingForm.TrySimple, () => error('Unexpected interpolation')); + + const {bindingName, instruction} = getBindingNameAndInstruction(binding.name); + statements.push(...bindingExpr.stmts); - statements.push(o.importExpr(R3.elementProperty) + statements.push(o.importExpr(instruction) .callFn([ o.variable('elIndex'), - o.literal(binding.name), + o.literal(bindingName), o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]), ]) .toStmt()); @@ -649,6 +656,22 @@ function createHostBindingsFunction( return null; } +function getBindingNameAndInstruction(bindingName: string): + {bindingName: string, instruction: o.ExternalReference} { + let instruction !: o.ExternalReference; + + // Check to see if this is an attr binding or a property binding + const attrMatches = bindingName.match(ATTR_REGEX); + if (attrMatches) { + bindingName = attrMatches[1]; + instruction = R3.elementAttribute; + } else { + instruction = R3.elementProperty; + } + + return {bindingName, instruction}; +} + function createFactoryExtraStatementsFn(meta: R3DirectiveMetadata, bindingParser: BindingParser): ((instance: o.Expression) => o.Statement[])|null { const eventBindings = @@ -698,8 +721,8 @@ const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/; // Represents the groups in the above regex. const enum HostBindingGroup { - // group 1: "prop" from "[prop]" - Property = 1, + // group 1: "prop" from "[prop]", or "attr.role" from "[attr.role]" + Binding = 1, // group 2: "event" from "(event)" Event = 2, @@ -724,8 +747,8 @@ export function parseHostBindings(host: {[key: string]: string}): { const matches = key.match(HOST_REG_EXP); if (matches === null) { attributes[key] = value; - } else if (matches[HostBindingGroup.Property] != null) { - properties[matches[HostBindingGroup.Property]] = value; + } else if (matches[HostBindingGroup.Binding] != null) { + properties[matches[HostBindingGroup.Binding]] = value; } else if (matches[HostBindingGroup.Event] != null) { listeners[matches[HostBindingGroup.Event]] = value; } else if (matches[HostBindingGroup.Animation] != null) { diff --git a/packages/core/test/render3/host_binding_spec.ts b/packages/core/test/render3/host_binding_spec.ts index aa4dd225c1..3f57b01fd3 100644 --- a/packages/core/test/render3/host_binding_spec.ts +++ b/packages/core/test/render3/host_binding_spec.ts @@ -622,4 +622,28 @@ describe('host bindings', () => { expect(hostElement.title).toBe('other title'); }); + it('should support host attributes', () => { + // host: { + // 'role': 'listbox' + // } + class HostAttributeDir { + static ngDirectiveDef = defineDirective({ + selectors: [['', 'hostAttributeDir', '']], + type: HostAttributeDir, + factory: () => new HostAttributeDir(), + attributes: ['role', 'listbox'] + }); + } + + //
+ const App = createComponent('app', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + element(0, 'div', ['hostAttributeDir', '']); + } + }, 1, 0, [HostAttributeDir]); + + const fixture = new ComponentFixture(App); + expect(fixture.html).toEqual(`
`); + }); + }); diff --git a/packages/forms/test/reactive_integration_spec.ts b/packages/forms/test/reactive_integration_spec.ts index 49f456bf2a..2ba8f67604 100644 --- a/packages/forms/test/reactive_integration_spec.ts +++ b/packages/forms/test/reactive_integration_spec.ts @@ -1918,81 +1918,80 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec'; expect(form.valid).toEqual(true); }); - fixmeIvy('host attribute instructions are not generated properly') && - it('changes on bound properties should change the validation state of the form', () => { - const fixture = initTest(ValidationBindingsForm); - const form = new FormGroup({ - 'login': new FormControl(''), - 'min': new FormControl(''), - 'max': new FormControl(''), - 'pattern': new FormControl('') - }); - fixture.componentInstance.form = form; - fixture.detectChanges(); + it('changes on bound properties should change the validation state of the form', () => { + const fixture = initTest(ValidationBindingsForm); + const form = new FormGroup({ + 'login': new FormControl(''), + 'min': new FormControl(''), + 'max': new FormControl(''), + 'pattern': new FormControl('') + }); + fixture.componentInstance.form = form; + fixture.detectChanges(); - const required = fixture.debugElement.query(By.css('[name=required]')); - const minLength = fixture.debugElement.query(By.css('[name=minlength]')); - const maxLength = fixture.debugElement.query(By.css('[name=maxlength]')); - const pattern = fixture.debugElement.query(By.css('[name=pattern]')); + const required = fixture.debugElement.query(By.css('[name=required]')); + const minLength = fixture.debugElement.query(By.css('[name=minlength]')); + const maxLength = fixture.debugElement.query(By.css('[name=maxlength]')); + const pattern = fixture.debugElement.query(By.css('[name=pattern]')); - required.nativeElement.value = ''; - minLength.nativeElement.value = '1'; - maxLength.nativeElement.value = '1234'; - pattern.nativeElement.value = '12'; + required.nativeElement.value = ''; + minLength.nativeElement.value = '1'; + maxLength.nativeElement.value = '1234'; + pattern.nativeElement.value = '12'; - dispatchEvent(required.nativeElement, 'input'); - dispatchEvent(minLength.nativeElement, 'input'); - dispatchEvent(maxLength.nativeElement, 'input'); - dispatchEvent(pattern.nativeElement, 'input'); + dispatchEvent(required.nativeElement, 'input'); + dispatchEvent(minLength.nativeElement, 'input'); + dispatchEvent(maxLength.nativeElement, 'input'); + dispatchEvent(pattern.nativeElement, 'input'); - expect(form.hasError('required', ['login'])).toEqual(false); - expect(form.hasError('minlength', ['min'])).toEqual(false); - expect(form.hasError('maxlength', ['max'])).toEqual(false); - expect(form.hasError('pattern', ['pattern'])).toEqual(false); - expect(form.valid).toEqual(true); + expect(form.hasError('required', ['login'])).toEqual(false); + expect(form.hasError('minlength', ['min'])).toEqual(false); + expect(form.hasError('maxlength', ['max'])).toEqual(false); + expect(form.hasError('pattern', ['pattern'])).toEqual(false); + expect(form.valid).toEqual(true); - fixture.componentInstance.required = true; - fixture.componentInstance.minLen = 3; - fixture.componentInstance.maxLen = 3; - fixture.componentInstance.pattern = '.{3,}'; - fixture.detectChanges(); + fixture.componentInstance.required = true; + fixture.componentInstance.minLen = 3; + fixture.componentInstance.maxLen = 3; + fixture.componentInstance.pattern = '.{3,}'; + fixture.detectChanges(); - dispatchEvent(required.nativeElement, 'input'); - dispatchEvent(minLength.nativeElement, 'input'); - dispatchEvent(maxLength.nativeElement, 'input'); - dispatchEvent(pattern.nativeElement, 'input'); + dispatchEvent(required.nativeElement, 'input'); + dispatchEvent(minLength.nativeElement, 'input'); + dispatchEvent(maxLength.nativeElement, 'input'); + dispatchEvent(pattern.nativeElement, 'input'); - expect(form.hasError('required', ['login'])).toEqual(true); - expect(form.hasError('minlength', ['min'])).toEqual(true); - expect(form.hasError('maxlength', ['max'])).toEqual(true); - expect(form.hasError('pattern', ['pattern'])).toEqual(true); - expect(form.valid).toEqual(false); + expect(form.hasError('required', ['login'])).toEqual(true); + expect(form.hasError('minlength', ['min'])).toEqual(true); + expect(form.hasError('maxlength', ['max'])).toEqual(true); + expect(form.hasError('pattern', ['pattern'])).toEqual(true); + expect(form.valid).toEqual(false); - expect(required.nativeElement.getAttribute('required')).toEqual(''); - expect(fixture.componentInstance.minLen.toString()) - .toEqual(minLength.nativeElement.getAttribute('minlength')); - expect(fixture.componentInstance.maxLen.toString()) - .toEqual(maxLength.nativeElement.getAttribute('maxlength')); - expect(fixture.componentInstance.pattern.toString()) - .toEqual(pattern.nativeElement.getAttribute('pattern')); + expect(required.nativeElement.getAttribute('required')).toEqual(''); + expect(fixture.componentInstance.minLen.toString()) + .toEqual(minLength.nativeElement.getAttribute('minlength')); + expect(fixture.componentInstance.maxLen.toString()) + .toEqual(maxLength.nativeElement.getAttribute('maxlength')); + expect(fixture.componentInstance.pattern.toString()) + .toEqual(pattern.nativeElement.getAttribute('pattern')); - fixture.componentInstance.required = false; - fixture.componentInstance.minLen = null !; - fixture.componentInstance.maxLen = null !; - fixture.componentInstance.pattern = null !; - fixture.detectChanges(); + fixture.componentInstance.required = false; + fixture.componentInstance.minLen = null !; + fixture.componentInstance.maxLen = null !; + fixture.componentInstance.pattern = null !; + fixture.detectChanges(); - expect(form.hasError('required', ['login'])).toEqual(false); - expect(form.hasError('minlength', ['min'])).toEqual(false); - expect(form.hasError('maxlength', ['max'])).toEqual(false); - expect(form.hasError('pattern', ['pattern'])).toEqual(false); - expect(form.valid).toEqual(true); + expect(form.hasError('required', ['login'])).toEqual(false); + expect(form.hasError('minlength', ['min'])).toEqual(false); + expect(form.hasError('maxlength', ['max'])).toEqual(false); + expect(form.hasError('pattern', ['pattern'])).toEqual(false); + expect(form.valid).toEqual(true); - expect(required.nativeElement.getAttribute('required')).toEqual(null); - expect(required.nativeElement.getAttribute('minlength')).toEqual(null); - expect(required.nativeElement.getAttribute('maxlength')).toEqual(null); - expect(required.nativeElement.getAttribute('pattern')).toEqual(null); - }); + expect(required.nativeElement.getAttribute('required')).toEqual(null); + expect(required.nativeElement.getAttribute('minlength')).toEqual(null); + expect(required.nativeElement.getAttribute('maxlength')).toEqual(null); + expect(required.nativeElement.getAttribute('pattern')).toEqual(null); + }); it('should support rebound controls with rebound validators', () => { const fixture = initTest(ValidationBindingsForm); diff --git a/packages/forms/test/template_integration_spec.ts b/packages/forms/test/template_integration_spec.ts index bd1515c203..39db4df45b 100644 --- a/packages/forms/test/template_integration_spec.ts +++ b/packages/forms/test/template_integration_spec.ts @@ -1420,77 +1420,76 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat expect(form.control.hasError('minlength', ['tovalidate'])).toBeTruthy(); })); - fixmeIvy('host attribute instructions are not generated properly') && - it('changes on bound properties should change the validation state of the form', - fakeAsync(() => { - const fixture = initTest(NgModelValidationBindings); - fixture.detectChanges(); - tick(); + it('changes on bound properties should change the validation state of the form', + fakeAsync(() => { + const fixture = initTest(NgModelValidationBindings); + fixture.detectChanges(); + tick(); - const required = fixture.debugElement.query(By.css('[name=required]')); - const minLength = fixture.debugElement.query(By.css('[name=minlength]')); - const maxLength = fixture.debugElement.query(By.css('[name=maxlength]')); - const pattern = fixture.debugElement.query(By.css('[name=pattern]')); + const required = fixture.debugElement.query(By.css('[name=required]')); + const minLength = fixture.debugElement.query(By.css('[name=minlength]')); + const maxLength = fixture.debugElement.query(By.css('[name=maxlength]')); + const pattern = fixture.debugElement.query(By.css('[name=pattern]')); - required.nativeElement.value = ''; - minLength.nativeElement.value = '1'; - maxLength.nativeElement.value = '1234'; - pattern.nativeElement.value = '12'; + required.nativeElement.value = ''; + minLength.nativeElement.value = '1'; + maxLength.nativeElement.value = '1234'; + pattern.nativeElement.value = '12'; - dispatchEvent(required.nativeElement, 'input'); - dispatchEvent(minLength.nativeElement, 'input'); - dispatchEvent(maxLength.nativeElement, 'input'); - dispatchEvent(pattern.nativeElement, 'input'); + dispatchEvent(required.nativeElement, 'input'); + dispatchEvent(minLength.nativeElement, 'input'); + dispatchEvent(maxLength.nativeElement, 'input'); + dispatchEvent(pattern.nativeElement, 'input'); - const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.hasError('required', ['required'])).toEqual(false); - expect(form.control.hasError('minlength', ['minlength'])).toEqual(false); - expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(false); - expect(form.control.hasError('pattern', ['pattern'])).toEqual(false); - expect(form.valid).toEqual(true); + const form = fixture.debugElement.children[0].injector.get(NgForm); + expect(form.control.hasError('required', ['required'])).toEqual(false); + expect(form.control.hasError('minlength', ['minlength'])).toEqual(false); + expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(false); + expect(form.control.hasError('pattern', ['pattern'])).toEqual(false); + expect(form.valid).toEqual(true); - fixture.componentInstance.required = true; - fixture.componentInstance.minLen = 3; - fixture.componentInstance.maxLen = 3; - fixture.componentInstance.pattern = '.{3,}'; - fixture.detectChanges(); + fixture.componentInstance.required = true; + fixture.componentInstance.minLen = 3; + fixture.componentInstance.maxLen = 3; + fixture.componentInstance.pattern = '.{3,}'; + fixture.detectChanges(); - dispatchEvent(required.nativeElement, 'input'); - dispatchEvent(minLength.nativeElement, 'input'); - dispatchEvent(maxLength.nativeElement, 'input'); - dispatchEvent(pattern.nativeElement, 'input'); + dispatchEvent(required.nativeElement, 'input'); + dispatchEvent(minLength.nativeElement, 'input'); + dispatchEvent(maxLength.nativeElement, 'input'); + dispatchEvent(pattern.nativeElement, 'input'); - expect(form.control.hasError('required', ['required'])).toEqual(true); - expect(form.control.hasError('minlength', ['minlength'])).toEqual(true); - expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(true); - expect(form.control.hasError('pattern', ['pattern'])).toEqual(true); - expect(form.valid).toEqual(false); + expect(form.control.hasError('required', ['required'])).toEqual(true); + expect(form.control.hasError('minlength', ['minlength'])).toEqual(true); + expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(true); + expect(form.control.hasError('pattern', ['pattern'])).toEqual(true); + expect(form.valid).toEqual(false); - expect(required.nativeElement.getAttribute('required')).toEqual(''); - expect(fixture.componentInstance.minLen.toString()) - .toEqual(minLength.nativeElement.getAttribute('minlength')); - expect(fixture.componentInstance.maxLen.toString()) - .toEqual(maxLength.nativeElement.getAttribute('maxlength')); - expect(fixture.componentInstance.pattern.toString()) - .toEqual(pattern.nativeElement.getAttribute('pattern')); + expect(required.nativeElement.getAttribute('required')).toEqual(''); + expect(fixture.componentInstance.minLen.toString()) + .toEqual(minLength.nativeElement.getAttribute('minlength')); + expect(fixture.componentInstance.maxLen.toString()) + .toEqual(maxLength.nativeElement.getAttribute('maxlength')); + expect(fixture.componentInstance.pattern.toString()) + .toEqual(pattern.nativeElement.getAttribute('pattern')); - fixture.componentInstance.required = false; - fixture.componentInstance.minLen = null !; - fixture.componentInstance.maxLen = null !; - fixture.componentInstance.pattern = null !; - fixture.detectChanges(); + fixture.componentInstance.required = false; + fixture.componentInstance.minLen = null !; + fixture.componentInstance.maxLen = null !; + fixture.componentInstance.pattern = null !; + fixture.detectChanges(); - expect(form.control.hasError('required', ['required'])).toEqual(false); - expect(form.control.hasError('minlength', ['minlength'])).toEqual(false); - expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(false); - expect(form.control.hasError('pattern', ['pattern'])).toEqual(false); - expect(form.valid).toEqual(true); + expect(form.control.hasError('required', ['required'])).toEqual(false); + expect(form.control.hasError('minlength', ['minlength'])).toEqual(false); + expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(false); + expect(form.control.hasError('pattern', ['pattern'])).toEqual(false); + expect(form.valid).toEqual(true); - expect(required.nativeElement.getAttribute('required')).toEqual(null); - expect(required.nativeElement.getAttribute('minlength')).toEqual(null); - expect(required.nativeElement.getAttribute('maxlength')).toEqual(null); - expect(required.nativeElement.getAttribute('pattern')).toEqual(null); - })); + expect(required.nativeElement.getAttribute('required')).toEqual(null); + expect(required.nativeElement.getAttribute('minlength')).toEqual(null); + expect(required.nativeElement.getAttribute('maxlength')).toEqual(null); + expect(required.nativeElement.getAttribute('pattern')).toEqual(null); + })); fixmeIvy('ngModelChange callback never called') && it('should update control status', fakeAsync(() => {