Angular 2使指令需要@Input

Sim*_*lla 35 angular2-directives angular

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

  }
}
Run Code Online (Sandbox Code Playgroud)

Gün*_*uer 44

签入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");
       }
    }
}
Run Code Online (Sandbox Code Playgroud)

您也可以签入ngOnChanges(changes) {...}是否未设置值null.另见https://angular.io/docs/ts/latest/api/core/OnChanges-interface.html


Iho*_*hor 36

这是我的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 }); }
}
Run Code Online (Sandbox Code Playgroud)

解决方案#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 });
    },
  });
}
Run Code Online (Sandbox Code Playgroud)

在您的课程中稍后您只需要按照要求标记您的属性:

Component({
  selector: 'my-dir',
  template: '<div></div>'
})
export class MyComponent {
  @Input() @Required a: number;
}
Run Code Online (Sandbox Code Playgroud)

说明:

如果a定义了属性 - 属性的setter a将覆盖自身,并将使用传递给attribute的值.否则 - 在组件初始化之后 - 第一次要a在类或模板中使用属性时 - 将抛出错误.

注意: getters/setter在Angular的组件/服务等中运行良好,并且可以安全地使用它们.但是在Angular之外的纯类中使用这种方法时要小心.问题是typescript如何转换getter/setter - 它们被分配给prototype类的属性.在这种情况下,我们会改变原型属性,这对于所有类的实例都是相同的.意味着我们可以得到这样的东西:

const instance1 = new ClassStub();
instance1.property = 'some value';
const instance2 = new ClassStub();
console.log(instance2.property); // 'some value'
Run Code Online (Sandbox Code Playgroud)

  • 很好地使用装饰器 - 很好地扩展 (7认同)
  • 如果我将 null/undefined 传递给属性怎么办? (2认同)
  • 同样在这里。“可配置:true”似乎不再起作用 (2认同)
  • 您需要将“configurable: true”标志添加到“Required”装饰器中的父“Object.defineProperty”调用中。否则,它会抛出“无法重新定义”错误。貌似作者漏掉了 (2认同)
  • 即使使用“configurable: true”,装饰器方法似乎也不起作用 (2认同)
  • 建议对官方解决方案进行编辑:添加`!`,使`@Input() a!: number;`。否则,在 Angular 严格模式下,您可能会因为不在构造函数中设置输入值而遇到麻烦。 (2认同)
  • 装饰器/getter-setter 解决方案的另一个缺点是,在读取属性之前,它不会检查属性是否存在。如果仅在边缘情况下读取所需的输入,则使用的开发人员可能永远不会遇到异常,这意味着该属性没有提供假定的安全性。这样一来,它就和正常的类型错误一样好。 (2认同)

Rya*_*avs 18

正式的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() {

    }
}
Run Code Online (Sandbox Code Playgroud)

这样做的好处是,如果开发人员a在模板中引用组件时不包括属性(),则代码不会编译。这意味着编译时安全而不是运行时安全,这很好。

令人遗憾的是,开发人员将收到的错误消息是my-dir不是已知元素”,这不是很清楚。

我尝试了ihor提到的装饰器方法,但由于它适用于Class(因此在TS编译为原型之后)而不是实例,因此我遇到了问题。这意味着装饰器对于一个组件的所有副本仅运行一次,或者至少我找不到使它可用于多个实例的方法。

这是选择器选项文档。请注意,它实际上允许非常灵活的CSS样式选择器选择(甜言蜜语)。

我在Github功能请求线程上找到了此建议。

  • “官方的 Angular 方法是在您的组件的选择器中包含所需的属性”您能否发布对此的引用?我在 Angular 中找不到任何官方可以说明这一点的内容。谢谢! (3认同)
  • @developer033这种方法的问题是错误消息具有误导性。它会让您相信您的组件尚未通过某个模块向 Angular 注册,而事实上,您只是忘记添加必需的属性。我还认为这是“官方 Angular 方式”来做到这一点,只是因为贡献者提到这是“一种”推荐的方式。就像我说的,它会导致抛出极具误导性且难以调试的错误。 (3认同)
  • @developer033 它在答案中字面意思是“官方 Angular 方式”。只是说。我知道这不是你的答案。在 Angular 在他们的风格指南中说明如何做到这一点之前,我将忽略核心团队成员对长期封闭问题的一次性评论。干杯。 (2认同)
  • 实际上,默认情况下输入应该是强制的,以反映它在 TypeScript 中的情况并使其更加快速失败。 (2认同)

Was*_*siF 16

声明必填字段的非常简单且适应性强的方法

许多答案已经展示了这种官方技术。如果您想添加多个必需的文件怎么办?然后执行以下操作:

单个必填字段

@Component({
  selector: 'my-component[field1]',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.scss']
})
Run Code Online (Sandbox Code Playgroud)

多个字段且全部为必填字段

@Component({
  selector: 'my-component[field1][field2][field3]',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.scss']
})
Run Code Online (Sandbox Code Playgroud)

多个字段,但至少需要一个字段

@Component({
  selector: 'my-component[field1], my-component[field2], my-component[field3]',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.scss']
})
Run Code Online (Sandbox Code Playgroud)

下面是在html中使用的方法

<my-component [field1]="value" [field2]="value" [field3]="value"></my-component>
Run Code Online (Sandbox Code Playgroud)

  • 您应该注意,当您在“app.module.ts”中使用“CUSTOM_ELEMENTS_SCHEMA”时,属性选择器(即“my-component[field1]”)的使用不起作用(因为它允许使用任何 HTML 元素) (2认同)

小智 8

从 Angular v16 开始,您可以轻松使用所需的输入功能。只需添加required选项@Input

@Component(...)
export class MyComponent {
  @Input({ required: true }) title: string = '';
}
Run Code Online (Sandbox Code Playgroud)

文档: https: //angular.io/api/core/Input#required


Sas*_*sxa 6

你可以这样做:

constructor() {}
ngOnInit() {
  if (!this.a) throw new Error();
}
Run Code Online (Sandbox Code Playgroud)

  • 这是错误的,因为如果你 *do* 提供值 `0`,这将抛出错误,因为 `0` 是 JS 中的 *falsy* 值之一。测试 `this.a === undefined` 或 `this.a == undefined`(也测试 null)将允许给出 0,并且仍然需要该值。 (3认同)

Ste*_*aul 5

为什么不使用@angular/forms库来验证您的@Inputs? 以下解决方案:

  • 快速失败(不仅仅是当@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()]
        })
      }
    }
Run Code Online (Sandbox Code Playgroud)

实用功能:

    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)}`);
      }
    }
Run Code Online (Sandbox Code Playgroud)

闪电战

  • 很好的解决方案,@Stephen Paul!我正在为内部库寻找一个强大的解决方案,并找到了这个。顺便说一句,我做了一些修改(更少的循环、变量等),你可以检查它[**这里**](https://stackblitz.com/edit/angular-ayeqp1)。感谢您分享这个:) (2认同)