'Unable to bind boolean to custom component
I've the following two Angular components with their respective templates:
Todo:
@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.less'],
})
export class TodoComponent implements OnInit {
@Input() uuid!: string;
@Input() isCompleted!: boolean;
@Input() title!: string;
toggleCompletionStatus(): void {
this.isCompleted = !this.isCompleted;
}
constructor() {}
ngOnInit(): void {}
}
<mat-checkbox [checked]="isCompleted" (change)="toggleCompletionStatus()">
<span class="todo-title">{{title}}</span>
</mat-checkbox>
and TodoContainer:
@Component({
selector: 'app-todo-container',
templateUrl: './todo-container.component.html',
styleUrls: ['./todo-container.component.less'],
})
export class TodoContainerComponent implements OnInit {
todos: Todo[] = [];
constructor(private todoService: TodoService) {}
ngOnInit(): void {
this.todoService.getTodos().subscribe((todos) => {
this.todos = todos;
});
}
}
<ol>
<li *ngFor="let todo of todos">
<div *ngIf="todo.isCompleted">
<app-todo [id]="todo.id" [title]="todo.title" [isCompleted]="true"></app-todo>
</div>
<div *ngIf="!todo.isCompleted">
<app-todo [id]="todo.id" [title]="todo.title" [isCompleted]="false"></app-todo>
</div>
</li>
</ol>
My problem is that I can't get bind to isCompleted from TodoContainer's template; the value is spelled out as either true or false, but tests reveal that it is always null. id and title bind properly. In fact, if I use isCompleted="true" instead of [isCompleted]="true", my tests pass (but the template has a type error of course). This has led me to believe that the issue is with isCompleted being a boolean.
How can I correctly bind a boolean value to an attribute?
EDIT: Below is the test on TodoContainer that's failing:
describe('TodoContainerComponent', () => {
let component: TodoContainerComponent;
let fixture: ComponentFixture<TodoContainerComponent>;
let element: HTMLElement;
let mockTodos: Todo[] = [];
const todoService = jasmine.createSpyObj('TodoService', [
'getTodos',
'markCompletionStatus',
]);
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [TodoContainerComponent],
providers: [{ provide: TodoService, useValue: todoService }],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(TodoContainerComponent);
component = fixture.componentInstance;
for (let i = 0; i < 10; ++i) {
mockTodos.push({
id: uuid4(),
title: `title ${i}`,
isCompleted: i % 2 == 0,
ownerID: uuid4(),
});
}
todoService.getTodos.and.returnValue(of(mockTodos));
fixture.detectChanges();
element = fixture.nativeElement;
});
it('should render todos', () => {
const todoItems = element.getElementsByTagName('app-todo');
expect(todoItems.length).toBe(mockTodos.length);
for (let i = 0; i < todoItems.length; ++i) {
expect(todoItems[i].getAttribute('id')).toBe(mockTodos[i].id);
expect(todoItems[i].getAttribute('title')).toBe(mockTodos[i].title);
// this doesn't pass:
const isCompletedAttr = todoItems[i].getAttribute('isCompleted');
expect(isCompletedAttr)
.withContext(`completion status is ${isCompletedAttr}`)
.not.toBeNull();
}
});
});
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
