diff --git a/apps/angular/1-projection/src/app/app.component.ts b/apps/angular/1-projection/src/app/app.component.ts index df654bbc2..75dc670bc 100644 --- a/apps/angular/1-projection/src/app/app.component.ts +++ b/apps/angular/1-projection/src/app/app.component.ts @@ -5,6 +5,7 @@ import { TeacherCardComponent } from './component/teacher-card/teacher-card.comp @Component({ selector: 'app-root', + standalone: true, template: `
diff --git a/apps/angular/1-projection/src/app/component/city-card/city-card.component.ts b/apps/angular/1-projection/src/app/component/city-card/city-card.component.ts index 8895c8c84..867a70e92 100644 --- a/apps/angular/1-projection/src/app/component/city-card/city-card.component.ts +++ b/apps/angular/1-projection/src/app/component/city-card/city-card.component.ts @@ -1,9 +1,65 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { NgOptimizedImage } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + inject, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { CityStore } from '../../data-access/city.store'; +import { + FakeHttpService, + randomCity, +} from '../../data-access/fake-http.service'; +import { CardComponent, CardItemDirective } from '../../ui/card/card.component'; +import { ListItemComponent } from '../../ui/list-item/list-item.component'; @Component({ selector: 'app-city-card', - template: 'TODO City', - imports: [], + standalone: true, + template: ` + + city + + + {{ city.name }} + + + + `, + styles: [ + ` + .bg-pink { + background-color: rgba(111, 20, 20, 0.5); + } + `, + ], + imports: [ + CardComponent, + CardItemDirective, + NgOptimizedImage, + ListItemComponent, + ], + encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class CityCardComponent {} +export class CityCardComponent implements OnInit { + private http = inject(FakeHttpService); + private store = inject(CityStore); + cities = this.store.cities; + ngOnInit(): void { + this.http.fetchCities$.subscribe((c) => this.store.addAll(c)); + } + addNewItem() { + this.store.addOne(randomCity()); + } + + deleteItem(id: number) { + this.store.deleteOne(id); + } +} diff --git a/apps/angular/1-projection/src/app/component/student-card/student-card.component.ts b/apps/angular/1-projection/src/app/component/student-card/student-card.component.ts index bdfa4abd4..15e367110 100644 --- a/apps/angular/1-projection/src/app/component/student-card/student-card.component.ts +++ b/apps/angular/1-projection/src/app/component/student-card/student-card.component.ts @@ -1,30 +1,56 @@ +import { NgOptimizedImage } from '@angular/common'; import { ChangeDetectionStrategy, Component, inject, OnInit, + ViewEncapsulation, } from '@angular/core'; -import { FakeHttpService } from '../../data-access/fake-http.service'; +import { + FakeHttpService, + randStudent, +} from '../../data-access/fake-http.service'; import { StudentStore } from '../../data-access/student.store'; import { CardType } from '../../model/card.model'; -import { CardComponent } from '../../ui/card/card.component'; +import { CardComponent, CardItemDirective } from '../../ui/card/card.component'; +import { ListItemComponent } from '../../ui/list-item/list-item.component'; @Component({ selector: 'app-student-card', + standalone: true, template: ` + (added)="addNewItem()" + customClass="bg-light-green"> + student + + + + {{ student.firstName }} + + + `, styles: [ ` - ::ng-deep .bg-light-green { + .bg-light-green { background-color: rgba(0, 250, 0, 0.1); } `, ], - imports: [CardComponent], + imports: [ + CardComponent, + CardItemDirective, + NgOptimizedImage, + ListItemComponent, + ], + encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) export class StudentCardComponent implements OnInit { @@ -37,4 +63,12 @@ export class StudentCardComponent implements OnInit { ngOnInit(): void { this.http.fetchStudents$.subscribe((s) => this.store.addAll(s)); } + + addNewItem() { + this.store.addOne(randStudent()); + } + + deleteItem(id: number) { + this.store.deleteOne(id); + } } diff --git a/apps/angular/1-projection/src/app/component/teacher-card/teacher-card.component.ts b/apps/angular/1-projection/src/app/component/teacher-card/teacher-card.component.ts index adf0ad3c1..3de6d98fa 100644 --- a/apps/angular/1-projection/src/app/component/teacher-card/teacher-card.component.ts +++ b/apps/angular/1-projection/src/app/component/teacher-card/teacher-card.component.ts @@ -1,25 +1,50 @@ -import { Component, inject, OnInit } from '@angular/core'; -import { FakeHttpService } from '../../data-access/fake-http.service'; +import { NgOptimizedImage } from '@angular/common'; +import { Component, inject, OnInit, ViewEncapsulation } from '@angular/core'; +import { + FakeHttpService, + randTeacher, +} from '../../data-access/fake-http.service'; import { TeacherStore } from '../../data-access/teacher.store'; import { CardType } from '../../model/card.model'; -import { CardComponent } from '../../ui/card/card.component'; +import { CardComponent, CardItemDirective } from '../../ui/card/card.component'; +import { ListItemComponent } from '../../ui/list-item/list-item.component'; @Component({ selector: 'app-teacher-card', + standalone: true, template: ` + (added)="addNewItem()" + customClass="bg-light-red"> + teacher + + + {{ teacher.firstName }} + + + `, styles: [ ` - ::ng-deep .bg-light-red { + .bg-light-red { background-color: rgba(250, 0, 0, 0.1); } `, ], - imports: [CardComponent], + encapsulation: ViewEncapsulation.None, + imports: [ + CardComponent, + CardItemDirective, + NgOptimizedImage, + ListItemComponent, + ], }) export class TeacherCardComponent implements OnInit { private http = inject(FakeHttpService); @@ -31,4 +56,12 @@ export class TeacherCardComponent implements OnInit { ngOnInit(): void { this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t)); } + + addNewItem() { + this.store.addOne(randTeacher()); + } + + deleteItem(id: number) { + this.store.deleteOne(id); + } } diff --git a/apps/angular/1-projection/src/app/data-access/city.store.ts b/apps/angular/1-projection/src/app/data-access/city.store.ts index a8b523569..9fbcb346b 100644 --- a/apps/angular/1-projection/src/app/data-access/city.store.ts +++ b/apps/angular/1-projection/src/app/data-access/city.store.ts @@ -5,7 +5,7 @@ import { City } from '../model/city.model'; providedIn: 'root', }) export class CityStore { - private cities = signal([]); + public cities = signal([]); addAll(cities: City[]) { this.cities.set(cities); diff --git a/apps/angular/1-projection/src/app/ui/card/card.component.ts b/apps/angular/1-projection/src/app/ui/card/card.component.ts index 166d9c49a..2832dfbcc 100644 --- a/apps/angular/1-projection/src/app/ui/card/card.component.ts +++ b/apps/angular/1-projection/src/app/ui/card/card.component.ts @@ -1,31 +1,38 @@ -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, + Directive, + input, + output, + TemplateRef, +} from '@angular/core'; + +@Directive({ + selector: 'ng-template[cardItem]', + standalone: true, +}) +export class CardItemDirective {} @Component({ selector: 'app-card', + standalone: true, template: `
- @if (type() === CardType.TEACHER) { - - } - @if (type() === CardType.STUDENT) { - - } +
- @for (item of list(); track item) { - - } +
+ @for (item of list(); track item) { + + } +
`, - imports: [ListItemComponent, NgOptimizedImage], + imports: [NgTemplateOutlet], }) -export class CardComponent { - private teacherStore = inject(TeacherStore); - private studentStore = inject(StudentStore); - - readonly list = input(null); - readonly type = input.required(); +export class CardComponent { + readonly list = input([]); readonly customClass = input(''); - - CardType = CardType; + readonly added = output(); + readonly deleted = output(); + readonly itemTemplate = contentChild.required(CardItemDirective, { + read: TemplateRef, + }); addNewItem() { - const type = this.type(); - if (type === CardType.TEACHER) { - this.teacherStore.addOne(randTeacher()); - } else if (type === CardType.STUDENT) { - this.studentStore.addOne(randStudent()); - } + this.added.emit(); + } + + deleteItem(event: number) { + this.deleted.emit(event); } } diff --git a/apps/angular/1-projection/src/app/ui/list-item/list-item.component.ts b/apps/angular/1-projection/src/app/ui/list-item/list-item.component.ts index c04a29c05..a4d8b9d1e 100644 --- a/apps/angular/1-projection/src/app/ui/list-item/list-item.component.ts +++ b/apps/angular/1-projection/src/app/ui/list-item/list-item.component.ts @@ -1,19 +1,12 @@ -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, output } from '@angular/core'; @Component({ selector: 'app-list-item', + standalone: true, template: `
- {{ name() }} -
@@ -21,19 +14,9 @@ import { CardType } from '../../model/card.model'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class ListItemComponent { - private teacherStore = inject(TeacherStore); - private studentStore = inject(StudentStore); + readonly deleted = output(); - readonly id = input.required(); - readonly name = input.required(); - readonly type = input.required(); - - delete(id: number) { - const type = this.type(); - if (type === CardType.TEACHER) { - this.teacherStore.deleteOne(id); - } else if (type === CardType.STUDENT) { - this.studentStore.deleteOne(id); - } + delete() { + this.deleted.emit(); } } diff --git a/apps/angular/13-highly-customizable-css/src/app/page.component.ts b/apps/angular/13-highly-customizable-css/src/app/page.component.ts index 029ca52d2..8307023f1 100644 --- a/apps/angular/13-highly-customizable-css/src/app/page.component.ts +++ b/apps/angular/13-highly-customizable-css/src/app/page.component.ts @@ -8,9 +8,9 @@ import { TextComponent } from './text.component'; imports: [TextStaticComponent, TextComponent], template: ` - - - This is a blue text + + + This is a blue text `, }) export class PageComponent {} diff --git a/apps/angular/13-highly-customizable-css/src/app/static-text.component.ts b/apps/angular/13-highly-customizable-css/src/app/static-text.component.ts index 703e2a538..f06a9016a 100644 --- a/apps/angular/13-highly-customizable-css/src/app/static-text.component.ts +++ b/apps/angular/13-highly-customizable-css/src/app/static-text.component.ts @@ -1,5 +1,5 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, computed, input } from '@angular/core'; +import { Component } from '@angular/core'; import { TextComponent } from './text.component'; export type StaticTextType = 'normal' | 'warning' | 'error'; @@ -8,31 +8,25 @@ export type StaticTextType = 'normal' | 'warning' | 'error'; selector: 'static-text', imports: [TextComponent], template: ` - This is a static text + This is a static text `, -}) -export class TextStaticComponent { - type = input('normal'); + styles: [ + ` + :host-context(.error) { + --font: 30px; + --color: red; + } - font = computed(() => { - switch (this.type()) { - case 'error': - return 30; - case 'warning': - return 25; - default: - return 10; - } - }); + :host-context(.warning) { + --font: 25px; + --color: orange; + } - color = computed(() => { - switch (this.type()) { - case 'error': - return 'red'; - case 'warning': - return 'orange'; - default: - return 'black'; - } - }); -} + :host-context(.normal) { + --font: 10px; + --color: black; + } + `, + ], +}) +export class TextStaticComponent {} diff --git a/apps/angular/13-highly-customizable-css/src/app/text.component.ts b/apps/angular/13-highly-customizable-css/src/app/text.component.ts index 9682c6b87..ba38a945c 100644 --- a/apps/angular/13-highly-customizable-css/src/app/text.component.ts +++ b/apps/angular/13-highly-customizable-css/src/app/text.component.ts @@ -1,15 +1,28 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, input } from '@angular/core'; +import { Component } from '@angular/core'; @Component({ selector: 'text', template: ` -

+

`, + styles: [ + ` + p { + font-size: var(--font, 10px); + color: var(--color, black); + } + + :host-context(.font) { + --font: 15px; + } + + :host-context(.color) { + --color: blue; + } + `, + ], }) -export class TextComponent { - font = input(10); - color = input('black'); -} +export class TextComponent {} diff --git a/apps/angular/21-anchor-navigation/src/app/app.config.ts b/apps/angular/21-anchor-navigation/src/app/app.config.ts index 66ab7f73f..ba519ab03 100644 --- a/apps/angular/21-anchor-navigation/src/app/app.config.ts +++ b/apps/angular/21-anchor-navigation/src/app/app.config.ts @@ -1,6 +1,14 @@ import { ApplicationConfig } from '@angular/core'; -import { provideRouter } from '@angular/router'; +import { provideRouter, withInMemoryScrolling } from '@angular/router'; import { appRoutes } from './app.routes'; export const appConfig: ApplicationConfig = { - providers: [provideRouter(appRoutes)], + providers: [ + provideRouter( + appRoutes, + withInMemoryScrolling({ + anchorScrolling: 'enabled', + scrollPositionRestoration: 'enabled', + }), + ), + ], }; diff --git a/apps/angular/21-anchor-navigation/src/app/foo.component.ts b/apps/angular/21-anchor-navigation/src/app/foo.component.ts index 6744c3662..f85d4d1f8 100644 --- a/apps/angular/21-anchor-navigation/src/app/foo.component.ts +++ b/apps/angular/21-anchor-navigation/src/app/foo.component.ts @@ -6,7 +6,7 @@ import { NavButtonComponent } from './nav-button.component'; selector: 'app-foo', template: ` Welcome to foo page - Home Page + Home Page
section 1
section 2
`, diff --git a/apps/angular/21-anchor-navigation/src/app/nav-button.component.ts b/apps/angular/21-anchor-navigation/src/app/nav-button.component.ts index 7a22c7f38..8a2e9bc7a 100644 --- a/apps/angular/21-anchor-navigation/src/app/nav-button.component.ts +++ b/apps/angular/21-anchor-navigation/src/app/nav-button.component.ts @@ -1,10 +1,12 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, input } from '@angular/core'; +import { Component, computed, input } from '@angular/core'; +import { RouterLinkWithHref } from '@angular/router'; @Component({ selector: 'nav-button', + imports: [RouterLinkWithHref], template: ` - + `, @@ -14,4 +16,9 @@ import { Component, input } from '@angular/core'; }) export class NavButtonComponent { href = input(''); + isAnchor = computed(() => + this.href().startsWith('#') ? this.href().substring(1) : '', + ); + link = computed(() => (this.isAnchor() ? [] : this.href())); + fragment = computed(() => (this.isAnchor() ? this.isAnchor() : undefined)); } diff --git a/apps/angular/22-router-input/src/app/app.config.ts b/apps/angular/22-router-input/src/app/app.config.ts index ed404941f..a7c1007b9 100644 --- a/apps/angular/22-router-input/src/app/app.config.ts +++ b/apps/angular/22-router-input/src/app/app.config.ts @@ -1,7 +1,7 @@ import { ApplicationConfig } from '@angular/core'; -import { provideRouter } from '@angular/router'; +import { provideRouter, withComponentInputBinding } from '@angular/router'; import { appRoutes } from './app.routes'; export const appConfig: ApplicationConfig = { - providers: [provideRouter(appRoutes)], + providers: [provideRouter(appRoutes, withComponentInputBinding())], }; diff --git a/apps/angular/22-router-input/src/app/test.component.ts b/apps/angular/22-router-input/src/app/test.component.ts index 747ab4483..152adc1bf 100644 --- a/apps/angular/22-router-input/src/app/test.component.ts +++ b/apps/angular/22-router-input/src/app/test.component.ts @@ -1,21 +1,17 @@ import { AsyncPipe } from '@angular/common'; -import { Component, inject } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { map } from 'rxjs'; +import { Component, input } from '@angular/core'; @Component({ selector: 'app-subscription', imports: [AsyncPipe], template: ` -
TestId: {{ testId$ | async }}
-
Permission: {{ permission$ | async }}
-
User: {{ user$ | async }}
+
TestId: {{ testId() }}
+
Permission: {{ permission() }}
+
User: {{ user() }}
`, }) export default class TestComponent { - private activatedRoute = inject(ActivatedRoute); - - testId$ = this.activatedRoute.params.pipe(map((p) => p['testId'])); - permission$ = this.activatedRoute.data.pipe(map((d) => d['permission'])); - user$ = this.activatedRoute.queryParams.pipe(map((q) => q['user'])); + testId = input(''); + permission = input(''); + user = input(''); } diff --git a/apps/angular/31-module-to-standalone/src/app/app.component.ts b/apps/angular/31-module-to-standalone/src/app/app.component.ts index 986df84b5..d96233615 100644 --- a/apps/angular/31-module-to-standalone/src/app/app.component.ts +++ b/apps/angular/31-module-to-standalone/src/app/app.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { RouterLink, RouterOutlet } from '@angular/router'; @Component({ selector: 'app-root', @@ -25,6 +26,6 @@ import { Component } from '@angular/core'; host: { class: 'flex flex-col p-4 gap-3', }, - standalone: false, + imports: [RouterLink, RouterOutlet], }) export class AppComponent {} diff --git a/apps/angular/31-module-to-standalone/src/app/app.module.ts b/apps/angular/31-module-to-standalone/src/app/app.module.ts deleted file mode 100644 index c795a11b9..000000000 --- a/apps/angular/31-module-to-standalone/src/app/app.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MainShellModule } from '@angular-challenges/module-to-standalone/shell'; -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { AppComponent } from './app.component'; - -@NgModule({ - declarations: [AppComponent], - imports: [BrowserModule, MainShellModule], - bootstrap: [AppComponent], -}) -export class AppModule {} diff --git a/apps/angular/31-module-to-standalone/src/main.ts b/apps/angular/31-module-to-standalone/src/main.ts index 8cd87d53b..916f78679 100644 --- a/apps/angular/31-module-to-standalone/src/main.ts +++ b/apps/angular/31-module-to-standalone/src/main.ts @@ -1,9 +1,14 @@ +import { provideToken } from '@angular-challenges/module-to-standalone/core/providers'; +import { appRoutes } from '@angular-challenges/module-to-standalone/shell'; import { provideZoneChangeDetection } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './app/app.module'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideRouter } from '@angular/router'; +import { AppComponent } from './app/app.component'; -platformBrowserDynamic() - .bootstrapModule(AppModule, { - applicationProviders: [provideZoneChangeDetection()], - }) - .catch((err) => console.error(err)); +bootstrapApplication(AppComponent, { + providers: [ + provideZoneChangeDetection(), + provideRouter(appRoutes), + provideToken('main-shell-token'), + ], +}).catch((err) => console.error(err)); diff --git a/apps/angular/5-crud-application/api.config.ts b/apps/angular/5-crud-application/api.config.ts new file mode 100644 index 000000000..9955cdff0 --- /dev/null +++ b/apps/angular/5-crud-application/api.config.ts @@ -0,0 +1,16 @@ +import { InjectionToken, makeEnvironmentProviders } from '@angular/core'; + +export interface ApiConfig { + baseUrl: string; +} + +export const API_CONFIG = new InjectionToken('API_CONFIG'); + +export function provideApiConfig(config: ApiConfig = { baseUrl: '/api' }) { + return makeEnvironmentProviders([ + { + provide: API_CONFIG, + useValue: config, + }, + ]); +} diff --git a/apps/angular/5-crud-application/app.config.ts b/apps/angular/5-crud-application/app.config.ts new file mode 100644 index 000000000..f3d63a66e --- /dev/null +++ b/apps/angular/5-crud-application/app.config.ts @@ -0,0 +1,7 @@ +import { provideHttpClient } from '@angular/common/http'; +import { ApplicationConfig } from '@angular/core'; +import { provideApiConfig } from './api.config'; + +export const appConfig: ApplicationConfig = { + providers: [provideHttpClient(), provideApiConfig()], +}; diff --git a/apps/angular/5-crud-application/project.json b/apps/angular/5-crud-application/project.json index a718f02b6..6db9f53c6 100644 --- a/apps/angular/5-crud-application/project.json +++ b/apps/angular/5-crud-application/project.json @@ -56,6 +56,10 @@ }, "serve": { "executor": "@angular-devkit/build-angular:dev-server", + "options": { + "buildTarget": "angular-crud-application:build:development", + "proxyConfig": "apps/angular/5-crud-application/proxy.conf.json" + }, "configurations": { "production": { "buildTarget": "angular-crud-application:build:production" diff --git a/apps/angular/5-crud-application/proxy.conf.json b/apps/angular/5-crud-application/proxy.conf.json new file mode 100644 index 000000000..4bed70c4c --- /dev/null +++ b/apps/angular/5-crud-application/proxy.conf.json @@ -0,0 +1,11 @@ +{ + "/api": { + "target": "https://jsonplaceholder.typicode.com", + "secure": false, + "changeOrigin": true, + "pathRewrite": { + "^/api": "" + }, + "logLevel": "debug" + } +} diff --git a/apps/angular/5-crud-application/src/app/app.component.ts b/apps/angular/5-crud-application/src/app/app.component.ts index 73ba0dc34..193e6bfe0 100644 --- a/apps/angular/5-crud-application/src/app/app.component.ts +++ b/apps/angular/5-crud-application/src/app/app.component.ts @@ -1,49 +1,50 @@ -import { HttpClient } from '@angular/common/http'; -import { Component, inject, OnInit } from '@angular/core'; +import { Component, computed, inject } from '@angular/core'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { randText } from '@ngneat/falso'; +import { Todo } from './todo.model'; +import { TodoService } from './todo.service'; @Component({ - imports: [], + imports: [MatProgressSpinnerModule], selector: 'app-root', template: ` - @for (todo of todos; track todo.id) { - {{ todo.title }} - + @if (loadingState()) { + < + + } @else if (errorState()) { +

{{ errorState() }}

+ } @else { +

Todo List:

+
    + @for (todo of todoList(); track todo.id) { +
  • + {{ todo.title }} + + +
  • + } +
} `, styles: [], }) -export class AppComponent implements OnInit { - private http = inject(HttpClient); - - todos!: any[]; +export class AppComponent { + private readonly todoService = inject(TodoService); + readonly todos = this.todoService.todos; + protected readonly todoList = computed(() => this.todoService.todos() || []); + loadingState = this.todoService.loadingState; + errorState = this.todoService.errorState; ngOnInit(): void { - this.http - .get('https://jsonplaceholder.typicode.com/todos') - .subscribe((todos) => { - this.todos = todos; - }); + this.todoService.loadTodos(); + } + + update(todo: Todo): void { + const updatedTodo = { ...todo, title: randText() }; + this.todoService.updateTodo(updatedTodo); } - update(todo: any) { - this.http - .put( - `https://jsonplaceholder.typicode.com/todos/${todo.id}`, - JSON.stringify({ - todo: todo.id, - title: randText(), - body: todo.body, - userId: todo.userId, - }), - { - headers: { - 'Content-type': 'application/json; charset=UTF-8', - }, - }, - ) - .subscribe((todoUpdated: any) => { - this.todos[todoUpdated.id - 1] = todoUpdated; - }); + delete(id: number): void { + this.todoService.deleteTodo(id); } } diff --git a/apps/angular/5-crud-application/src/app/app.config.ts b/apps/angular/5-crud-application/src/app/app.config.ts index 1c0c9422f..42fef0c5b 100644 --- a/apps/angular/5-crud-application/src/app/app.config.ts +++ b/apps/angular/5-crud-application/src/app/app.config.ts @@ -1,6 +1,7 @@ import { provideHttpClient } from '@angular/common/http'; import { ApplicationConfig } from '@angular/core'; +import { provideApiConfig } from './core/provide-api-config'; export const appConfig: ApplicationConfig = { - providers: [provideHttpClient()], + providers: [provideHttpClient(), provideApiConfig()], }; diff --git a/apps/angular/5-crud-application/src/app/core/provide-api-config.ts b/apps/angular/5-crud-application/src/app/core/provide-api-config.ts new file mode 100644 index 000000000..bf979476a --- /dev/null +++ b/apps/angular/5-crud-application/src/app/core/provide-api-config.ts @@ -0,0 +1,18 @@ +import { InjectionToken, makeEnvironmentProviders } from '@angular/core'; + +export interface ApiConfig { + baseUrl: string; +} + +export const API_CONFIG = new InjectionToken('API_CONFIG'); + +export function provideApiConfig() { + return makeEnvironmentProviders([ + { + provide: API_CONFIG, + useValue: { + baseUrl: '/api', + } satisfies ApiConfig, + }, + ]); +} diff --git a/apps/angular/5-crud-application/src/app/todo.model.ts b/apps/angular/5-crud-application/src/app/todo.model.ts new file mode 100644 index 000000000..db525ce1d --- /dev/null +++ b/apps/angular/5-crud-application/src/app/todo.model.ts @@ -0,0 +1,6 @@ +export interface Todo { + userId: number; + id: number; + title: string; + completed: boolean; +} diff --git a/apps/angular/5-crud-application/src/app/todo.service.spec.ts b/apps/angular/5-crud-application/src/app/todo.service.spec.ts new file mode 100644 index 000000000..b886192dd --- /dev/null +++ b/apps/angular/5-crud-application/src/app/todo.service.spec.ts @@ -0,0 +1,84 @@ +import { provideHttpClient } from '@angular/common/http'; +import { + HttpTestingController, + provideHttpClientTesting, +} from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { provideApiConfig } from './core/provide-api-config'; +import { Todo } from './todo.model'; +import { TodoService } from './todo.service'; + +describe('TodoService', () => { + let service: TodoService; + let httpTesting: HttpTestingController; + + const todos: Todo[] = [ + { id: 1, userId: 1, title: 'First todo', completed: false }, + { id: 2, userId: 1, title: 'Second todo', completed: false }, + { id: 3, userId: 1, title: 'Third todo', completed: true }, + ]; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + provideHttpClient(), + provideHttpClientTesting(), + provideApiConfig(), + ], + }); + + service = TestBed.inject(TodoService); + httpTesting = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpTesting.verify(); + }); + + it('should load todos', () => { + service.loadTodos(); + + const request = httpTesting.expectOne('/api/todos'); + + expect(request.request.method).toBe('GET'); + + request.flush(todos); + + expect(service.todos()).toEqual(todos); + expect(service.loadingState()).toBe(false); + expect(service.errorState()).toBeNull(); + }); + + it('should update a todo without changing the list order', () => { + service.setTodo(todos); + + const updatedTodo: Todo = { + ...todos[1], + title: 'Updated todo', + }; + + service.updateTodo(updatedTodo); + + const request = httpTesting.expectOne('/api/todos/2'); + + expect(request.request.method).toBe('PUT'); + + request.flush(updatedTodo); + + expect(service.todos()).toEqual([todos[0], updatedTodo, todos[2]]); + }); + + it('should delete a todo', () => { + service.setTodo(todos); + + service.deleteTodo(2); + + const request = httpTesting.expectOne('/api/todos/2'); + + expect(request.request.method).toBe('DELETE'); + + request.flush(null); + + expect(service.todos()).toEqual([todos[0], todos[2]]); + }); +}); diff --git a/apps/angular/5-crud-application/src/app/todo.service.ts b/apps/angular/5-crud-application/src/app/todo.service.ts new file mode 100644 index 000000000..b62ece0e0 --- /dev/null +++ b/apps/angular/5-crud-application/src/app/todo.service.ts @@ -0,0 +1,107 @@ +import { HttpClient } from '@angular/common/http'; +import { inject, Injectable, signal } from '@angular/core'; +import { API_CONFIG } from './core/provide-api-config'; +import { Todo } from './todo.model'; + +@Injectable({ + providedIn: 'root', +}) +export class TodoService { + private readonly http = inject(HttpClient); + private readonly apiConfig = inject(API_CONFIG); + private readonly apiUrl = `${this.apiConfig.baseUrl}/todos`; + readonly todoList = signal(null); + readonly loadingState = signal(false); + readonly errorState = signal(null); + private readonly processingIds = signal>(new Set()); + + readonly todos = this.todoList.asReadonly(); + + loadTodos(): void { + this.loadingState.set(true); + this.errorState.set(null); + this.http.get(this.apiUrl).subscribe({ + next: (todos) => { + this.todoList.set(todos); + this.loadingState.set(false); + }, + error: (error) => { + this.errorState.set('Failed to load todos'); + this.loadingState.set(false); + }, + }); + } + + updateTodo(todo: Todo): void { + this.loadingState.set(true); + this.setProcessing(todo.id, true); + this.http + .put(`${this.apiUrl}/${todo.id}`, todo, { + headers: { + 'Content-type': 'application/json; charset=UTF-8', + }, + }) + .subscribe({ + next: (updatedTodo) => { + const currentTodos = this.todoList(); + if (currentTodos) { + const index = currentTodos.findIndex( + (t) => t.id === updatedTodo.id, + ); + if (index !== -1) { + const updatedTodos = [...currentTodos]; + updatedTodos[index] = updatedTodo; + this.todoList.set(updatedTodos); + } + } + this.setProcessing(todo.id, false); + this.loadingState.set(false); + }, + error: () => { + this.errorState.set('Failed to update todo'); + this.setProcessing(todo.id, false); + }, + }); + } + + deleteTodo(id: number): void { + this.setProcessing(id, true); + this.loadingState.set(true); + this.http.delete(`${this.apiUrl}/${id}`).subscribe({ + next: () => { + const currentTodos = this.todoList(); + if (currentTodos) { + this.todoList.set(currentTodos.filter((t) => t.id !== id)); + } + this.setProcessing(id, false); + this.loadingState.set(false); + }, + error: () => { + this.errorState.set('Failed to delete todo'); + this.setProcessing(id, false); + }, + }); + } + + setTodo(userValue: Todo[] | null): void { + this.todoList.set(userValue); + } + + isProcessing(id: number): boolean { + return this.processingIds().has(id); + } + + private setProcessing(id: number, processing: boolean): void { + this.processingIds.update((ids) => { + const nextIds = new Set(ids); + + if (processing) { + nextIds.add(id); + } else { + nextIds.delete(id); + } + + return nextIds; + }); + } +} diff --git a/apps/angular/5-crud-application/src/main.ts b/apps/angular/5-crud-application/src/main.ts index 866d45959..ed35c8ca6 100644 --- a/apps/angular/5-crud-application/src/main.ts +++ b/apps/angular/5-crud-application/src/main.ts @@ -1,10 +1,9 @@ import { provideZoneChangeDetection } from '@angular/core'; -import { appConfig } from './app/app.config'; - import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; +import { appConfig } from './app/app.config'; bootstrapApplication(AppComponent, { ...appConfig, - providers: [provideZoneChangeDetection(), ...appConfig.providers], + providers: [provideZoneChangeDetection(), ...(appConfig.providers ?? [])], }).catch((err) => console.error(err)); diff --git a/apps/angular/8-pure-pipe/src/app/app.component.ts b/apps/angular/8-pure-pipe/src/app/app.component.ts index 930fe1313..3623b8ef5 100644 --- a/apps/angular/8-pure-pipe/src/app/app.component.ts +++ b/apps/angular/8-pure-pipe/src/app/app.component.ts @@ -1,18 +1,14 @@ import { Component } from '@angular/core'; - +import { HeavyComputationPipe } from './heavy-computation.pipe'; @Component({ + imports: [HeavyComputationPipe], selector: 'app-root', template: ` @for (person of persons; track person) { - {{ heavyComputation(person, $index) }} + {{ person | heavyComputation: $index }} } `, }) export class AppComponent { persons = ['toto', 'jack']; - - heavyComputation(name: string, index: number) { - // very heavy computation - return `${name} - ${index}`; - } } diff --git a/apps/angular/8-pure-pipe/src/app/heavy-computation.pipe.spec.ts b/apps/angular/8-pure-pipe/src/app/heavy-computation.pipe.spec.ts new file mode 100644 index 000000000..adea7e6fe --- /dev/null +++ b/apps/angular/8-pure-pipe/src/app/heavy-computation.pipe.spec.ts @@ -0,0 +1,20 @@ +import { HeavyComputationPipe } from './heavy-computation.pipe'; + +describe('HeavyComputationPipe', () => { + let pipe: HeavyComputationPipe; + beforeEach(() => { + pipe = new HeavyComputationPipe(); + }); + it('create an instance', () => { + expect(pipe).toBeTruthy(); + }); + + it('should return formatted name with index', () => { + expect(pipe.transform('toto', 0)).toBe('toto - 0'); + expect(pipe.transform('jack', 1)).toBe('jack - 1'); + }); + + it('should return the original value when value is not a string', () => { + expect(pipe.transform(123, 0)).toBe(123); + }); +}); diff --git a/apps/angular/8-pure-pipe/src/app/heavy-computation.pipe.ts b/apps/angular/8-pure-pipe/src/app/heavy-computation.pipe.ts new file mode 100644 index 000000000..993645589 --- /dev/null +++ b/apps/angular/8-pure-pipe/src/app/heavy-computation.pipe.ts @@ -0,0 +1,16 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'heavyComputation', +}) +export class HeavyComputationPipe implements PipeTransform { + transform(value: unknown, ...args: unknown[]): unknown { + if (typeof value === 'string' && typeof args[0] === 'number') { + const name = value; + const index = args[0]; + // very heavy computation + return `${name} - ${index}`; + } + return value; + } +} diff --git a/libs/module-to-standalone/admin/feature/src/index.ts b/libs/module-to-standalone/admin/feature/src/index.ts index 76c8330f9..cda67dde5 100644 --- a/libs/module-to-standalone/admin/feature/src/index.ts +++ b/libs/module-to-standalone/admin/feature/src/index.ts @@ -1 +1 @@ -export * from './lib/admin-feature.module'; +export * from './lib/admin-features.routes'; diff --git a/libs/module-to-standalone/admin/feature/src/lib/admin-feature.module.ts b/libs/module-to-standalone/admin/feature/src/lib/admin-feature.module.ts deleted file mode 100644 index 84e893eeb..000000000 --- a/libs/module-to-standalone/admin/feature/src/lib/admin-feature.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -@NgModule({ - declarations: [], - imports: [ - CommonModule, - RouterModule.forChild([ - { - path: '', - loadChildren: () => - import('./dashboard/dashboard.component').then( - (m) => m.DashboardModule, - ), - }, - { - path: 'create-user', - loadChildren: () => - import('./create-user/create-user.component').then( - (m) => m.CreateUserModule, - ), - }, - ]), - ], -}) -export class AdminFeatureModule {} diff --git a/libs/module-to-standalone/admin/feature/src/lib/admin-features.routes.ts b/libs/module-to-standalone/admin/feature/src/lib/admin-features.routes.ts new file mode 100644 index 000000000..eb203224d --- /dev/null +++ b/libs/module-to-standalone/admin/feature/src/lib/admin-features.routes.ts @@ -0,0 +1,18 @@ +import { Route } from '@angular/router'; + +export const adminFeatureRoutes: Route[] = [ + { + path: '', + loadComponent: () => + import('./dashboard/dashboard.component').then( + (m) => m.DashboardComponent, + ), + }, + { + path: 'create-user', + loadComponent: () => + import('./create-user/create-user.component').then( + (m) => m.CreateUserComponent, + ), + }, +]; diff --git a/libs/module-to-standalone/admin/feature/src/lib/create-user/create-user.component.ts b/libs/module-to-standalone/admin/feature/src/lib/create-user/create-user.component.ts index 6a2d4cfd4..d3087f358 100644 --- a/libs/module-to-standalone/admin/feature/src/lib/create-user/create-user.component.ts +++ b/libs/module-to-standalone/admin/feature/src/lib/create-user/create-user.component.ts @@ -1,5 +1,5 @@ -import { Component, NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; +import { Component } from '@angular/core'; +import { RouterLink } from '@angular/router'; @Component({ selector: 'lib-create-user', @@ -12,14 +12,6 @@ import { RouterModule } from '@angular/router'; Back `, - standalone: false, + imports: [RouterLink], }) export class CreateUserComponent {} - -@NgModule({ - imports: [ - RouterModule.forChild([{ path: '', component: CreateUserComponent }]), - ], - declarations: [CreateUserComponent], -}) -export class CreateUserModule {} diff --git a/libs/module-to-standalone/admin/feature/src/lib/dashboard/dashboard.component.ts b/libs/module-to-standalone/admin/feature/src/lib/dashboard/dashboard.component.ts index 801efb520..ed5014701 100644 --- a/libs/module-to-standalone/admin/feature/src/lib/dashboard/dashboard.component.ts +++ b/libs/module-to-standalone/admin/feature/src/lib/dashboard/dashboard.component.ts @@ -1,5 +1,5 @@ -import { Component, NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; +import { Component } from '@angular/core'; +import { RouterLink } from '@angular/router'; @Component({ selector: 'lib-dashboard', @@ -12,14 +12,6 @@ import { RouterModule } from '@angular/router'; Create User `, - standalone: false, + imports: [RouterLink], }) export class DashboardComponent {} - -@NgModule({ - imports: [ - RouterModule.forChild([{ path: '', component: DashboardComponent }]), - ], - declarations: [DashboardComponent], -}) -export class DashboardModule {} diff --git a/libs/module-to-standalone/forbidden/src/index.ts b/libs/module-to-standalone/forbidden/src/index.ts index 672622f64..8057feea2 100644 --- a/libs/module-to-standalone/forbidden/src/index.ts +++ b/libs/module-to-standalone/forbidden/src/index.ts @@ -1 +1 @@ -export * from './lib/forbidden.module'; +export * from './lib/forbidden.routes'; diff --git a/libs/module-to-standalone/forbidden/src/lib/forbidden.component.ts b/libs/module-to-standalone/forbidden/src/lib/forbidden.component.ts index a5e6e2d77..755cd1516 100644 --- a/libs/module-to-standalone/forbidden/src/lib/forbidden.component.ts +++ b/libs/module-to-standalone/forbidden/src/lib/forbidden.component.ts @@ -5,6 +5,5 @@ import { Component } from '@angular/core'; template: ` Forbidden component `, - standalone: false, }) export class ForbiddenComponent {} diff --git a/libs/module-to-standalone/forbidden/src/lib/forbidden.module.ts b/libs/module-to-standalone/forbidden/src/lib/forbidden.module.ts deleted file mode 100644 index 0b363bf04..000000000 --- a/libs/module-to-standalone/forbidden/src/lib/forbidden.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { ForbiddenComponent } from './forbidden.component'; - -@NgModule({ - declarations: [ForbiddenComponent], - imports: [ - CommonModule, - RouterModule.forChild([{ path: '', component: ForbiddenComponent }]), - ], -}) -export class ForbiddenModule {} diff --git a/libs/module-to-standalone/forbidden/src/lib/forbidden.routes.ts b/libs/module-to-standalone/forbidden/src/lib/forbidden.routes.ts new file mode 100644 index 000000000..777c66446 --- /dev/null +++ b/libs/module-to-standalone/forbidden/src/lib/forbidden.routes.ts @@ -0,0 +1,6 @@ +import { Routes } from '@angular/router'; +import { ForbiddenComponent } from './forbidden.component'; + +export const forbiddenRoutes: Routes = [ + { path: '', component: ForbiddenComponent }, +]; diff --git a/libs/module-to-standalone/home/src/index.ts b/libs/module-to-standalone/home/src/index.ts index fe97ad579..8d2233ecb 100644 --- a/libs/module-to-standalone/home/src/index.ts +++ b/libs/module-to-standalone/home/src/index.ts @@ -1 +1 @@ -export * from './lib/home.module'; +export * from './lib/home.router'; diff --git a/libs/module-to-standalone/home/src/lib/home.component.ts b/libs/module-to-standalone/home/src/lib/home.component.ts index bf350d47d..b6f73e25a 100644 --- a/libs/module-to-standalone/home/src/lib/home.component.ts +++ b/libs/module-to-standalone/home/src/lib/home.component.ts @@ -1,5 +1,6 @@ import { TOKEN } from '@angular-challenges/module-to-standalone/core/providers'; import { AuthorizationService } from '@angular-challenges/module-to-standalone/core/service'; +import { AsyncPipe } from '@angular/common'; import { Component, inject } from '@angular/core'; @Component({ @@ -9,10 +10,10 @@ import { Component, inject } from '@angular/core';
Authorization : - - (isAuthorized: {{ authorizeService.isAuthorized$ | async }}) @@ -20,7 +21,7 @@ import { Component, inject } from '@angular/core';
LoadedToken {{ token }}
`, - standalone: false, + imports: [AsyncPipe], }) export class HomeComponent { public authorizeService = inject(AuthorizationService); diff --git a/libs/module-to-standalone/home/src/lib/home.module.ts b/libs/module-to-standalone/home/src/lib/home.module.ts deleted file mode 100644 index 30ae1ea8a..000000000 --- a/libs/module-to-standalone/home/src/lib/home.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { HomeComponent } from './home.component'; - -@NgModule({ - declarations: [HomeComponent], - imports: [ - RouterModule.forChild([{ path: '', component: HomeComponent }]), - CommonModule, - ], -}) -export class ModuleToStandaloneHomeModule {} diff --git a/libs/module-to-standalone/home/src/lib/home.router.ts b/libs/module-to-standalone/home/src/lib/home.router.ts new file mode 100644 index 000000000..7fca2c06e --- /dev/null +++ b/libs/module-to-standalone/home/src/lib/home.router.ts @@ -0,0 +1,4 @@ +import { Routes } from '@angular/router'; +import { HomeComponent } from './home.component'; + +export const homeRoutes: Routes = [{ path: '', component: HomeComponent }]; diff --git a/libs/module-to-standalone/shell/src/index.ts b/libs/module-to-standalone/shell/src/index.ts index 494d4096f..902420448 100644 --- a/libs/module-to-standalone/shell/src/index.ts +++ b/libs/module-to-standalone/shell/src/index.ts @@ -1 +1 @@ -export * from './lib/main-shell.module'; +export * from './lib/main-shell.routes'; diff --git a/libs/module-to-standalone/shell/src/lib/main-shell.module.ts b/libs/module-to-standalone/shell/src/lib/main-shell.module.ts deleted file mode 100644 index e886da911..000000000 --- a/libs/module-to-standalone/shell/src/lib/main-shell.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { provideToken } from '@angular-challenges/module-to-standalone/core/providers'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { appRoutes } from './main-shell.routes'; - -@NgModule({ - imports: [RouterModule.forRoot(appRoutes)], - exports: [RouterModule], - providers: [provideToken('main-shell-token')], -}) -export class MainShellModule {} diff --git a/libs/module-to-standalone/shell/src/lib/main-shell.routes.ts b/libs/module-to-standalone/shell/src/lib/main-shell.routes.ts index 84f9a8d03..6f52a5f60 100644 --- a/libs/module-to-standalone/shell/src/lib/main-shell.routes.ts +++ b/libs/module-to-standalone/shell/src/lib/main-shell.routes.ts @@ -2,12 +2,16 @@ import { IsAuthorizedGuard } from '@angular-challenges/module-to-standalone/admi import { Route } from '@angular/router'; export const appRoutes: Route[] = [ - { path: '', redirectTo: 'home', pathMatch: 'full' }, + { + path: '', + redirectTo: 'home', + pathMatch: 'full', + }, { path: 'home', loadChildren: () => import('@angular-challenges/module-to-standalone/home').then( - (m) => m.ModuleToStandaloneHomeModule, + (m) => m.homeRoutes, ), }, { @@ -15,22 +19,21 @@ export const appRoutes: Route[] = [ canActivate: [IsAuthorizedGuard], loadChildren: () => import('@angular-challenges/module-to-standalone/admin/feature').then( - (m) => m.AdminFeatureModule, + (m) => m.adminFeatureRoutes, ), }, { path: 'user', loadChildren: () => import('@angular-challenges/module-to-standalone/user/shell').then( - (m) => m.UserShellModule, + (m) => m.userShellRoutes, ), }, - { path: 'forbidden', loadChildren: () => import('@angular-challenges/module-to-standalone/forbidden').then( - (m) => m.ForbiddenModule, + (m) => m.forbiddenRoutes, ), }, ]; diff --git a/libs/module-to-standalone/user/contact/src/contact-feature.routes.ts b/libs/module-to-standalone/user/contact/src/contact-feature.routes.ts new file mode 100644 index 000000000..155b7ce74 --- /dev/null +++ b/libs/module-to-standalone/user/contact/src/contact-feature.routes.ts @@ -0,0 +1,18 @@ +import { Routes } from '@angular/router'; + +export const contactFeatureRoutes: Routes = [ + { + path: '', + loadComponent: () => + import('./lib/dashboard/dashboard.component').then( + (m) => m.ContactDashboardComponent, + ), + }, + { + path: 'create-contact', + loadComponent: () => + import('./lib/create-contact/create-contact.component').then( + (m) => m.CreateContactComponent, + ), + }, +]; diff --git a/libs/module-to-standalone/user/contact/src/index.ts b/libs/module-to-standalone/user/contact/src/index.ts index 8561ea519..c3f79ba6e 100644 --- a/libs/module-to-standalone/user/contact/src/index.ts +++ b/libs/module-to-standalone/user/contact/src/index.ts @@ -1 +1 @@ -export * from './lib/contact-feature.module'; +export * from './contact-feature.routes'; diff --git a/libs/module-to-standalone/user/contact/src/lib/contact-feature.module.ts b/libs/module-to-standalone/user/contact/src/lib/contact-feature.module.ts deleted file mode 100644 index 09da407c7..000000000 --- a/libs/module-to-standalone/user/contact/src/lib/contact-feature.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -@NgModule({ - declarations: [], - imports: [ - CommonModule, - RouterModule.forChild([ - { - path: '', - loadChildren: () => - import('./dashboard/dashboard.component').then( - (m) => m.ContactDashboardModule, - ), - }, - { - path: 'create-contact', - loadChildren: () => - import('./create-contact/create-contact.component').then( - (m) => m.CreateContactModule, - ), - }, - ]), - ], -}) -export class ContactFeatureModule {} diff --git a/libs/module-to-standalone/user/contact/src/lib/create-contact/create-contact.component.ts b/libs/module-to-standalone/user/contact/src/lib/create-contact/create-contact.component.ts index 9c5cea9ff..963eb84b1 100644 --- a/libs/module-to-standalone/user/contact/src/lib/create-contact/create-contact.component.ts +++ b/libs/module-to-standalone/user/contact/src/lib/create-contact/create-contact.component.ts @@ -1,5 +1,5 @@ import { Component, NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; +import { RouterLink, RouterModule } from '@angular/router'; @Component({ selector: 'lib-create-contact', @@ -12,14 +12,14 @@ import { RouterModule } from '@angular/router'; Back `, - standalone: false, + imports: [RouterLink], }) export class CreateContactComponent {} @NgModule({ imports: [ RouterModule.forChild([{ path: '', component: CreateContactComponent }]), + CreateContactComponent, ], - declarations: [CreateContactComponent], }) export class CreateContactModule {} diff --git a/libs/module-to-standalone/user/contact/src/lib/dashboard/dashboard.component.ts b/libs/module-to-standalone/user/contact/src/lib/dashboard/dashboard.component.ts index 8e56b721a..5e05f94ec 100644 --- a/libs/module-to-standalone/user/contact/src/lib/dashboard/dashboard.component.ts +++ b/libs/module-to-standalone/user/contact/src/lib/dashboard/dashboard.component.ts @@ -1,5 +1,5 @@ -import { Component, NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; +import { Component } from '@angular/core'; +import { RouterLink } from '@angular/router'; @Component({ selector: 'lib-contact-dashboard', @@ -12,14 +12,6 @@ import { RouterModule } from '@angular/router'; Create contact `, - standalone: false, + imports: [RouterLink], }) export class ContactDashboardComponent {} - -@NgModule({ - imports: [ - RouterModule.forChild([{ path: '', component: ContactDashboardComponent }]), - ], - declarations: [ContactDashboardComponent], -}) -export class ContactDashboardModule {} diff --git a/libs/module-to-standalone/user/home/src/index.ts b/libs/module-to-standalone/user/home/src/index.ts index fe97ad579..0095f2e67 100644 --- a/libs/module-to-standalone/user/home/src/index.ts +++ b/libs/module-to-standalone/user/home/src/index.ts @@ -1 +1 @@ -export * from './lib/home.module'; +export * from './lib/user.home.routes'; diff --git a/libs/module-to-standalone/user/home/src/lib/home.component.ts b/libs/module-to-standalone/user/home/src/lib/home.component.ts index 253c76622..d5472cb10 100644 --- a/libs/module-to-standalone/user/home/src/lib/home.component.ts +++ b/libs/module-to-standalone/user/home/src/lib/home.component.ts @@ -5,6 +5,5 @@ import { Component } from '@angular/core'; template: ` User Home component `, - standalone: false, }) export class UserHomeComponent {} diff --git a/libs/module-to-standalone/user/home/src/lib/home.module.ts b/libs/module-to-standalone/user/home/src/lib/home.module.ts deleted file mode 100644 index ceeb49511..000000000 --- a/libs/module-to-standalone/user/home/src/lib/home.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { UserHomeComponent } from './home.component'; - -@NgModule({ - declarations: [UserHomeComponent], - imports: [ - RouterModule.forChild([{ path: '', component: UserHomeComponent }]), - ], -}) -export class UserHomeModule {} diff --git a/libs/module-to-standalone/user/home/src/lib/user.home.routes.ts b/libs/module-to-standalone/user/home/src/lib/user.home.routes.ts new file mode 100644 index 000000000..b3c837b2d --- /dev/null +++ b/libs/module-to-standalone/user/home/src/lib/user.home.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; + +export const userHomeRoutes: Routes = [ + { + path: '', + loadComponent: () => + import('./home.component').then((m) => m.UserHomeComponent), + }, +]; diff --git a/libs/module-to-standalone/user/shell/src/index.ts b/libs/module-to-standalone/user/shell/src/index.ts index 641fd6817..bcebb64da 100644 --- a/libs/module-to-standalone/user/shell/src/index.ts +++ b/libs/module-to-standalone/user/shell/src/index.ts @@ -1 +1 @@ -export * from './lib/user-shell.module'; +export * from './lib/user-shell.routes'; diff --git a/libs/module-to-standalone/user/shell/src/lib/user-shell.component.ts b/libs/module-to-standalone/user/shell/src/lib/user-shell.component.ts index 558c4069f..4fa20c126 100644 --- a/libs/module-to-standalone/user/shell/src/lib/user-shell.component.ts +++ b/libs/module-to-standalone/user/shell/src/lib/user-shell.component.ts @@ -1,5 +1,6 @@ import { TOKEN } from '@angular-challenges/module-to-standalone/core/providers'; import { Component, inject } from '@angular/core'; +import { RouterLink, RouterOutlet } from '@angular/router'; @Component({ selector: 'lib-user-shell', @@ -27,7 +28,7 @@ import { Component, inject } from '@angular/core'; host: { class: 'flex flex-col p-4 gap-3 border border-blue', }, - standalone: false, + imports: [RouterLink, RouterOutlet], }) export class UserShellComponent { public token = inject(TOKEN); diff --git a/libs/module-to-standalone/user/shell/src/lib/user-shell.module.ts b/libs/module-to-standalone/user/shell/src/lib/user-shell.module.ts deleted file mode 100644 index 433d6f77b..000000000 --- a/libs/module-to-standalone/user/shell/src/lib/user-shell.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { provideToken } from '@angular-challenges/module-to-standalone/core/providers'; -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { UserShellComponent } from './user-shell.component'; -import { userShellRoutes } from './user-shell.routes'; - -@NgModule({ - imports: [CommonModule, RouterModule.forChild(userShellRoutes), RouterModule], - declarations: [UserShellComponent], - providers: [provideToken('user-token')], -}) -export class UserShellModule {} diff --git a/libs/module-to-standalone/user/shell/src/lib/user-shell.routes.ts b/libs/module-to-standalone/user/shell/src/lib/user-shell.routes.ts index b5813e5d5..9594dc753 100644 --- a/libs/module-to-standalone/user/shell/src/lib/user-shell.routes.ts +++ b/libs/module-to-standalone/user/shell/src/lib/user-shell.routes.ts @@ -1,3 +1,4 @@ +import { provideToken } from '@angular-challenges/module-to-standalone/core/providers'; import { Route } from '@angular/router'; import { UserShellComponent } from './user-shell.component'; @@ -5,20 +6,25 @@ export const userShellRoutes: Route[] = [ { path: '', component: UserShellComponent, + providers: [provideToken('user-token')], children: [ - { path: '', redirectTo: 'home', pathMatch: 'full' }, + { + path: '', + redirectTo: 'home', + pathMatch: 'full', + }, { path: 'home', loadChildren: () => import('@angular-challenges/module-to-standalone/user/home').then( - (m) => m.UserHomeModule, + (m) => m.userHomeRoutes, ), }, { path: 'contact', loadChildren: () => import('@angular-challenges/module-to-standalone/user/contact').then( - (m) => m.ContactFeatureModule, + (m) => m.contactFeatureRoutes, ), }, ],