Nous développons des composants et lors de leur utilisation, nous aimerions utiliser le même mécanisme que pour les nœuds DOM pour définir conditionnellement les attributs. Donc, pour empêcher les attributs de s'afficher du tout, nous définissons la valeur sur null et elle n'existe pas dans la sortie HTML finale. Génial!
_type: string = 'default'; @Input() set type(v: string) {if (v !== null) this._type = v;} get type() { return this._type; }
Maintenant, lorsque vous utilisez nos propres composants, cela ne fonctionne pas. Lorsque nous définissons null
, nous obtenons en fait null
dans les composants @Input comme valeur. Toute valeur définie par défaut sera écrasée.
<myElement #myelem [type]="condition ? 'something' : myelem.type"></myElement>
<myElment [type]="condition ? 'something' : null"></myElement>
Ainsi, chaque fois que nous lisons le type
dans le composant, nous obtenons null
au lieu de la 'default'
qui a été définie.
J'ai essayé de trouver un moyen d'obtenir la valeur par défaut d'origine, mais je ne l'ai pas trouvée. Il existe dans le ngBaseDef
lors de l'accès au temps du constructor
, mais cela ne fonctionne pas en production. Je m'attendais à ce que ngOnChanges
me donne la valeur réelle (par défaut) dans le premier changement effectué et puisse donc empêcher que null
soit défini, mais la valeur previousValue
n'est undefined
.
Nous avons trouvé des moyens de résoudre ce problème:
default
et définir pour chaque entrée la valeur par défaut lorsqu'elle est null
... @Component({ selector: 'myElement', templateUrl: './my-element.component.html' }) export class MyElementComponent { @Input() type: string = 'default'; ...
<button [attr.disabled]="condition ? true : null"></button>
mais je suis curieux de savoir s'il y en a peut-être d'autres qui ont des problèmes similaires et comment cela a été résolu. J'apprécierais également toute autre idée peut-être plus élégante.
Merci!
4 Réponses :
Vous pouvez utiliser l'opérateur ternaire dans votre composant parent comme suit:
<myElment [attr.type]="condition ? 'something' : null"></myElement>
Cela ne transmettra une valeur au composant enfant que si votre condition est remplie, sinon votre valeur d'initialisation ne sera pas écrasée
Il n'est pas nécessaire d'avoir de code supplémentaire dans le composant enfant
Le problème avec ceci est que vous pouvez perdre la capacité de peluchage
Comme vous le voyez dans ma question, nous savons comment faire cela pour les éléments DOM. Mais cela ne fonctionnera pas pour les composants, car cela définira l'attribut DOM de myElement au lieu de la valeur d'entrée.
Il n'y a pas de méthode angulaire standard, car vous voudriez souvent que la null
soit null
ou undefined
. Vos idées ne sont pas de mauvaises solutions. J'en ai quelques autres
ngOnChanges
pour cela:@Input() @Default('defaultType') type!: string;
Observables
:function Default(value: any) { return function(target: any, key: string | symbol) { const valueAccessor = '__' + key.toString() + '__'; Object.defineProperty(target, key, { get: function () { return this[valueAccessor] != null ? this[valueAccessor] : value }, set: function (next) { if (!Object.prototype.hasOwnProperty.call(this, valueAccessor)) { Object.defineProperty(this, valueAccessor, { writable: true, enumerable: false }); } this[valueAccessor] = next; }, enumerable: true }); }; }
private readonly _type$ = new BehaviorSubject('defaultType'); readonly type$ = this._type$.pipe( map((type) => type == null ? 'defaultType' : type) ); @Input() set type(type: string) { this._type$.next(type); }
que vous pouvez utiliser comme ceci:
@Input() type: string = 'defaultType'; ngOnChanges(changes: SimpleChanges): void { // == null to also match undefined if (this.type == null) { this.type = 'defaultType'; } }
La troisième solution semble très prometteuse. Malheureusement, cela ne fonctionne pas dès que vous avez un composant plus d'une fois. Tous les composants recevront le même ensemble de types de l'extérieur, même si certains ou un sont nuls, il obtiendra le type de l'autre composant. Comme cela semble le plus prometteur, je vais regarder plus loin et voir où est (j'ai fait) l'erreur :) Merci!
@Fabian Je n'aurais pas dû publier de code non testé :) de toute façon, j'ai mis à jour ma réponse. Nous devons évidemment utiliser une propriété d'accesseur de valeur, afin qu'elle soit différente pour chaque instance.
J'ai déjà une version de travail ensemble par moi-même, merci beaucoup pour la mise à jour. Maintenant, cela fonctionne comme un charme! ;) Merci pour l'idée de créer ses propres décorateurs.
@Fabian Vous êtes le bienvenu :) comment avez-vous réussi à le faire fonctionner pour plusieurs instances de classe?
@Fabian J'ai un peu mis à jour ma réponse du décorateur, car j'ai remarqué que dans une boucle for .. in
, il afficherait également la propriété accesseur de valeur. En le déclarant directement dans this.set
et en définissant enumerable sur false, il this.set
plus.
J'ai adopté l'approche décoratrice, vérifiez mon exemple de stackblitz avec des tests décorateurs
@Nexaddo votre décorateur échouera lorsque vous aurez deux composants avec des valeurs différentes. J'ai bifurqué votre stackblitz ici . Comme vous pouvez le voir, l'indéfini est écrasé par la valeur définie du 2ème composant. Je l'ai d'abord eu comme vous, mais j'ai dû le changer pour utiliser une valeur masquée valueAccessor
:)
Vous avez raison, le problème semble être dû au fait que les décorateurs de propriétés ne sont initialisés qu'une seule fois par application entière et non par classe. donc la variable val
dans le décorateur ne contiendra que la dernière valeur modifiée. Nous avons besoin que la classe détienne la valeur pour que cela fonctionne. Merci pour les commentaires @PoulKruijt, j'ai mis à jour mon exemple stackblitz avec un nouveau test pour vérifier ce problème
Si vous devez le faire pour plusieurs entrées, vous pouvez également utiliser un pipe
personnalisé au lieu de définir manuellement le getter / setter et la valeur par défaut pour chaque entrée. Le pipe
peut contenir la logique et defaultArg pour renvoyer defaultArg si l'entrée est null
.
c'est à dire
<!-- myElem template --> <p>Type Input: {{ type | ifNotNullElse: 'default' }}</p> <p>Another Input: {{ anotherType | ifNotNullElse: 'anotherDefault' }}</p>
// pipe @Pipe({name: 'ifNotNullElse'}) export class IfNotNullElsePipe implements PipeTransform { transform(value: string, defaultVal?: string): string { return value !== null ? value : defaultVal; } }
Encore une option (peut-être plus simple si vous ne voulez pas implémenter votre propre @annotation personnalisée) basée sur la solution Paul Krujit:
export class MyElementComponent { typeWrapped = 'default'; @Input() set type(selected: string) { // this makes sure only truthy values get assigned // so [type]="null" or [type]="undefined" still go to the default. if (selected) { this.typeWrapped = selected; } } get type() { return this.typeWrapped; } }
le problème avec cela, si vous avez d'abord une valeur sur l'entrée, puis définissez cette valeur sur null
ou undefined
cela ne changera pas la propriété typeWrapped
et votre valeur par défaut a disparu.
duplication possible de stackoverflow.com/questions/35839120/… et stackoverflow.com/questions/36071942/…