英文:
Reusable component with input property is is not rendering correctly on parent component if used multiple times
问题
I have created a reusable component (star rating) and I am planning to use it at multiple places to rate different things, it is working fine when I use the reusable component only once but it is not getting rendered when I use it multiple times on a component.
as you might have guessed, the reusable component (star rating component) has stars which will be highlighted when the @input
property has some value in it, for e.g if @input
property has 3 then 3 stars will be highlighted and if 4 then 4 stars.
when I use it at a place where the reusable component (star rating component) is used multiple times and if the different component has different value if @input
property only last is considered.
I have made a mini demo of that and pasting the code of that to understand
rating.component.html
<div class="rating-wrapper">
<!-- star 5 -->
<input type="radio" id="5-star-rating" name="star-rating" value="5" [checked]="starrating === '5'" (change)="onRatingChange($event.target)" [(ngModel)]="starrating">
<label for="5-star-rating" class="star-rating">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 4 -->
<input type="radio" id="4-star-rating" name="star-rating" value="4" [checked]="starrating === '4'" (change)="onRatingChange($event.target)" [(ngModel)]="starrating">
<label for="4-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 3 -->
<input type="radio" id="3-star-rating" name="star-rating" value="3" [checked]="starrating === '3'" (change)="onRatingChange($event.target)" [(ngModel)]="starrating">
<label for="3-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 2 -->
<input type="radio" id="2-star-rating" name="star-rating" value="2" [checked]="starrating === '2'" (change)="onRatingChange($event.target)" [(ngModel)]="starrating">
<label for="2-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 1 -->
<input type="radio" id="1-star-rating" name="star-rating" value="1" [checked]="starrating === '1'" (change)="onRatingChange($event.target)" [(ngModel)]="starrating">
<label for="1-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
</div>
rating.component.css
.container-wrapper {
background-color: #EDF0F9;
}
.container {
height: 100vh;
}
.rating-wrapper {
align-self: center;
/* box-shadow: 7px 7px 25px rgba(198, 206, 237, 0.7), -7px -7px 35px rgba(255, 255, 255, 0.7), inset 0px 0px 4px rgba(255, 255, 255, 0.9), inset 7px 7px 15px rgba(198, 206, 237, 0.8); */
border-radius: 4rem;
display: inline-flex;
direction: rtl !important;
padding: 1.5rem 2.5rem;
margin-left: auto;
}
.rating-wrapper label {
color: #E1E6F6;
cursor: pointer;
display: inline-flex;
font-size: 2.1rem;
padding: 1rem 0.6rem;
transition: color 0.5s;
}
.rating-wrapper svg {
-webkit-text-fill-color: transparent;
-webkit-filter: drop-shadow 4px 1px 6px #c6ceed;
filter: drop-shadow(5px 1px 3px #c6ceed);
}
.rating-wrapper input {
height: 100%;
width: 100%;
}
.rating-wrapper input {
display: none;
}
.rating-wrapper label:hover,
.rating-wrapper label:hover ~ label,
.rating-wrapper input:checked ~ label {
color: #34AC9E;
}
.rating-wrapper label:hover,
.rating-wrapper label:hover ~ label,
.rating-wrapper input:checked ~ label {
color: #34AC9E;
}
rating.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-rating',
templateUrl: './rating.component.html',
styleUrls: ['./rating.component.css']
})
export class RatingComponent {
constructor(){}
@Input() starrating:string='3'; //<== this will get value and the stars will be highlighted
onRatingChange(val:any){
//blah blah blah
}
}
when I use it in my other component it is not getting rendered
Manytimes.Component.html
<div *ngFor="let item of arrayResponse">
<img
src="assets\150.png"
class="img-fluid rounded-circle mb-3"
alt="coach image"
/> <br>
<div>{{item.name}}</div>
<app-rating [starrating]="item?.rating"></app-rating> // here I am passing the value of how many star should get highlighted
</div>
Manytimes.Component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-manytimes',
templateUrl: './manytimes.component.html',
styleUrls: ['./manytimes.component.css']
})
export class ManytimesComponent {
// just for demo the below response is hard coded, but actually it will come from api response
arrayResponse:any=[
{
"name":"ram",
"rating":"3"
},
{
"name":"Dinesh",
"rating":"1"
},
{
"name":"ganesh",
"rating":"2"
},
]
}
when this is renderd in my app I am expecting the first should have 3 star highlighted, then 1 star and the last should have 2 star hightlighted
but only the last is highlighted with 2 star and all other are blanks
英文:
I have created a reusable component (star rating) and I am planning to use it at multiple places to rate different things, it is working fine when I use the reusable component only once but it is not getting rendered when I use it multiple times on a component.
as you might have guessed, the reusable component (star rating component) has stars which will be highlighted when the @input
property has some value in it, for e.g if @input
property has 3 then 3 stars will be highlighted and if 4 then 4 stars.
when I use it at a place where the reusable component (star rating component) is used multiple times and if the different component has different value if @input
property only last is considered.
I have made a mini demo of that and pasting the code of that to understand
rating.component.html
<div class="rating-wrapper">
<!-- star 5 -->
<input type="radio" id="5-star-rating" name="star-rating" value="5" [checked]="starrating === '5'" (change)="onRatingChange($event.target)" [(ngModel)]="starrating">
<label for="5-star-rating" class="star-rating">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 4 -->
<input type="radio" id="4-star-rating" name="star-rating" value="4" [checked]="starrating === '4'" (change)="onRatingChange($event.target)" [(ngModel)]="starrating" >
<label for="4-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 3 -->
<input type="radio" id="3-star-rating" name="star-rating" value="3" [checked]="starrating === '3'" (change)="onRatingChange($event.target)" [(ngModel)]="starrating">
<label for="3-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 2 -->
<input type="radio" id="2-star-rating" name="star-rating" value="2" [checked]="starrating === '2'" (change)="onRatingChange($event.target)" [(ngModel)]="starrating">
<label for="2-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 1 -->
<input type="radio" id="1-star-rating" name="star-rating" value="1" [checked]="starrating === '1'" (change)="onRatingChange($event.target)" [(ngModel)]="starrating">
<label for="1-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
</div>
rating.component.css
.container-wrapper {
background-color: #EDF0F9;
}
.container {
height: 100vh;
}
.rating-wrapper {
align-self: center;
/* box-shadow: 7px 7px 25px rgba(198, 206, 237, 0.7), -7px -7px 35px rgba(255, 255, 255, 0.7), inset 0px 0px 4px rgba(255, 255, 255, 0.9), inset 7px 7px 15px rgba(198, 206, 237, 0.8); */
border-radius: 4rem;
display: inline-flex;
direction: rtl !important;
padding: 1.5rem 2.5rem;
margin-left: auto;
}
.rating-wrapper label {
color: #E1E6F6;
cursor: pointer;
display: inline-flex;
font-size: 2.1rem;
padding: 1rem 0.6rem;
transition: color 0.5s;
}
.rating-wrapper svg {
-webkit-text-fill-color: transparent;
-webkit-filter: drop-shadow 4px 1px 6px #c6ceed;
filter: drop-shadow(5px 1px 3px #c6ceed);
}
.rating-wrapper input {
height: 100%;
width: 100%;
}
.rating-wrapper input {
display: none;
}
.rating-wrapper label:hover,
.rating-wrapper label:hover ~ label,
.rating-wrapper input:checked ~ label {
color: #34AC9E;
}
.rating-wrapper label:hover,
.rating-wrapper label:hover ~ label,
.rating-wrapper input:checked ~ label {
color: #34AC9E;
}
rating.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-rating',
templateUrl: './rating.component.html',
styleUrls: ['./rating.component.css']
})
export class RatingComponent {
constructor(){}
@Input() starrating:string='3'; //<== this will get value and the stars will be highlighted
onRatingChange(val:any){
//blah blah blah
}
}
when I use it in my other component it is not getting rendered
Manytimes.Component.html
<div *ngFor="let item of arrayResponse">
<img
src="assets\150.png"
class="img-fluid rounded-circle mb-3"
alt="coach image"
/> <br>
<div>{{item.name}}</div>
<app-rating [starrating]="item?.rating"></app-rating> // here I am passing the value of how many star should get highlighted
</div>
Manytimes.Component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-manytimes',
templateUrl: './manytimes.component.html',
styleUrls: ['./manytimes.component.css']
})
export class ManytimesComponent {
// just for demo the below response is hard coded, but actually it will come from api response
arrayResponse:any=[
{
"name":"ram",
"rating":"3"
},
{
"name":"Dinesh",
"rating":"1"
},
{
"name":"ganesh",
"rating":"2"
},
]
}
when this is renderd in my app I am expecting the first should have 3 star highlighted, then 1 star and the last should have 2 star hightlighted
but only the last is highlighted with 2 star and all other are blanks
why this might be happening? I read a lot of other answers but could not get it worked. can anyoen please help?
thanks
答案1
得分: 0
主要问题是 @Input() starrating:string='3';
会改变视图,以至于指令 [(ngModel)]
获取到最后发出的值,即 '2'。
这里,解决方案使用了一个额外的标识符,然后在子组件 - rating 中使用它。你可以以多种方式实现这一点,例如在数组中收集并传递索引位置,如 <app-rating [componentId]="i"></app-rating>
<div *ngFor="let item of arrayResponse; let i = index">
<img src="assets\150.png" class="img-fluid rounded-circle mb-3" alt="coach image"/> <br>
<div>{{item.name}}</div>
<app-rating [starrating]="item?.rating" [componentId]="i"></app-rating>
</div>
更新后的 Rating 组件:
ngOnInit() {
this.selectedRating[this.componentId] = this.starrating;
}
@Input() starrating: any = '3';
@Input() componentId: number = 0;
selectedRating: { [componentId: string]: string } = {};
onRatingChange(componentId: number, rating: string) {
this.selectedRating[componentId] = rating;
}
在这里,你可以注意到我们创建了一个新对象 selectedRating
,它保存临时数据。实际上,你可以在父级上创建这个对象,并将其作为附加输入传递给子组件,以便不会丢失由新输入保存的数据,但是对于这个示例,让我们跳过这一步。
由于我们初始化了新变量 @Input() componentId: number = 0;
和 selectedRating: { [componentId: string]: string } = {};
,我们需要在 rating.component.html
中使用它们:
<div class="rating-wrapper">
<ng-container *ngFor="let star of ['5', '4', '3', '2', '1']">
<input
type="radio"
[id]="star + '-star-rating-' + componentId"
[name]="componentId + '-star-rating'"
[value]="star"
[ngModel]="selectedRating[componentId]"
(change)="onRatingChange(componentId, star)"
/>
<label [for]="star + '-star-rating-' + componentId" class="star-rating">
<i class="fa fa-star d-inline-block"></i>
</label>
</ng-container>
</div>
在这里我:
- 简化了代码以解决 DRY 原则
- 添加了一个新属性
name
,它保存每个组件的唯一值。这很重要,因为它确保一个组件中星星的更改不会影响其他组件。 - 在输入的 id 中分配了组件的索引
- 调整了 ngModel 指令
- 编写了 (change) 方法的逻辑
- 跳过了 checked 指令
英文:
The main problem is that the @Input() starrating:string='3';
mutates the view, so that the directive [(ngModel)]
gets the last emitted value, which is '2'.
Here, the solution uses an additional identifier, and later on u a use of it in child component - rating. You could do this in multiple ways, for sample the index position in array is collected and passed as <app-rating [componentId]="i"></app-rating>
<div *ngFor="let item of arrayResponse; let i = index">
<img src="assets\150.png" class="img-fluid rounded-circle mb-3" alt="coach image"/> <br>
<div>{{item.name}}</div>
<app-rating [starrating]="item?.rating" [componentId]="i"></app-rating>
</div>
The updated Rating component:
ngOnInit() {
this.selectedRating[this.componentId] = this.starrating;
}
@Input() starrating: any = '3';
@Input() componentId: number = 0;
selectedRating: { [componentId: string]: string } = {};
onRatingChange(componentId: number, rating: string) {
this.selectedRating[componentId] = rating;
}
Here you can notice that we created a new object selectedRating
, which holds the temporary data. You could actually create this object on parent level and pass it as additional input to child component, so that you would not lose the data holded by new Input, however for this sample lets skip this.
Since we initiate a new variables - @Input() componentId: number = 0;
and selectedRating: { [componentId: string]: string } = {};
we need to make use of them in rating.component.html
:
<div class="rating-wrapper">
<ng-container *ngFor="let star of ['5', '4', '3', '2', '1']">
<input
type="radio"
[id]="star + '-star-rating-' + componentId"
[name]="componentId + '-star-rating'"
[value]="star"
[ngModel]="selectedRating[componentId]"
(change)="onRatingChange(componentId, star)"
/>
<label [for]="star + '-star-rating-' + componentId" class="star-rating">
<i class="fa fa-star d-inline-block"></i>
</label>
</ng-container>
</div>
Here I:
- Simplified a code to resolve the DRY principle
- Added a new property
name
which holds a unique value per component. It's important manner, since it ensures that the change in one component of stars, won't affect the other. - Assign an index of component in id of input
- Adjusted ngModel directive
- Wrote the (change) method logic
- Skipped the checked directive
答案2
得分: 0
当您使用属性并希望进行双向绑定时,您需要一个@Input
和一个@Output
,请参阅文档 - 看到"输出名称是属性名称+Change"
@Input() starrating!: number | string;
@Output() starratingChange = new EventEmitter<number|string>();
当您使用输入类型为单选按钮(radio)并希望创建一个"单选按钮组"时,输入元素应该具有一个"名称"(name)。但是,该属性应该在"整个组"中是唯一的。您的组件始终具有相同的名称:star-rating
您可以选择两个选项,使用外部变量或强制用户添加一个"name"属性,参见这个 SO
使用外部变量的示例:
let StarratingUniqueId = 0;
@Component({
// ...
})
export class RatingComponent {
id:number;
constructor(){
this.id=StarratingUniqueId++;
}
}
或者,强制用户添加"name"属性的示例:
constructor(@Optional() @Attribute('name') public name:string){
if (!name)
throw new Error("The name attribute is necessary")
}
不要使用"checked",您可以使用类似以下方式:
[class.checked]="starrating === '1'"
并使用以下样式:
input.checked {
/* ... */
}
英文:
When you use a property and you want to make a two ways binding you need one@Input
and one @Output
, see the docs -see that the "ouput name it's the property+Change"
@Input() starrating!: number | string;
@Output() starratingChange = new EventEmitter<number|string>();
When you use a input type radio and you want to create a "radio group", the inputs should to have a "name". But this property should be unique to the "whole group". Your components have always the same name: star-rating
You can choose two options, use a external variable or force user to makes an atribute "name", see this SO
let StarratingUniqueId = 0;
@Component({
...
}){
export class RatingComponent {
id:number;
constructor(){
this.id=StarratingUniqueId ++;
}
}
Or
constructor(@Optional() @Attribute('name') public name:string){
if (!name)
throw new Error("The name attribute it's neccessary")
}
Instead of use checked you can use some like
[class.checked]="starrating === '1'"
And use
input.checked{...}
答案3
得分: 0
当您在表单中的多个输入元素中使用相同的name属性值时,可能会导致意外行为,因为浏览器将这些输入视为同一组的一部分。这可能会导致表单提交和数据处理方面的问题。当您在具有相同名称的多个输入上使用ngModel绑定时,所有这些输入可能都绑定到组件中的同一属性。因此,一个输入中的更改会立即反映在所有其他具有相同名称的输入中。
您可以在此处为name属性提供唯一的值。
rating.component.html
<div class="rating-wrapper">
<!-- star 5 -->
<input
type="radio"
id="5-star-rating"
name="5-star-rating"
value="5"
[checked]="starrating === '5'"
[(ngModel)]="starrating"
/>
<label for="5-star-rating" class="star-rating">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 4 -->
<input
type="radio"
id="4-star-rating"
name="4-star-rating"
value="4"
[checked]="starrating === '4'"
[(ngModel)]="starrating"
/>
<label for="4-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 3 -->
<input
type="radio"
id="3-star-rating"
name="3-star-rating"
value="3"
[checked]="starrating === '3'"
[(ngModel)]="starrating"
/>
<label for="3-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 2 -->
<input
type="radio"
id="2-star-rating"
name="2-star-rating"
value="2"
[checked]="starrating === '2'"
[(ngModel)]="starrating"
/>
<label for="2-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 1 -->
<input
type="radio"
id="1-star-rating"
name="1-star-rating"
value="1"
[checked]="starrating === '1'"
[(ngModel)]="starrating"
/>
<label for="1-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
</div>
我们还可以通过使用Angular的ngFor指令,根据值数组动态生成星级评分输入,从而简化代码。
<div class="rating-wrapper">
<ng-container *ngFor="let rating of [5, 4, 3, 2, 1]">
<input
type="radio"
[id]="rating + '-star-rating'"
[name]="'star-rating-' + rating"
[value]="rating"
[checked]="starrating === rating.toString()"
/>
<label [for]="rating + '-star-rating'" class="star-rating">
<i class="fa fa-star d-inline-block"></i>
</label>
</ng-container>
</div>
英文:
When you use the same value for the name attribute in multiple input elements within a form, it can lead to unexpected behavior because the browser treats those inputs as part of the same group. This can cause issues with form submission and data handling. And when you use ngModel binding on multiple inputs with the same name, all those inputs might be bound to the same property in your component. As a result, changes in one input will immediately be reflected in all other inputs sharing the same name.
You could give unique value for the name property here.
rating.component.html
<div class="rating-wrapper">
<!-- star 5 -->
<input
type="radio"
id="5-star-rating"
name="5-star-rating"
value="5"
[checked]="starrating === '5'"
[(ngModel)]="starrating"
/>
<label for="5-star-rating" class="star-rating">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 4 -->
<input
type="radio"
id="4-star-rating"
name="4-star-rating"
value="4"
[checked]="starrating === '4'"
[(ngModel)]="starrating"
/>
<label for="4-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 3 -->
<input
type="radio"
id="3-star-rating"
name="3-star-rating"
value="3"
[checked]="starrating === '3'"
[(ngModel)]="starrating"
/>
<label for="3-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 2 -->
<input
type="radio"
id="2-star-rating"
name="2-star-rating"
value="2"
[checked]="starrating === '2'"
[(ngModel)]="starrating"
/>
<label for="2-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
<!-- star 1 -->
<input
type="radio"
id="1-star-rating"
name="1-star-rating"
value="1"
[checked]="starrating === '1'"
[(ngModel)]="starrating"
/>
<label for="1-star-rating" class="star-rating star">
<i class="fa fa-star d-inline-block"></i>
</label>
</div>
We can also simplify the code by using Angular's ngFor directive to dynamically generate star rating inputs based on an array of values.
<div class="rating-wrapper">
<ng-container *ngFor="let rating of [5, 4, 3, 2, 1]">
<input
type="radio"
[id]="rating + '-star-rating'"
[name]="'star-rating-' + rating"
[value]="rating"
[checked]="starrating === rating.toString()"
/>
<label [for]="rating + '-star-rating'" class="star-rating">
<i class="fa fa-star d-inline-block"></i>
</label>
</ng-container>
</div>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论