Angular 2 사용자 지정 양식 입력
네이티브 <input>
태그 처럼 작동하는 사용자 지정 구성 요소를 어떻게 만들 수 있습니까? 사용자 지정 양식 컨트롤이 ngControl, ngForm, [(ngModel)]을 지원할 수 있도록 만들고 싶습니다.
알다시피, 고유 한 폼 컨트롤이 네이티브 컨트롤처럼 작동하도록하려면 몇 가지 인터페이스를 구현해야합니다.
또한 ngForm 지시문 <input>
이 태그 에만 바인딩되는 것 같습니다. 맞 습니까? 어떻게 처리 할 수 있습니까?
왜 이것이 필요한지 설명하겠습니다. 여러 입력 요소를 래핑하여 단일 입력으로 함께 작동 할 수 있도록하고 싶습니다. 그것을 처리하는 다른 방법이 있습니까? 한 번 더 :이 컨트롤을 네이티브 컨트롤처럼 만들고 싶습니다. 유효성 검사, ngForm, ngModel 양방향 바인딩 및 기타.
추신 : Typescript를 사용합니다.
실제로 구현해야 할 두 가지가 있습니다.
- 양식 구성 요소의 논리를 제공하는 구성 요소입니다.
ngModel
자체적 으로 제공되므로 입력이 아닙니다. ControlValueAccessor
이 구성 요소와ngModel
/ 사이의 브리지를 구현할 사용자 지정ngControl
샘플을 봅시다. 회사의 태그 목록을 관리하는 구성 요소를 구현하고 싶습니다. 구성 요소는 태그를 추가 및 제거 할 수 있습니다. 태그 목록이 비어 있지 않은지 확인하기 위해 유효성 검사를 추가하고 싶습니다. 아래에 설명 된대로 구성 요소에서 정의합니다.
(...)
import {TagsComponent} from './app.tags.ngform';
import {TagsValueAccessor} from './app.tags.ngform.accessor';
function notEmpty(control) {
if(control.value == null || control.value.length===0) {
return {
notEmpty: true
}
}
return null;
}
@Component({
selector: 'company-details',
directives: [ FormFieldComponent, TagsComponent, TagsValueAccessor ],
template: `
<form [ngFormModel]="companyForm">
Name: <input [(ngModel)]="company.name"
[ngFormControl]="companyForm.controls.name"/>
Tags: <tags [(ngModel)]="company.tags"
[ngFormControl]="companyForm.controls.tags"></tags>
</form>
`
})
export class DetailsComponent implements OnInit {
constructor(_builder:FormBuilder) {
this.company = new Company('companyid',
'some name', [ 'tag1', 'tag2' ]);
this.companyForm = _builder.group({
name: ['', Validators.required],
tags: ['', notEmpty]
});
}
}
TagsComponent
구성 요소를 추가하고 요소 제거하는 로직 정의 tags
목록을.
@Component({
selector: 'tags',
template: `
<div *ngIf="tags">
<span *ngFor="#tag of tags" style="font-size:14px"
class="label label-default" (click)="removeTag(tag)">
{{label}} <span class="glyphicon glyphicon-remove"
aria- hidden="true"></span>
</span>
<span> | </span>
<span style="display:inline-block;">
<input [(ngModel)]="tagToAdd"
style="width: 50px; font-size: 14px;" class="custom"/>
<em class="glyphicon glyphicon-ok" aria-hidden="true"
(click)="addTag(tagToAdd)"></em>
</span>
</div>
`
})
export class TagsComponent {
@Output()
tagsChange: EventEmitter;
constructor() {
this.tagsChange = new EventEmitter();
}
setValue(value) {
this.tags = value;
}
removeLabel(tag:string) {
var index = this.tags.indexOf(tag, 0);
if (index != undefined) {
this.tags.splice(index, 1);
this.tagsChange.emit(this.tags);
}
}
addLabel(label:string) {
this.tags.push(this.tagToAdd);
this.tagsChange.emit(this.tags);
this.tagToAdd = '';
}
}
보시다시피이 구성 요소에는 입력이 없지만 setValue
하나입니다 (여기서 이름은 중요하지 않음). 나중에이 값을 사용하여 ngModel
에서 구성 요소로 값을 제공합니다 . 이 구성 요소는 구성 요소 (태그 목록)의 상태가 업데이트 될 때 알리는 이벤트를 정의합니다.
이제이 구성 요소와 ngModel
/ 간의 링크를 구현해 보겠습니다 ngControl
. 이는 ControlValueAccessor
인터페이스 를 구현하는 지시문에 해당합니다 . 이 값 접근 자에 대해 NG_VALUE_ACCESSOR
토큰 에 대해 공급자를 정의해야합니다 ( forwardRef
지시문이 이후에 정의되므로 사용하는 것을 잊지 마십시오 ).
지시문은 tagsChange
호스트 의 이벤트에 이벤트 리스너를 첨부합니다 (예 : 지시문이 첨부 된 구성 요소, 즉 TagsComponent
). onChange
이벤트가 발생할 때 메서드가 호출됩니다. 이 방법은 Angular2에서 등록한 방법에 해당합니다. 이렇게하면 연관된 양식 컨트롤에 따라 변경 및 업데이트를 인식합니다.
에 writeValue
바인딩 된 값 ngForm
이 업데이트 되면이 호출 됩니다. 첨부 된 컴포넌트 (예 : TagsComponent)를 삽입 한 후이 값을 전달하기 위해 호출 할 수 있습니다 (이전 setValue
메서드 참조 ).
CUSTOM_VALUE_ACCESSOR
지시문의 바인딩에 를 제공하는 것을 잊지 마십시오 .
다음은 사용자 정의의 전체 코드입니다 ControlValueAccessor
.
import {TagsComponent} from './app.tags.ngform';
const CUSTOM_VALUE_ACCESSOR = CONST_EXPR(new Provider(
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => TagsValueAccessor), multi: true}));
@Directive({
selector: 'tags',
host: {'(tagsChange)': 'onChange($event)'},
providers: [CUSTOM_VALUE_ACCESSOR]
})
export class TagsValueAccessor implements ControlValueAccessor {
onChange = (_) => {};
onTouched = () => {};
constructor(private host: TagsComponent) { }
writeValue(value: any): void {
this.host.setValue(value);
}
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}
이렇게 tags
하면 회사를 모두 제거 valid
하면 companyForm.controls.tags
제어 속성 이 false
자동으로 나타납니다.
자세한 내용은이 문서 ( "NgModel 호환 구성 요소"섹션)를 참조하십시오.
인터넷에서 찾은 모든 예가 왜 그렇게 복잡해야하는지 이해가 안 돼요. 새로운 개념을 설명 할 때 가능한 가장 간단하고 실제적인 예제를 사용하는 것이 항상 최선이라고 생각합니다. 나는 그것을 약간 증류했습니다.
ngModel을 구현하는 구성 요소를 사용하는 외부 양식 용 HTML :
EmailExternal=<input [(ngModel)]="email">
<inputfield [(ngModel)]="email"></inputfield>
자체 포함 된 구성 요소 (별도의 '접근 자'클래스 없음-요점이 누락되었을 수 있음) :
import {Component, Provider, forwardRef, Input} from "@angular/core";
import {ControlValueAccessor, NG_VALUE_ACCESSOR, CORE_DIRECTIVES} from "@angular/common";
const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => InputField),
multi: true
});
@Component({
selector : 'inputfield',
template: `<input [(ngModel)]="value">`,
directives: [CORE_DIRECTIVES],
providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class InputField implements ControlValueAccessor {
private _value: any = '';
get value(): any { return this._value; };
set value(v: any) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}
writeValue(value: any) {
this._value = value;
this.onChange(value);
}
onChange = (_) => {};
onTouched = () => {};
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}
사실, 저는이 모든 것을 추상 클래스로 추상화했습니다. 이제 ngModel을 사용하는 데 필요한 모든 구성 요소로 확장합니다. 나에게 이것은 내가 없이도 할 수있는 엄청난 양의 오버 헤드와 상용구 코드이다.
편집 : 여기 있습니다 :
import { forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
export abstract class AbstractValueAccessor implements ControlValueAccessor {
_value: any = '';
get value(): any { return this._value; };
set value(v: any) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}
writeValue(value: any) {
this._value = value;
// warning: comment below if only want to emit on user intervention
this.onChange(value);
}
onChange = (_) => {};
onTouched = () => {};
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}
export function MakeProvider(type : any){
return {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => type),
multi: true
};
}
다음은이를 사용하는 구성 요소입니다. (TS) :
import {Component, Input} from "@angular/core";
import {CORE_DIRECTIVES} from "@angular/common";
import {AbstractValueAccessor, MakeProvider} from "../abstractValueAcessor";
@Component({
selector : 'inputfield',
template: require('./genericinput.component.ng2.html'),
directives: [CORE_DIRECTIVES],
providers: [MakeProvider(InputField)]
})
export class InputField extends AbstractValueAccessor {
@Input('displaytext') displaytext: string;
@Input('placeholder') placeholder: string;
}
HTML :
<div class="form-group">
<label class="control-label" >{{displaytext}}</label>
<input [(ngModel)]="value" type="text" placeholder="{{placeholder}}" class="form-control input-md">
</div>
RC5 버전에 대한이 링크에 예제가 있습니다. http://almerosteyn.com/2016/04/linkup-custom-control-to-ngcontrol-ngmodel
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
const noop = () => {
};
export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
};
@Component({
selector: 'custom-input',
template: `<div class="form-group">
<label>
<ng-content></ng-content>
<input [(ngModel)]="value"
class="form-control"
(blur)="onBlur()" >
</label>
</div>`,
providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class CustomInputComponent implements ControlValueAccessor {
//The internal data model
private innerValue: any = '';
//Placeholders for the callbacks which are later providesd
//by the Control Value Accessor
private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;
//get accessor
get value(): any {
return this.innerValue;
};
//set accessor including call the onchange callback
set value(v: any) {
if (v !== this.innerValue) {
this.innerValue = v;
this.onChangeCallback(v);
}
}
//Set touched on blur
onBlur() {
this.onTouchedCallback();
}
//From ControlValueAccessor interface
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
//From ControlValueAccessor interface
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
//From ControlValueAccessor interface
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
}
그런 다음이 사용자 지정 컨트롤을 다음과 같이 사용할 수 있습니다.
<form>
<custom-input name="someValue"
[(ngModel)]="dataModel">
Enter data:
</custom-input>
</form>
Thierry의 예가 도움이됩니다. 다음은 TagsValueAccessor를 실행하는 데 필요한 가져 오기입니다.
import {Directive, Provider} from 'angular2/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR } from 'angular2/common';
import {CONST_EXPR} from 'angular2/src/facade/lang';
import {forwardRef} from 'angular2/src/core/di';
@ViewChild 지시문으로도이 문제를 해결할 수 있습니다. 이것은 부모에게 주입 된 자식의 모든 멤버 변수와 함수에 대한 전체 액세스를 제공합니다.
참조 : 삽입 된 양식 구성 요소의 입력 필드에 액세스하는 방법
I wrote a library that helps reduce some boilerplate for this case: s-ng-utils
. Some of the other answers are giving example of wrapping a single form control. Using s-ng-utils
that can be done very simply using WrappedFormControlSuperclass
:
@Component({
template: `
<!-- any fancy wrapping you want in the template -->
<input [formControl]="formControl">
`,
providers: [provideValueAccessor(StringComponent)],
})
class StringComponent extends WrappedFormControlSuperclass<string> {
// This looks unnecessary, but is required for Angular to provide `Injector`
constructor(injector: Injector) {
super(injector);
}
}
In your post you mention that you want to wrap multiple form controls into a single component. Here is a full example doing that with FormControlSuperclass
.
import { Component, Injector } from "@angular/core";
import { FormControlSuperclass, provideValueAccessor } from "s-ng-utils";
interface Location {
city: string;
country: string;
}
@Component({
selector: "app-location",
template: `
City:
<input
[ngModel]="location.city"
(ngModelChange)="modifyLocation('city', $event)"
/>
Country:
<input
[ngModel]="location.country"
(ngModelChange)="modifyLocation('country', $event)"
/>
`,
providers: [provideValueAccessor(LocationComponent)],
})
export class LocationComponent extends FormControlSuperclass<Location> {
location!: Location;
// This looks unnecessary, but is required for Angular to provide `Injector`
constructor(injector: Injector) {
super(injector);
}
handleIncomingValue(value: Location) {
this.location = value;
}
modifyLocation<K extends keyof Location>(field: K, value: Location[K]) {
this.location = { ...this.location, [field]: value };
this.emitOutgoingValue(this.location);
}
}
You can then use <app-location>
with [(ngModel)]
, [formControl]
, custom validators - everything you can do with the controls Angular supports out of the box.
Why to create a new value accessor when you can use the inner ngModel. Whenever you are creating a custom component which has an input[ngModel] in it, we already are instantiating an ControlValueAccessor. And that's the accessor we need.
template:
<div class="form-group" [ngClass]="{'has-error' : hasError}">
<div><label>{{label}}</label></div>
<input type="text" [placeholder]="placeholder" ngModel [ngClass]="{invalid: (invalid | async)}" [id]="identifier" name="{{name}}-input" />
</div>
Component:
export class MyInputComponent {
@ViewChild(NgModel) innerNgModel: NgModel;
constructor(ngModel: NgModel) {
//First set the valueAccessor of the outerNgModel
this.outerNgModel.valueAccessor = this.innerNgModel.valueAccessor;
//Set the innerNgModel to the outerNgModel
//This will copy all properties like validators, change-events etc.
this.innerNgModel = this.outerNgModel;
}
}
Use as:
<my-input class="col-sm-6" label="First Name" name="firstname"
[(ngModel)]="user.name" required
minlength="5" maxlength="20"></my-input>
This is quite easy to do with ControlValueAccessor
NG_VALUE_ACCESSOR
.
You can read this article to make a simple custom field Create Custom Input Field Component with Angular
참고URL : https://stackoverflow.com/questions/34948961/angular-2-custom-form-input
'IT박스' 카테고리의 다른 글
Mac OS X Lion의 git (0) | 2020.10.07 |
---|---|
Node.js 테스트 모듈 인 Mocha를 어떻게 설치하고 실행합니까? (0) | 2020.10.07 |
Layman의 관점에서 Pumping Lemma는 무엇입니까? (0) | 2020.10.07 |
Rails 내에서 루비 버전 확인 (0) | 2020.10.07 |
HTML5 / Canvas는 이중 버퍼링을 지원합니까? (0) | 2020.10.07 |