web-dev-qa-db-fra.com

Angular 2 make @Input on directive required

Dans Angular 1, nous pourrions rendre obligatoire un attribut de directive. Comment faire cela dans Angular 2 avec @Input? Les documents ne le mentionnent pas).

Par exemple.

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

  }
}
32
Simon Trewhella

Vérifiez dans ngOnInit() (les entrées ne sont pas encore définies lorsque le constructeur est exécuté) si l'attribut a une valeur.

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");
       }
    }
}

Vous pouvez également vérifier dans ngOnChanges(changes) {...} si les valeurs n'ont pas été définies sur null. Voir aussi https://angular.io/docs/ts/latest/api/core/OnChanges-interface.html

44
Günter Zöchbauer

Voici ma solution avec les getters/setters. À mon humble avis, c'est une solution beaucoup plus élégante car tout est fait en un seul endroit et cette solution ne nécessite pas de dépendance OnInit.

Solution n ° 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 }); }
}

Solution n ° 2 :

Cela pourrait être fait encore plus facilement avec les décorateurs. Donc, vous définissez dans votre application une fois décorateur comme celui-ci:

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 });
    },
  });
}

Et plus tard dans votre classe, il vous suffit de marquer votre propriété comme requis comme ceci:

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

Explication :

Si l'attribut a est défini - le paramètre de la propriété a se substituera à lui-même et la valeur transmise à l'attribut sera utilisée. Sinon - après l'initialisation du composant - la première fois que vous souhaitez utiliser la propriété a dans votre classe ou votre modèle - une erreur sera générée.

Remarque: les getters/setters fonctionnent bien dans les composants/services d'Angular, etc. et il est sûr de les utiliser comme ça. Mais soyez prudent lorsque vous utilisez cette approche avec des classes pures en dehors d'Angular. Le problème est de savoir comment TypeScript convertit les getters/setters - ils sont affectés à la propriété prototype de la classe. Dans ce cas, nous modifions la propriété prototype qui sera la même pour toutes les instances de classe. Signifie que nous pouvons obtenir quelque chose comme ceci:

const instance1 = new ClassStub();
instance1.property = 'some value';
const instance2 = new ClassStub();
console.log(instance2.property); // 'some value'
29
Ihor

La manière officielle Angular pour ce faire est d'inclure les propriétés requises dans le sélecteur de votre composant. Donc, quelque chose comme:

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

    }
}

L'avantage est que si un développeur n'inclut pas la propriété (a) lors du référencement du composant dans son modèle, le code ne sera pas compilé. Cela signifie la sécurité au moment de la compilation au lieu de la sécurité au moment de l'exécution, ce qui est agréable.

Le plus grave est que le message d'erreur que le développeur recevra est "my-dir n'est pas un élément connu ", ce qui n'est pas très clair.

J'ai essayé l'approche décoratrice mentionnée par ihor, et j'ai rencontré des problèmes car elle s'applique à la classe (et donc après la compilation de TS au prototype), pas à l'instance; cela signifiait que le décorateur ne s'exécute qu'une seule fois pour toutes les copies d'un composant, ou du moins je ne pouvais pas trouver un moyen de le faire fonctionner pour plusieurs instances.

Voici les documents pour l'option sélecteur . Notez qu'il permet en fait une sélection très flexible de style CSS (sweet Word).

J'ai trouvé cette recommandation sur un thread de demande de fonctionnalité Github .

14
Ryan Miglavs

Vous pouvez le faire comme ceci:

constructor() {}
ngOnInit() {
  if (!this.a) throw new Error();
}
6
Sasxa

Pour moi, je devais le faire de cette façon:

ngOnInit() { if(!this.hasOwnProperty('a') throw new Error("Attribute 'a' is required"); }

Pour info, si vous souhaitez exiger des directives @Output, essayez ceci:

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");
    }
}
3
Ulfius