Angular 2使指令需要@Input

时间:2016-02-20 20:07:26

标签: angular angular2-directives

在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(){

  }
}

9 个答案:

答案 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值时)
  • 允许重复使用已用于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)}`);
  }
}

答案 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>