Skip to content

Instantly share code, notes, and snippets.

@Fredx87
Created August 22, 2018 17:57
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Fredx87/ddda60714527c1fcb43370bb6a157678 to your computer and use it in GitHub Desktop.
Save Fredx87/ddda60714527c1fcb43370bb6a157678 to your computer and use it in GitHub Desktop.
Angular SyncControl directive
import { Component } from '@angular/core';
import { async, TestBed } from '@angular/core/testing';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { SyncControlDirective } from './sync-control.directive';
class FormGroupHostComponent {
formGroup = new FormGroup({
ctrl: new FormControl('')
});
}
@Component({
template: `
<form [formGroup]="formGroup">
<input formControlName="ctrl" appSyncControl>
<input formControlName="ctrl" appSyncControl>
</form>
`
})
class FormControlNameHostComponent extends FormGroupHostComponent {}
@Component({
template: `
<div>
<input [formControl]="ctrl" appSyncControl>
<input [formControl]="ctrl" appSyncControl>
</div>
`
})
class FormControlHostComponent {
ctrl = new FormControl('');
}
@Component({
template: `
<form [formGroup]="formGroup">
<input formControlName="ctrl" [appSyncControl]="true">
<input formControlName="ctrl" [appSyncControl]="false">
<input formControlName="ctrl" appSyncControl>
</form>
`
})
class ActiveStateHostComponent extends FormGroupHostComponent {}
@Component({
template: `
<form [formGroup]="formGroup">
<input formControlName="ctrl">
<input formControlName="ctrl" [appSyncControl]="syncControls">
<input formControlName="ctrl" [appSyncControl]="syncControls">
</form>
`
})
class DynamicActiveStateHostComponent extends FormGroupHostComponent {
syncControls: boolean;
}
describe('SyncControlDirective', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule],
declarations: [
SyncControlDirective,
FormControlNameHostComponent,
FormControlHostComponent,
ActiveStateHostComponent,
DynamicActiveStateHostComponent
]
});
}));
it('should sync two controls with formControlName', () => {
const fixture = TestBed.createComponent(FormControlNameHostComponent);
fixture.detectChanges();
const inputs = fixture.debugElement.queryAll(By.css('input'));
const firstInputEl = inputs[0].nativeElement as HTMLInputElement;
const secondInputEl = inputs[1].nativeElement as HTMLInputElement;
firstInputEl.value = 'test';
firstInputEl.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(firstInputEl.value).toEqual('test');
expect(secondInputEl.value).toEqual('test');
});
it('should sync two controls with formControl', () => {
const fixture = TestBed.createComponent(FormControlHostComponent);
fixture.detectChanges();
const inputs = fixture.debugElement.queryAll(By.css('input'));
const firstInputEl = inputs[0].nativeElement as HTMLInputElement;
const secondInputEl = inputs[1].nativeElement as HTMLInputElement;
firstInputEl.value = 'hello!';
firstInputEl.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(firstInputEl.value).toEqual('hello!');
expect(secondInputEl.value).toEqual('hello!');
});
it('should sync controls with undefined or true input, and should not sync controls with false input', () => {
const fixture = TestBed.createComponent(ActiveStateHostComponent);
fixture.detectChanges();
const inputs = fixture.debugElement.queryAll(By.css('input'));
const firstInputEl = inputs[0].nativeElement as HTMLInputElement;
const secondInputEl = inputs[1].nativeElement as HTMLInputElement;
const thirdInputEl = inputs[2].nativeElement as HTMLInputElement;
firstInputEl.value = 'sync me';
firstInputEl.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(firstInputEl.value).toEqual('sync me');
expect(secondInputEl.value).not.toEqual('sync me');
expect(thirdInputEl.value).toEqual('sync me');
});
it('should dinamically sync controls if input changes', () => {
const fixture = TestBed.createComponent(DynamicActiveStateHostComponent);
const component = fixture.componentInstance;
fixture.detectChanges();
const inputs = fixture.debugElement.queryAll(By.css('input'));
const firstInputEl = inputs[0].nativeElement as HTMLInputElement;
const secondInputEl = inputs[1].nativeElement as HTMLInputElement;
const thirdInputEl = inputs[2].nativeElement as HTMLInputElement;
firstInputEl.value = 'first sync';
firstInputEl.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(firstInputEl.value).toEqual('first sync');
expect(secondInputEl.value).toEqual('first sync');
expect(thirdInputEl.value).toEqual('first sync');
component.syncControls = false;
fixture.detectChanges();
firstInputEl.value = 'second sync';
firstInputEl.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(firstInputEl.value).toEqual('second sync');
expect(secondInputEl.value).not.toEqual('second sync');
expect(thirdInputEl.value).not.toEqual('second sync');
component.syncControls = true;
fixture.detectChanges();
firstInputEl.value = 'third sync';
firstInputEl.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(firstInputEl.value).toEqual('third sync');
expect(secondInputEl.value).toEqual('third sync');
expect(thirdInputEl.value).toEqual('third sync');
});
});
import { Directive, Input, OnDestroy } from '@angular/core';
import { NgControl } from '@angular/forms';
import { Subscription } from 'rxjs';
@Directive({
selector: '[appSyncControl]'
})
export class SyncControlDirective implements OnDestroy {
// tslint:disable-next-line:no-input-rename
@Input('appSyncControl')
set active(val: boolean | undefined) {
if (val === false) {
this.destroySubscription();
} else {
this.createSubscription();
}
}
private subscription?: Subscription;
constructor(private ngControl: NgControl) {}
ngOnDestroy() {
this.destroySubscription();
}
createSubscription() {
if (this.subscription || !this.ngControl || !this.ngControl.control) {
return;
}
this.subscription = this.ngControl.control.valueChanges.subscribe(value => {
if (this.ngControl.valueAccessor) {
this.ngControl.valueAccessor.writeValue(value);
}
});
}
destroySubscription() {
if (this.subscription) {
this.subscription.unsubscribe();
this.subscription = undefined;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment