mirror of
https://github.com/Raghu-Ch/angular-challenges.git
synced 2026-02-13 06:13:03 -05:00
feat(challenge30): interop rxjs signal
This commit is contained in:
36
apps/interop-rxjs-signal/.eslintrc.json
Normal file
36
apps/interop-rxjs-signal/.eslintrc.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"rules": {
|
||||
"@angular-eslint/directive-selector": [
|
||||
"error",
|
||||
{
|
||||
"type": "attribute",
|
||||
"prefix": "app",
|
||||
"style": "camelCase"
|
||||
}
|
||||
],
|
||||
"@angular-eslint/component-selector": [
|
||||
"error",
|
||||
{
|
||||
"type": "element",
|
||||
"prefix": "app",
|
||||
"style": "kebab-case"
|
||||
}
|
||||
]
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@nx/angular",
|
||||
"plugin:@angular-eslint/template/process-inline-templates"
|
||||
]
|
||||
},
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"extends": ["plugin:@nx/angular-template"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
26
apps/interop-rxjs-signal/README.md
Normal file
26
apps/interop-rxjs-signal/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
<h1>interoperability Rxjs and Signal</h1>
|
||||
|
||||
> Author: Thomas Laforge
|
||||
|
||||
### Information
|
||||
|
||||
In this challenge, we have a small reactive application using RxJS and NgRx/Component-Store.
|
||||
|
||||
The goal of this challenge is to use the new **Signal API** introduced in Angular v16. However, we should not convert everything. Certain portions of the code are better suited for RxJS rather than Signal. It is up to you to determine the threshold and observe how **Signal and RxJS coexist**, as well as how the interoperability is achieved in Angular.
|
||||
|
||||
### Submitting your work
|
||||
|
||||
1. Fork the project
|
||||
2. clone it
|
||||
3. npm ci
|
||||
4. `npx nx serve interop-rxjs-signal`
|
||||
5. _...work on it_
|
||||
6. Commit your work
|
||||
7. Submit a PR with a title beginning with **Answer:30** that I will review and other dev can review.
|
||||
|
||||
<a href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A30+label%3Aanswer"><img src="https://img.shields.io/badge/-Solutions-green" alt="interop-rxjs-signal"/></a>
|
||||
<a href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A30+label%3A"answer+author"'><img src="https://img.shields.io/badge/-Author solution-important" alt="interop-rxjs-signal solution author"/></a>
|
||||
|
||||
<!-- <a href="{Blog post url}" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/-Blog post explanation-blue" alt="interop-rxjs-signal blog article"/></a> -->
|
||||
|
||||
_You can ask any question on_ <a href="https://twitter.com/laforge_toma" target="_blank" rel="noopener noreferrer"><img src="./../../logo/twitter.svg" height=20px alt="twitter"/></a>
|
||||
22
apps/interop-rxjs-signal/jest.config.ts
Normal file
22
apps/interop-rxjs-signal/jest.config.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'interop-rxjs-signal',
|
||||
preset: '../../jest.preset.js',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||
coverageDirectory: '../../coverage/apps/interop-rxjs-signal',
|
||||
transform: {
|
||||
'^.+\\.(ts|mjs|js|html)$': [
|
||||
'jest-preset-angular',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
},
|
||||
],
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||
'jest-preset-angular/build/serializers/html-comment',
|
||||
],
|
||||
};
|
||||
98
apps/interop-rxjs-signal/project.json
Normal file
98
apps/interop-rxjs-signal/project.json
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"name": "interop-rxjs-signal",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"sourceRoot": "apps/interop-rxjs-signal/src",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@angular-devkit/build-angular:browser",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/apps/interop-rxjs-signal",
|
||||
"index": "apps/interop-rxjs-signal/src/index.html",
|
||||
"main": "apps/interop-rxjs-signal/src/main.ts",
|
||||
"polyfills": ["zone.js"],
|
||||
"tsConfig": "apps/interop-rxjs-signal/tsconfig.app.json",
|
||||
"assets": [
|
||||
"apps/interop-rxjs-signal/src/favicon.ico",
|
||||
"apps/interop-rxjs-signal/src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"apps/interop-rxjs-signal/src/styles.scss",
|
||||
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "interop-rxjs-signal:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "interop-rxjs-signal:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"executor": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "interop-rxjs-signal:build"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"apps/interop-rxjs-signal/**/*.ts",
|
||||
"apps/interop-rxjs-signal/**/*.html"
|
||||
]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "apps/interop-rxjs-signal/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
apps/interop-rxjs-signal/src/app/app.component.ts
Normal file
11
apps/interop-rxjs-signal/src/app/app.component.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [RouterOutlet],
|
||||
selector: 'app-root',
|
||||
template: `<router-outlet />`,
|
||||
styles: [''],
|
||||
})
|
||||
export class AppComponent {}
|
||||
29
apps/interop-rxjs-signal/src/app/app.config.ts
Normal file
29
apps/interop-rxjs-signal/src/app/app.config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { ApplicationConfig } from '@angular/core';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { provideRouter, withComponentInputBinding } from '@angular/router';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
provideRouter(
|
||||
[
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
loadComponent: () => import('./list/photos.component'),
|
||||
},
|
||||
{
|
||||
path: 'detail',
|
||||
loadComponent: () => import('./detail/detail.component'),
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: '',
|
||||
},
|
||||
],
|
||||
withComponentInputBinding()
|
||||
),
|
||||
],
|
||||
};
|
||||
33
apps/interop-rxjs-signal/src/app/detail/detail.component.ts
Normal file
33
apps/interop-rxjs-signal/src/app/detail/detail.component.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, Input as RouterInput } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { Photo } from '../photo.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-photos',
|
||||
standalone: true,
|
||||
imports: [DatePipe, RouterLink],
|
||||
template: `
|
||||
<img src="{{ photo.url_m }}" alt="{{ photo.title }}" class="image" />
|
||||
<p><span class="font-bold">Title:</span> {{ photo.title }}</p>
|
||||
<p><span class="font-bold">Owner:</span> {{ photo.ownername }}</p>
|
||||
<p><span class="font-bold">Date:</span> {{ photo.datetaken | date }}</p>
|
||||
<p><span class="font-bold">Tags:</span> {{ photo.tags }}</p>
|
||||
|
||||
<button
|
||||
class="border border-black rounded-md px-4 py-2 mt-10"
|
||||
routerLink="">
|
||||
Back
|
||||
</button>
|
||||
`,
|
||||
host: {
|
||||
class: 'p-5 block',
|
||||
},
|
||||
})
|
||||
export default class DetailComponent {
|
||||
@RouterInput({
|
||||
required: true,
|
||||
transform: (value: string) => JSON.parse(decodeURIComponent(value)),
|
||||
})
|
||||
photo!: Photo;
|
||||
}
|
||||
119
apps/interop-rxjs-signal/src/app/list/photos.component.ts
Normal file
119
apps/interop-rxjs-signal/src/app/list/photos.component.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { NgFor, NgIf } from '@angular/common';
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { RouterLinkWithHref } from '@angular/router';
|
||||
import { LetDirective } from '@ngrx/component';
|
||||
import { provideComponentStore } from '@ngrx/component-store';
|
||||
import { debounceTime, distinctUntilChanged, skipWhile, tap } from 'rxjs';
|
||||
import { Photo } from '../photo.model';
|
||||
import { PhotoStore } from './photos.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-photos',
|
||||
standalone: true,
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatFormFieldModule,
|
||||
MatProgressBarModule,
|
||||
NgIf,
|
||||
NgFor,
|
||||
MatInputModule,
|
||||
LetDirective,
|
||||
RouterLinkWithHref,
|
||||
],
|
||||
template: `
|
||||
<h2 class="text-xl mb-2">Photos</h2>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Search</mat-label>
|
||||
<input
|
||||
type="text"
|
||||
matInput
|
||||
[formControl]="search"
|
||||
placeholder="write an article" />
|
||||
</mat-form-field>
|
||||
|
||||
<ng-container *ngrxLet="vm$ as vm">
|
||||
<section class="flex flex-col">
|
||||
<section class="flex gap-3 items-center">
|
||||
<button
|
||||
[disabled]="vm.page === 1"
|
||||
[class.bg-gray-400]="vm.page === 1"
|
||||
class="text-xl border rounded-md p-3"
|
||||
(click)="store.previousPage()">
|
||||
<
|
||||
</button>
|
||||
<button
|
||||
[disabled]="vm.endOfPage"
|
||||
[class.bg-gray-400]="vm.endOfPage"
|
||||
class="text-xl border rounded-md p-3"
|
||||
(click)="store.nextPage()">
|
||||
>
|
||||
</button>
|
||||
Page :{{ vm.page }} / {{ vm.pages }}
|
||||
</section>
|
||||
<mat-progress-bar
|
||||
mode="query"
|
||||
*ngIf="vm.loading"
|
||||
class="mt-5"></mat-progress-bar>
|
||||
<ul
|
||||
class="flex flex-wrap gap-4"
|
||||
*ngIf="vm.photos && vm.photos.length > 0; else noPhoto">
|
||||
<li *ngFor="let photo of vm.photos; trackBy: trackById">
|
||||
<a routerLink="detail" [queryParams]="{ photo: encode(photo) }">
|
||||
<img
|
||||
src="{{ photo.url_q }}"
|
||||
alt="{{ photo.title }}"
|
||||
class="image" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ng-template #noPhoto>
|
||||
<div>No Photos found. Type a search word.</div>
|
||||
</ng-template>
|
||||
<footer class="text-red-500">
|
||||
{{ vm.error }}
|
||||
</footer>
|
||||
</section>
|
||||
</ng-container>
|
||||
`,
|
||||
providers: [provideComponentStore(PhotoStore)],
|
||||
host: {
|
||||
class: 'p-5 block',
|
||||
},
|
||||
})
|
||||
export default class PhotosComponent implements OnInit {
|
||||
store = inject(PhotoStore);
|
||||
readonly vm$ = this.store.vm$.pipe(
|
||||
tap(({ search }) => {
|
||||
if (!this.formInit) {
|
||||
this.search.setValue(search);
|
||||
this.formInit = true;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
private formInit = false;
|
||||
search = new FormControl();
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.search(
|
||||
this.search.valueChanges.pipe(
|
||||
skipWhile(() => !this.formInit),
|
||||
debounceTime(300),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
trackById(index: number, photo: Photo) {
|
||||
return photo.id;
|
||||
}
|
||||
|
||||
encode(photo: Photo) {
|
||||
return encodeURIComponent(JSON.stringify(photo));
|
||||
}
|
||||
}
|
||||
135
apps/interop-rxjs-signal/src/app/list/photos.store.ts
Normal file
135
apps/interop-rxjs-signal/src/app/list/photos.store.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import {
|
||||
ComponentStore,
|
||||
OnStateInit,
|
||||
OnStoreInit,
|
||||
tapResponse,
|
||||
} from '@ngrx/component-store';
|
||||
import { pipe } from 'rxjs';
|
||||
import { filter, mergeMap, tap } from 'rxjs/operators';
|
||||
import { Photo } from '../photo.model';
|
||||
import { PhotoService } from '../photos.service';
|
||||
|
||||
const PHOTO_STATE_KEY = 'photo_search';
|
||||
|
||||
export interface PhotoState {
|
||||
photos: Photo[];
|
||||
search: string;
|
||||
page: number;
|
||||
pages: number;
|
||||
loading: boolean;
|
||||
error: unknown;
|
||||
}
|
||||
|
||||
const initialState: PhotoState = {
|
||||
photos: [],
|
||||
search: '',
|
||||
page: 1,
|
||||
pages: 1,
|
||||
loading: false,
|
||||
error: '',
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class PhotoStore
|
||||
extends ComponentStore<PhotoState>
|
||||
implements OnStoreInit, OnStateInit
|
||||
{
|
||||
private photoService = inject(PhotoService);
|
||||
|
||||
private readonly photos$ = this.select((s) => s.photos);
|
||||
private readonly search$ = this.select((s) => s.search);
|
||||
private readonly page$ = this.select((s) => s.page);
|
||||
private readonly pages$ = this.select((s) => s.pages);
|
||||
private readonly error$ = this.select((s) => s.error);
|
||||
private readonly loading$ = this.select((s) => s.loading);
|
||||
|
||||
private readonly endOfPage$ = this.select(
|
||||
this.page$,
|
||||
this.pages$,
|
||||
(page, pages) => page === pages
|
||||
);
|
||||
|
||||
readonly vm$ = this.select(
|
||||
{
|
||||
photos: this.photos$,
|
||||
search: this.search$,
|
||||
page: this.page$,
|
||||
pages: this.pages$,
|
||||
endOfPage: this.endOfPage$,
|
||||
loading: this.loading$,
|
||||
error: this.error$,
|
||||
},
|
||||
{ debounce: true }
|
||||
);
|
||||
|
||||
ngrxOnStoreInit() {
|
||||
const savedJSONState = localStorage.getItem(PHOTO_STATE_KEY);
|
||||
if (savedJSONState === null) {
|
||||
this.setState(initialState);
|
||||
} else {
|
||||
const savedState = JSON.parse(savedJSONState);
|
||||
this.setState({
|
||||
...initialState,
|
||||
search: savedState.search,
|
||||
page: savedState.page,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngrxOnStateInit() {
|
||||
this.searchPhotos(
|
||||
this.select({
|
||||
search: this.search$,
|
||||
page: this.page$,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
readonly search = this.updater(
|
||||
(state, search: string): PhotoState => ({
|
||||
...state,
|
||||
search,
|
||||
page: 1,
|
||||
})
|
||||
);
|
||||
|
||||
readonly nextPage = this.updater(
|
||||
(state): PhotoState => ({
|
||||
...state,
|
||||
page: state.page + 1,
|
||||
})
|
||||
);
|
||||
|
||||
readonly previousPage = this.updater(
|
||||
(state): PhotoState => ({
|
||||
...state,
|
||||
page: state.page - 1,
|
||||
})
|
||||
);
|
||||
|
||||
readonly searchPhotos = this.effect<{ search: string; page: number }>(
|
||||
pipe(
|
||||
filter(({ search }) => search.length >= 3),
|
||||
tap(() => this.patchState({ loading: true, error: '' })),
|
||||
mergeMap(({ search, page }) =>
|
||||
this.photoService.searchPublicPhotos(search, page).pipe(
|
||||
tapResponse(
|
||||
({ photos: { photo, pages } }) => {
|
||||
this.patchState({
|
||||
loading: false,
|
||||
photos: photo,
|
||||
pages,
|
||||
});
|
||||
localStorage.setItem(
|
||||
PHOTO_STATE_KEY,
|
||||
JSON.stringify({ search, page })
|
||||
);
|
||||
},
|
||||
(error: unknown) => this.patchState({ error, loading: false })
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
10
apps/interop-rxjs-signal/src/app/photo.model.ts
Normal file
10
apps/interop-rxjs-signal/src/app/photo.model.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface Photo {
|
||||
id: string;
|
||||
title: string;
|
||||
tags: string;
|
||||
owner: string;
|
||||
ownername: string;
|
||||
datetaken: string;
|
||||
url_q: string;
|
||||
url_m: string;
|
||||
}
|
||||
39
apps/interop-rxjs-signal/src/app/photos.service.ts
Normal file
39
apps/interop-rxjs-signal/src/app/photos.service.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Photo } from './photo.model';
|
||||
|
||||
export interface FlickrAPIResponse {
|
||||
photos: {
|
||||
pages: number;
|
||||
photo: Photo[];
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PhotoService {
|
||||
private http = inject(HttpClient);
|
||||
|
||||
public searchPublicPhotos(
|
||||
searchTerm: string,
|
||||
page: number
|
||||
): Observable<FlickrAPIResponse> {
|
||||
return this.http.get<FlickrAPIResponse>(
|
||||
'https://www.flickr.com/services/rest/',
|
||||
{
|
||||
params: {
|
||||
tags: searchTerm,
|
||||
method: 'flickr.photos.search',
|
||||
format: 'json',
|
||||
nojsoncallback: '1',
|
||||
tag_mode: 'all',
|
||||
media: 'photos',
|
||||
per_page: '30',
|
||||
page,
|
||||
extras: 'tags,date_taken,owner_name,url_q,url_m',
|
||||
api_key: 'c3050d39a5bb308d9921bef0e15c437d',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
0
apps/interop-rxjs-signal/src/assets/.gitkeep
Normal file
0
apps/interop-rxjs-signal/src/assets/.gitkeep
Normal file
BIN
apps/interop-rxjs-signal/src/favicon.ico
Normal file
BIN
apps/interop-rxjs-signal/src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
13
apps/interop-rxjs-signal/src/index.html
Normal file
13
apps/interop-rxjs-signal/src/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>rxjs-to-signal</title>
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
7
apps/interop-rxjs-signal/src/main.ts
Normal file
7
apps/interop-rxjs-signal/src/main.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { AppComponent } from './app/app.component';
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||
console.error(err)
|
||||
);
|
||||
5
apps/interop-rxjs-signal/src/styles.scss
Normal file
5
apps/interop-rxjs-signal/src/styles.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
2
apps/interop-rxjs-signal/src/test-setup.ts
Normal file
2
apps/interop-rxjs-signal/src/test-setup.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import 'jest-preset-angular/setup-jest';
|
||||
14
apps/interop-rxjs-signal/tailwind.config.js
Normal file
14
apps/interop-rxjs-signal/tailwind.config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
|
||||
const { join } = require('path');
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
|
||||
...createGlobPatternsForDependencies(__dirname),
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
10
apps/interop-rxjs-signal/tsconfig.app.json
Normal file
10
apps/interop-rxjs-signal/tsconfig.app.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": []
|
||||
},
|
||||
"files": ["src/main.ts"],
|
||||
"include": ["src/**/*.d.ts"],
|
||||
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
|
||||
}
|
||||
7
apps/interop-rxjs-signal/tsconfig.editor.json
Normal file
7
apps/interop-rxjs-signal/tsconfig.editor.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src/**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"types": ["jest", "node"]
|
||||
}
|
||||
}
|
||||
32
apps/interop-rxjs-signal/tsconfig.json
Normal file
32
apps/interop-rxjs-signal/tsconfig.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"useDefineForClassFields": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.editor.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
15
apps/interop-rxjs-signal/tsconfig.spec.json
Normal file
15
apps/interop-rxjs-signal/tsconfig.spec.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node", "@testing-library/jest-dom"]
|
||||
},
|
||||
"files": ["src/test-setup.ts"],
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user