在Angular 1中,我们可以创建一个指令属性。我们如何使用@Input在Angular 2中做到这一点?文档没有提到它。
例如
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(){
}
}
答案 0 :(得分:41)
签入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
答案 1 :(得分:27)
这是我的getter / setter解决方案。恕我直言,这是一个更优雅的解决方案,因为一切都在一个地方完成,而且这个解决方案并不需要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
的setter将覆盖自身,并将使用传递给attribute的值。否则 - 在组件初始化之后 - 第一次要在类或模板中使用属性a
- 将抛出错误。
注意: getters / setter在Angular的组件/服务等中运行良好,这样使用它们是安全的。但是在Angular之外的纯类中使用这种方法时要小心。问题是typescript converts getters/setters - 如何将它们分配给类的prototype
属性。在这种情况下,我们会改变原型属性,这对于所有类的实例都是相同的。意味着我们可以得到这样的东西:
const instance1 = new ClassStub();
instance1.property = 'some value';
const instance2 = new ClassStub();
console.log(instance2.property); // 'some value'
答案 2 :(得分:9)
正式的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提到的装饰器方法,但由于它适用于Class(因此在TS编译为原型之后)而不是实例,因此我遇到了问题;这意味着装饰器对于一个组件的所有副本只能运行一次,或者至少我找不到一种使其能够在多个实例中工作的方法。
这里是docs for the selector option。请注意,它实际上允许非常灵活的CSS样式选择器选择(甜言蜜语)。
我在Github feature request thread上找到了此建议。
答案 3 :(得分:6)
你可以这样做:
constructor() {}
ngOnInit() {
if (!this.a) throw new Error();
}
答案 4 :(得分:3)
对我来说,我必须这样做:
ngOnInit() {
if(!this.hasOwnProperty('a') throw new Error("Attribute 'a' is required");
}
仅供参考,如果您想要@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");
}
}
答案 5 :(得分:2)
这是另一种基于TypeScript装饰器的方法,它不太复杂且更易于理解。它还支持组件继承。
// Map of component name -> list of required properties
let requiredInputs = new Map<string, string[]>();
/**
* Mark @Input() as required.
*
* Supports inheritance chains for components.
*
* Example:
*
* import { isRequired, checkRequired } from '../requiredInput';
*
* export class MyComp implements OnInit {
*
* // Chain id paramter we check for from the wallet
* @Input()
* @isRequired
* requiredChainId: number;
*
* ngOnInit(): void {
* checkRequired(this);
* }
* }
*
* @param target Object given by the TypeScript decorator
* @param prop Property name from the TypeScript decorator
*/
export function isRequired(target: any, prop: string) {
// Maintain a global table which components require which inputs
const className = target.constructor.name;
requiredInputs[className] = requiredInputs[className] || [];
requiredInputs[className].push(prop);
// console.log(className, prop, requiredInputs[className]);
}
/**
* Check that all required inputs are filled.
*/
export function checkRequired(component: any) {
let className = component.constructor.name;
let nextParent = Object.getPrototypeOf(component);
// Walk through the parent class chain
while(className != "Object") {
for(let prop of (requiredInputs[className] || [])) {
const val = component[prop];
if(val === null || val === undefined) {
console.error(component.constructor.name, prop, "is required, but was not provided, actual value is", val);
}
}
className = nextParent.constructor.name;
nextParent = Object.getPrototypeOf(nextParent);
// console.log("Checking", component, className);
}
}
答案 6 :(得分:1)
我能够在第二个 this
中使用 Object.defineProperty
使 @ihor 的必需装饰器工作。 this
强制装饰器在每个实例上定义属性。
export function Required(message?: string) {
return function (target: Object, propertyKey: PropertyKey) {
Object.defineProperty(target, propertyKey, {
get() {
throw new Error(message || `Attribute ${String(propertyKey)} is required`);
},
set(value) {
Object.defineProperty(this, propertyKey, {
value,
writable: true
});
}
});
};
}
答案 7 :(得分:0)
为什么不使用@angular/forms
库来验证您的@Input
?
解决方法:
@input
值时)用法:
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)}`);
}
}
答案 8 :(得分:0)
一种非常简单和自适应的方式来声明必填字段
许多答案已经显示出这种官方技术。如果要添加多个必需的文件怎么办?然后执行以下操作:
单个必填字段
@Component({
selector: 'my-component[field1]',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.scss']
})
多个必填字段
@Component({
selector: 'my-component[field1][field2][field3]',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.scss']
})
这是在html中使用的方法
<my-component [field1]="value" [field2]="value" [field3]="value"></my-component>