-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Answer:1 Projection #1409
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Answer:1 Projection #1409
Changes from all commits
8e631b6
bcaf96b
5a7038a
a920e5a
b57f068
95bdb94
149bddd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,76 @@ | ||
| import { ChangeDetectionStrategy, Component } from '@angular/core'; | ||
| import { NgOptimizedImage } from '@angular/common'; | ||
| import { | ||
| ChangeDetectionStrategy, | ||
| Component, | ||
| inject, | ||
| OnInit, | ||
| } from '@angular/core'; | ||
| import { CityStore } from '../../data-access/city.store'; | ||
| import { | ||
| FakeHttpService, | ||
| randomCity, | ||
| } from '../../data-access/fake-http.service'; | ||
| import { OptionTemplateDirective } from '../../directive/option-template.directive'; | ||
| import { CardType } from '../../model/card.model'; | ||
| import { CardComponent } from '../../ui/card/card.component'; | ||
| import { ListItemComponent } from '../../ui/list-item/list-item.component'; | ||
|
|
||
| @Component({ | ||
| selector: 'app-city-card', | ||
| template: 'TODO City', | ||
| imports: [], | ||
| template: ` | ||
| <app-card [list]="cities()" [customClass]="'bg-light-yellow'"> | ||
| <img | ||
| ngProjectAs="card-image" | ||
| ngSrc="assets/img/city.png" | ||
| width="200" | ||
| height="200" /> | ||
| <ng-template appOptionTemplate let-city> | ||
| <app-list-item> | ||
| <ng-container ngProjectAs="item-name">{{ city.name }}</ng-container> | ||
| <button ngProjectAs="item-action" (click)="delete(city.id)"> | ||
| <img class="h-5" src="assets/svg/trash.svg" /> | ||
| </button> | ||
| </app-list-item> | ||
| </ng-template> | ||
| <button | ||
| ngProjectAs="card-action" | ||
| class="rounded-sm border border-blue-500 bg-blue-300 p-2" | ||
| (click)="addNewItem()"> | ||
| Add | ||
| </button> | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can add the button to |
||
| </app-card> | ||
| `, | ||
| styles: [ | ||
| ` | ||
| ::ng-deep .bg-light-yellow { | ||
| background-color: rgba(218, 255, 108, 0.6); | ||
| } | ||
| `, | ||
| ], | ||
| imports: [ | ||
| ListItemComponent, | ||
| CardComponent, | ||
| NgOptimizedImage, | ||
| OptionTemplateDirective, | ||
| ], | ||
| changeDetection: ChangeDetectionStrategy.OnPush, | ||
| }) | ||
| export class CityCardComponent {} | ||
| export class CityCardComponent implements OnInit { | ||
| private http = inject(FakeHttpService); | ||
| private store = inject(CityStore); | ||
|
|
||
| cities = this.store.cities; | ||
| cardType = CardType.CITY; | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not needed anymore |
||
|
|
||
| ngOnInit(): void { | ||
| this.http.fetchCities$.subscribe((s) => this.store.addAll(s)); | ||
| } | ||
|
|
||
| delete(id: number) { | ||
| this.store.deleteOne(id); | ||
| } | ||
|
|
||
| addNewItem() { | ||
| this.store.addOne(randomCity()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { OptionTemplateDirective } from './option-template.directive'; | ||
|
|
||
| describe('OptionTemplateDirective', () => { | ||
| it('should create an instance', () => { | ||
| const directive = new OptionTemplateDirective(); | ||
| expect(directive).toBeTruthy(); | ||
| }); | ||
| }); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this test is a bit useless 😅 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { Directive } from '@angular/core'; | ||
|
|
||
| @Directive({ | ||
| selector: '[appOptionTemplate]', | ||
| exportAs: 'appOptionTemplate', | ||
| }) | ||
| export class OptionTemplateDirective { | ||
| constructor() {} | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,58 +1,33 @@ | ||
| import { NgOptimizedImage } from '@angular/common'; | ||
| import { Component, inject, input } from '@angular/core'; | ||
| import { randStudent, randTeacher } from '../../data-access/fake-http.service'; | ||
| import { StudentStore } from '../../data-access/student.store'; | ||
| import { TeacherStore } from '../../data-access/teacher.store'; | ||
| import { CardType } from '../../model/card.model'; | ||
| import { ListItemComponent } from '../list-item/list-item.component'; | ||
| import { NgTemplateOutlet } from '@angular/common'; | ||
| import { Component, contentChild, input, TemplateRef } from '@angular/core'; | ||
| import { OptionTemplateDirective } from '../../directive/option-template.directive'; | ||
|
|
||
| @Component({ | ||
| selector: 'app-card', | ||
| template: ` | ||
| <div | ||
| class="flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4" | ||
| [class]="customClass()"> | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of wrapping your component inside another div, you can use the |
||
| @if (type() === CardType.TEACHER) { | ||
| <img ngSrc="assets/img/teacher.png" width="200" height="200" /> | ||
| } | ||
| @if (type() === CardType.STUDENT) { | ||
| <img ngSrc="assets/img/student.webp" width="200" height="200" /> | ||
| } | ||
| <ng-content select="card-image"></ng-content> | ||
| <section> | ||
| @for (item of list(); track item) { | ||
| <app-list-item | ||
| [name]="item.firstName" | ||
| [id]="item.id" | ||
| [type]="type()"></app-list-item> | ||
| @for (item of list(); let i = $index; track i) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if possible it's better to track on a unique property like an id, to be sure that list has an id, you can do also, you don't have to declare |
||
| <ng-container | ||
| [ngTemplateOutlet]="optionTemplateRef()" | ||
| [ngTemplateOutletContext]="{ | ||
| $implicit: item, | ||
| index: i | ||
| }"></ng-container> | ||
| } | ||
| </section> | ||
| <button | ||
| class="rounded-sm border border-blue-500 bg-blue-300 p-2" | ||
| (click)="addNewItem()"> | ||
| Add | ||
| </button> | ||
| <ng-content select="card-action"></ng-content> | ||
| </div> | ||
| `, | ||
| imports: [ListItemComponent, NgOptimizedImage], | ||
| imports: [NgTemplateOutlet], | ||
| }) | ||
| export class CardComponent { | ||
| private teacherStore = inject(TeacherStore); | ||
| private studentStore = inject(StudentStore); | ||
|
|
||
| readonly list = input<any[] | null>(null); | ||
| readonly type = input.required<CardType>(); | ||
| export class CardComponent<T> { | ||
| readonly list = input<T[]>(); | ||
| readonly customClass = input(''); | ||
|
|
||
| CardType = CardType; | ||
|
|
||
| addNewItem() { | ||
| const type = this.type(); | ||
| if (type === CardType.TEACHER) { | ||
| this.teacherStore.addOne(randTeacher()); | ||
| } else if (type === CardType.STUDENT) { | ||
| this.studentStore.addOne(randStudent()); | ||
| } | ||
| } | ||
| readonly optionTemplateRef = contentChild(OptionTemplateDirective, { | ||
| read: TemplateRef, | ||
| }); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💪 |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,39 +1,13 @@ | ||
| import { | ||
| ChangeDetectionStrategy, | ||
| Component, | ||
| inject, | ||
| input, | ||
| } from '@angular/core'; | ||
| import { StudentStore } from '../../data-access/student.store'; | ||
| import { TeacherStore } from '../../data-access/teacher.store'; | ||
| import { CardType } from '../../model/card.model'; | ||
| import { ChangeDetectionStrategy, Component } from '@angular/core'; | ||
|
|
||
| @Component({ | ||
| selector: 'app-list-item', | ||
| template: ` | ||
| <div class="border-grey-300 flex justify-between border px-2 py-1"> | ||
| {{ name() }} | ||
| <button (click)="delete(id())"> | ||
| <img class="h-5" src="assets/svg/trash.svg" /> | ||
| </button> | ||
| <ng-content select="item-name"></ng-content> | ||
| <ng-content select="item-action"></ng-content> | ||
| </div> | ||
| `, | ||
| changeDetection: ChangeDetectionStrategy.OnPush, | ||
| }) | ||
| export class ListItemComponent { | ||
| private teacherStore = inject(TeacherStore); | ||
| private studentStore = inject(StudentStore); | ||
|
|
||
| readonly id = input.required<number>(); | ||
| readonly name = input.required<string>(); | ||
| readonly type = input.required<CardType>(); | ||
|
|
||
| delete(id: number) { | ||
| const type = this.type(); | ||
| if (type === CardType.TEACHER) { | ||
| this.teacherStore.deleteOne(id); | ||
| } else if (type === CardType.STUDENT) { | ||
| this.studentStore.deleteOne(id); | ||
| } | ||
| } | ||
| } | ||
| export class ListItemComponent {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ngProjectAsis not needed here, but you can use it if you prefer