Angular 2 make @Input по директиве требуется
В Angular 1 мы могли бы сделать обязательным атрибут директивы. Как мы это делаем в Angular 2 с @Input? Документы не упоминают об этом.
Например.
Component({
selector: 'my-dir',
template: '<div></div>'
})
export class MyComponent {
@Input() a:number; // Make this a required attribute. Throw an exception if it doesnt exist
@Input() b:number;
constructor(){
}
}
Ответы
Ответ 1
Проверьте в ngOnInit()
(входные данные еще не установлены при выполнении конструктора), имеет ли атрибут значение.
Component({
selector: 'my-dir',
template: '<div></div>'
})
export class MyComponent implements OnInit, OnChanges {
@Input() a:number; // Make this a required attribute. Throw an exception if it doesnt exist
@Input() b:number;
constructor(){
}
ngOnInit() {
this.checkRequiredFields(this.a);
}
ngOnChanges(changes) {
this.checkRequiredFields(this.a);
}
checkRequiredFields(input) {
if(input === null) {
throw new Error("Attribute 'a' is required");
}
}
}
Вы также можете проверить в ngOnChanges(changes) {...}
, не были ли установлены значения null
. Смотрите также https://angular.io/docs/ts/latest/api/core/OnChanges-interface.html
Ответ 2
Вот мое решение с геттерами/сеттерами. ИМХО, это гораздо более элегантное решение, так как все делается в одном месте, и это решение не требует зависимости OnInit
.
Решение № 1
Component({
selector: 'my-dir',
template: '<div></div>'
})
export class MyComponent {
@Input()
get a () { throw new Error('Attribute "a" is required'); }
set a (value: number) { Object.defineProperty(this, 'a', { value, writable: true, configurable: true }); }
}
Решение № 2:
С декораторами это можно сделать еще проще. Итак, вы определяете в своем приложении один раз декоратор, как этот:
function Required(target: object, propertyKey: string) {
Object.defineProperty(target, propertyKey, {
get () {
throw new Error('Attribute ${propertyKey} is required');
},
set (value) {
Object.defineProperty(target, propertyKey, { value, writable: true, configurable: true });
},
});
}
И позже в вашем классе вам просто нужно пометить вашу собственность как требуется так:
Component({
selector: 'my-dir',
template: '<div></div>'
})
export class MyComponent {
@Input() @Required a: number;
}
Пояснение:
Если атрибут a
определен - установщик свойства a
переопределит себя, и будет использовано значение, переданное атрибуту. В противном случае - после инициализации компонента - в первый раз, когда вы захотите использовать свойство a
в своем классе или шаблоне - будет выдано сообщение об ошибке.
Примечание: геттеры/сеттеры хорошо работают в Angular-компонентах/сервисах и т.д., И их можно безопасно использовать таким образом. Но будьте осторожны при использовании этого подхода с чистыми классами вне Angular. Проблема заключается в том, как машинопись преобразует геттеры/сеттеры - они присваиваются свойству prototype
класса. В этом случае мы изменяем свойство прототипа, которое будет одинаковым для всех экземпляров класса. Значит, мы можем получить что-то вроде этого:
const instance1 = new ClassStub();
instance1.property = 'some value';
const instance2 = new ClassStub();
console.log(instance2.property); // 'some value'
Ответ 3
Официальный способ Angular сделать это - включить необходимые свойства в селекторе для вашего компонента. Итак, что-то вроде:
Component({
selector: 'my-dir[a]', // <-- Check it
template: '<div></div>'
})
export class MyComponent {
@Input() a:number; // This property is required by virtue of the selector above
@Input() b:number; // This property is still optional, but could be added to the selector to require it
constructor(){
}
ngOnInit() {
}
}
Преимущество этого заключается в том, что если разработчик не включает свойство (a
) при ссылке на компонент в своем шаблоне, код не будет компилироваться. Это означает безопасность во время компиляции вместо безопасности во время выполнения, что приятно.
Облом - то, что разработчик получит сообщение об ошибке "my-dir
не является известным элементом", что не совсем ясно.
Я попробовал подход декоратора, упомянутый ihor, и столкнулся с проблемами, поскольку он применяется к классу (и, следовательно, после компиляции TS для прототипа), а не к экземпляру; это означало, что декоратор запускается только один раз для всех копий компонента, или, по крайней мере, я не смог найти способ заставить его работать для нескольких экземпляров.
Вот документы для выбора селектора. Обратите внимание, что на самом деле он позволяет очень гибко выбирать стиль в стиле CSS (приятное слово).
Я нашел эту рекомендацию в ветке запроса функции Github.
Ответ 4
Вы можете сделать это следующим образом:
constructor() {}
ngOnInit() {
if (!this.a) throw new Error();
}
Ответ 5
Для меня я должен был сделать это следующим образом:
ngOnInit() {
if(!this.hasOwnProperty('a') throw new Error("Attribute 'a' is required");
}
FYI. Если вы хотите использовать директивы @Output, попробуйте следующее:
export class MyComponent {
@Output() myEvent = new EventEmitter(); // This a required event
ngOnInit() {
if(this.myEvent.observers.length === 0) throw new Error("Event 'myEvent' is required");
}
}
Ответ 6
Почему бы не использовать библиотеку @angular/forms
для проверки ваших @Input
?
Следующее решение:
- Быстро завершается неудачей (не только при первом обращении компонента к значению
@input
)
- Позволяет повторно использовать правила, которые вы уже использовали для своих angular форм
Использование:
export class MyComponent {
@Input() propOne: string;
@Input() propTwo: string;
ngOnInit() {
validateProps<MyComponent>(this, {
propOne: [Validators.required, Validators.pattern('[a-zA-Z ]*')],
propTwo: [Validators.required, Validators.minLength(5), myCustomRule()]
})
}
}
Сервисная функция:
import { FormArray, FormBuilder, ValidatorFn, FormControl } from '@angular/forms';
export function validateProps<T>(cmp: T, ruleset: {[key in keyof T]?: ValidatorFn[]} ) {
const toGroup = {};
Object.keys(ruleset)
.forEach(key => toGroup[key] = new FormControl(cmp[key], ruleset[key]));
const formGroup = new FormBuilder().group(toGroup);
formGroup.updateValueAndValidity();
const validationResult = {};
Object.keys(formGroup.controls)
.filter(key => formGroup.controls[key].errors)
.forEach(key => validationResult[key] = formGroup.controls[key].errors);
if (Object.keys(validationResult).length) {
throw new Error('Input validation failed:\n ${JSON.stringify(validationResult, null, 2)}');
}
}