├── .editorconfig
├── .gitignore
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
├── app
│ ├── app-routing.module.ts
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.spec.ts
│ └── app.component.ts
├── assets
│ ├── .gitkeep
│ └── images
│ │ └── signal-notif.png
├── favicon.ico
├── index.html
├── main.ts
└── styles.scss
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AngularSignals
2 |
3 | ## This project 🎯 explains Angular signals and demonstrates how to create and update signals, use effects, and create computed values
4 |
5 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.0.0-next.2.
6 |
7 | ## Development server
8 |
9 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
10 |
11 | ## Code scaffolding
12 |
13 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
14 |
15 | ## Build
16 |
17 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
18 |
19 | ## Running unit tests
20 |
21 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
22 |
23 | ## Running end-to-end tests
24 |
25 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
26 |
27 | ## Further help
28 |
29 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
30 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "angular-signals": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "style": "scss"
11 | }
12 | },
13 | "root": "",
14 | "sourceRoot": "src",
15 | "prefix": "app",
16 | "architect": {
17 | "build": {
18 | "builder": "@angular-devkit/build-angular:browser",
19 | "options": {
20 | "outputPath": "dist/angular-signals",
21 | "index": "src/index.html",
22 | "main": "src/main.ts",
23 | "polyfills": [
24 | "zone.js"
25 | ],
26 | "tsConfig": "tsconfig.app.json",
27 | "inlineStyleLanguage": "scss",
28 | "assets": [
29 | "src/favicon.ico",
30 | "src/assets"
31 | ],
32 | "styles": [
33 | "@angular/material/prebuilt-themes/pink-bluegrey.css",
34 | "src/styles.scss"
35 | ],
36 | "scripts": []
37 | },
38 | "configurations": {
39 | "production": {
40 | "budgets": [
41 | {
42 | "type": "initial",
43 | "maximumWarning": "500kb",
44 | "maximumError": "1mb"
45 | },
46 | {
47 | "type": "anyComponentStyle",
48 | "maximumWarning": "2kb",
49 | "maximumError": "4kb"
50 | }
51 | ],
52 | "outputHashing": "all"
53 | },
54 | "development": {
55 | "buildOptimizer": false,
56 | "optimization": false,
57 | "vendorChunk": true,
58 | "extractLicenses": false,
59 | "sourceMap": true,
60 | "namedChunks": true
61 | }
62 | },
63 | "defaultConfiguration": "production"
64 | },
65 | "serve": {
66 | "builder": "@angular-devkit/build-angular:dev-server",
67 | "configurations": {
68 | "production": {
69 | "browserTarget": "angular-signals:build:production"
70 | },
71 | "development": {
72 | "browserTarget": "angular-signals:build:development"
73 | }
74 | },
75 | "defaultConfiguration": "development"
76 | },
77 | "extract-i18n": {
78 | "builder": "@angular-devkit/build-angular:extract-i18n",
79 | "options": {
80 | "browserTarget": "angular-signals:build"
81 | }
82 | },
83 | "test": {
84 | "builder": "@angular-devkit/build-angular:karma",
85 | "options": {
86 | "polyfills": [
87 | "zone.js",
88 | "zone.js/testing"
89 | ],
90 | "tsConfig": "tsconfig.spec.json",
91 | "inlineStyleLanguage": "scss",
92 | "assets": [
93 | "src/favicon.ico",
94 | "src/assets"
95 | ],
96 | "styles": [
97 | "@angular/material/prebuilt-themes/pink-bluegrey.css",
98 | "src/styles.scss"
99 | ],
100 | "scripts": []
101 | }
102 | }
103 | }
104 | }
105 | },
106 | "cli": {
107 | "analytics": false
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-signals",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^16.0.0-next.7",
14 | "@angular/cdk": "^16.0.0-next.0",
15 | "@angular/common": "^16.0.0-next.7",
16 | "@angular/compiler": "^16.0.0-next.7",
17 | "@angular/core": "^16.0.0-next.7",
18 | "@angular/forms": "^16.0.0-next.7",
19 | "@angular/material": "^16.0.0-next.0",
20 | "@angular/platform-browser": "^16.0.0-next.7",
21 | "@angular/platform-browser-dynamic": "^16.0.0-next.7",
22 | "@angular/router": "^16.0.0-next.7",
23 | "rxjs": "~7.8.0",
24 | "tslib": "^2.3.0",
25 | "zone.js": "~0.13.0"
26 | },
27 | "devDependencies": {
28 | "@angular-devkit/build-angular": "^16.0.0-next.7",
29 | "@angular/cli": "~16.0.0-next.7",
30 | "@angular/compiler-cli": "^16.0.0-next.7",
31 | "@types/jasmine": "~4.3.0",
32 | "jasmine-core": "~4.5.0",
33 | "karma": "~6.4.0",
34 | "karma-chrome-launcher": "~3.1.0",
35 | "karma-coverage": "~2.2.0",
36 | "karma-jasmine": "~5.1.0",
37 | "karma-jasmine-html-reporter": "~2.0.0",
38 | "typescript": "~4.9.4"
39 | }
40 | }
--------------------------------------------------------------------------------
/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 |
4 | const routes: Routes = [];
5 |
6 | @NgModule({
7 | imports: [RouterModule.forRoot(routes)],
8 | exports: [RouterModule]
9 | })
10 | export class AppRoutingModule { }
11 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | What are Angular Signals 🚦?
5 | {{response1.hidden ? 'expand_more' : 'chevron_right'}}
6 |
7 |
8 |
Signals are a reactive value, technically are a zero-argument functions [(() => T)]
. When executed, they return the current value of the signal.
9 |
10 |
11 |
12 |
13 |
14 | how do we create a signal ?
15 | {{response2.hidden ? 'expand_more' : 'chevron_right'}}
16 |
17 |
18 |
To create and initialize a signal, we call the signal()
function:
19 |
const movies = signal<Movie[]>([]);
20 |
This function returns a specific type of signal called WritableSignal
.
21 |
Signals are getter functions, but the WritableSignal
type gives us the ability to modify the value using three methods:
22 |
23 | set(value: T)
: set the signal to a new value, and notify any dependents
24 | update(fn: (value: T) => T)
: deriving a new value (Update the value of the signal based on its current value, and notify any dependents), The update operation uses the set()
operation for performing updates behind the scenes.
25 | modify(fn: (value: T) => void)
: Performing an internal mutation of the current value (Update the current value by mutating it in-place, and notify any dependents)
26 |
27 |
28 |
29 |
30 |
31 |
32 | But what does it mean to notify any dependents?" 😕?
33 | {{response3.hidden ? 'expand_more' : 'chevron_right'}}
34 |
35 |
36 |
This is the magic part of Signal. Now we will see what dependents are for Signals and how to notify them.
37 |
Signal is not just a value that can be modified, it is more than that, Signal is a reactive value and is a producer that notify consumers(dependents) when it changes.
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | So, how we can add consumer to Signal ?
46 | {{response4.hidden ? 'expand_more' : 'chevron_right'}}
47 |
48 |
49 |
We can add consumers by using Effect and Computed
50 |
Effect
51 |
Sometimes, when a signal has a new value, we may need to add a side effect. To accomplish this, we can use the effect()
function.
52 |
Effect schedules and runs a side-effectful function inside a reactive context.
53 |
Signature of the effect function:
54 |
55 | export function effect(effectFn: () => EffectCleanupFn | void, options?: CreateEffectOptions): EffectRef
56 |
57 |
58 |
59 |
60 |
Computed
61 |
What if there is another value that depends on the values of other signals, and needs to be recalculated whenever any of those dependencies changes?
62 |
To accomplish this, we can use the computed()
function. Computed creates a memoizing signal, which calculates its value from the values of some number of input signals.
63 |
Signature of the computed function:
64 |
65 | export function computed<T>(computation: () => T, options?: CreateComputedOptions<T>): Signal<T>
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
I create a signal in the component with movieSig = signal<Movie|null>(null);
74 |
75 |
And I create another signal using computed()
76 |
77 | newMovieSig = computed( () => {{'{'}}
78 | let newMovie ={{'{'}}name : "Titanic",genre : "Romance",releaseYear: 1997, upVote:this.movieSig()?.upVote{{'}'}};
79 | return newMovie;
80 | {{'}'}}
81 |
82 |
83 |
84 |
85 |
And this is the informations of the movie, as you can see, there is no movie because we initialize our Signal with null
86 |
87 |
First Signal
88 |
89 |
Name:
90 |
{{movieSig()?.name}}
91 |
92 |
93 |
Genre:
94 |
{{movieSig()?.genre}}
95 |
96 |
97 |
Release Year:
98 |
{{movieSig()?.releaseYear}}
99 |
100 |
101 |
Up Vote:
102 |
{{movieSig()?.upVote}}
103 |
104 |
105 |
106 |
Signal Computed
107 |
108 |
Name:
109 |
{{newMovieSig().name}}
110 |
111 |
112 |
Genre:
113 |
{{newMovieSig().genre}}
114 |
115 |
116 |
Release Year:
117 |
{{newMovieSig().releaseYear}}
118 |
119 |
120 |
Up Vote:
121 |
{{newMovieSig().upVote}}
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | This Set() will execute this code to set new value :
132 | movieSig.set({{'{'}}
133 | name: 'Spider-Man',
134 | genre: 'Action,
135 | Aventure',
136 | releaseYear: 2002{{'{'}})
137 |
138 |
139 |
140 |
141 | This update() will execute this code to derived a value from the current value :
142 | movieSig.update((movie) => {{'{'}}
143 | if(movie)
144 | movie.upVote+1;
145 | return movie;
146 | })
147 |
148 |
149 |
150 |
151 | This mutate() will execute this code to mutate the value :
152 | movieSig.mutate((movie) => {{'{'}}
153 | if (movie) {{'{'}}
154 | movie.upVote = 2000;
155 | {{'}'}}{{'}'}});
156 |
157 |
158 |
159 |
160 |
161 | This effect() will execute this code to create an effect :
162 | runInInjectionContext(this.injector,
163 | () =>(this.effectSig = effect(() => {{'{'}}
164 | alert(`side effect angular signal after movie changes ${{'{'}}JSON.stringify(this.movieSig()
165 | ){{'}'}}`
166 | );{{'}'}})));
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 | This destroy() will execute this code to destroy the effect :
176 | effectSig?.destroy();
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ziedzayani/angular-signal-demo/b90f68a3d17428e30005e30a88c0a754698d18f1/src/app/app.component.scss
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(async () => {
7 | await TestBed.configureTestingModule({
8 | imports: [
9 | RouterTestingModule,
10 | AppComponent
11 | ]
12 | }).compileComponents();
13 | });
14 |
15 | it('should create the app', () => {
16 | const fixture = TestBed.createComponent(AppComponent);
17 | const app = fixture.componentInstance;
18 | expect(app).toBeTruthy();
19 | });
20 |
21 | it(`should have as title 'angular-signals'`, () => {
22 | const fixture = TestBed.createComponent(AppComponent);
23 | const app = fixture.componentInstance;
24 | expect(app.title).toEqual('angular-signals');
25 | });
26 |
27 | it('should render title', () => {
28 | const fixture = TestBed.createComponent(AppComponent);
29 | fixture.detectChanges();
30 | const compiled = fixture.nativeElement as HTMLElement;
31 | expect(compiled.querySelector('.content span')?.textContent).toContain('angular-signals app is running!');
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Component,
3 | signal,
4 | computed,
5 | Signal,
6 | EffectRef,
7 | effect,
8 | Injector,
9 | Inject,
10 | inject,
11 | EnvironmentInjector,
12 | runInInjectionContext,
13 | } from '@angular/core';
14 | import { RouterOutlet } from '@angular/router';
15 | import { MatIconModule } from '@angular/material/icon';
16 | import {
17 | trigger,
18 | state,
19 | style,
20 | transition,
21 | animate,
22 | } from '@angular/animations';
23 | import { MatButtonModule } from '@angular/material/button';
24 |
25 | export interface Movie {
26 | name: string;
27 | genre: string;
28 | releaseYear: number;
29 | upVote: number;
30 | }
31 |
32 | @Component({
33 | selector: 'app-root',
34 | templateUrl: './app.component.html',
35 | styleUrls: ['./app.component.scss'],
36 | standalone: true,
37 | imports: [MatIconModule, RouterOutlet, MatButtonModule],
38 | animations: [
39 | trigger('slideInOut', [
40 | state(
41 | 'in',
42 | style({
43 | 'max-height': '1000px',
44 | opacity: '1',
45 | visibility: 'visible',
46 | })
47 | ),
48 | state(
49 | 'out',
50 | style({
51 | 'max-height': '0px',
52 | opacity: '0',
53 | visibility: 'hidden',
54 | })
55 | ),
56 | transition('in => out', [animate('400ms ease-in-out')]),
57 | transition('out => in', [animate('400ms ease-in-out')]),
58 | ]),
59 | ],
60 | })
61 | export class AppComponent {
62 | movieSig = signal(null);
63 | injector = inject(EnvironmentInjector);
64 | newMovieSig = computed(() => {
65 | let newMovie = {
66 | name: 'Titanic',
67 | genre: 'Romance',
68 | releaseYear: 1997,
69 | upVote: this.movieSig()?.upVote,
70 | };
71 | return newMovie;
72 | });
73 | effectSig!: EffectRef;
74 | title = 'angular-signals';
75 |
76 | setMovie() {
77 | this.movieSig.set({
78 | name: 'Spider-Man',
79 | genre: 'Action, Aventure',
80 | releaseYear: 2002,
81 | upVote: 8,
82 | });
83 | }
84 |
85 | updateMovie() {
86 | this.movieSig.update((movie) => {
87 | if (movie) movie.upVote = movie.upVote + 1;
88 | return movie;
89 | });
90 | }
91 |
92 | mutateMovie() {
93 | this.movieSig.mutate((movie) => {
94 | if (movie) {
95 | movie.upVote = 2000;
96 | }
97 | });
98 | }
99 |
100 | createEffect() {
101 | runInInjectionContext(this.injector,
102 | () =>
103 | (this.effectSig = effect(() => {
104 | alert(
105 | `side effect angular signal after movie changes ${JSON.stringify(
106 | this.movieSig()
107 | )}`
108 | );
109 | }))
110 | );
111 | }
112 |
113 | destroyEffect() {
114 | this.effectSig?.destroy();
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ziedzayani/angular-signal-demo/b90f68a3d17428e30005e30a88c0a754698d18f1/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/images/signal-notif.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ziedzayani/angular-signal-demo/b90f68a3d17428e30005e30a88c0a754698d18f1/src/assets/images/signal-notif.png
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
1 | �PNG
2 |
3 |
IHDR ?�~� pHYs ��~� fIDATH��WKLQ���̔�uG�� e�n�.6qcb�l?���D`�F#�
Ku�F
1Qc�
4 | ��!���� ��C�P�|B?$���ܱ3����I&}��}�̽s�[*�ɀU�A� �K��yx�gY�Ajq��3L Š� ��˫�OD�4��3Ϗ:X�3��o�PJ�ğo#IH�a����,{>1/�2$�R AR]�)w��?�sZw^��q�Y�m_��e���r[8�^�
5 | �&p��-���A}c��- ������!����2_)E�)㊪j���v�m� �ZOi�g�nW�{�n.|�e2�a&�0aŸ����be�̀��C�fˤE%-{�ֺ��C��N��jXi�~c�C,t��T�����r �{� �L)s��V��6%�(�#ᤙ!�]��H�ҐH$R���^R�A�61(?Y舚�>���(Z����Qm�L2�K�ZIc��
6 | ���̧�C��2!⅄ �(����" �Go��>�q��=��$%�z`ѯ��T�&����PHh�Z!=� ��z��O��������,*VVV�1�f*CJ�]EE��K�k��d�#5���`2yT!�}7���߈~�,���zs�����y�T��V������D��C2�G��@%̑72Y�{oJ�"@��^h�~��fĬ�!a�D��6���Ha|�3��� [>�����]7U2п���] �ė
7 | ��PU��.Wejq�in�g��+ p<ߺQH����總j[������.��� Q���p _�K��1(��+��bB8�\ra
8 | �́�v.l���(���ǽ�w���L��w�8�C�� IEND�B`�
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AngularSignals
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 |
3 |
4 | import { importProvidersFrom } from '@angular/core';
5 | import { AppComponent } from './app/app.component';
6 | import { provideAnimations } from '@angular/platform-browser/animations';
7 | import { AppRoutingModule } from './app/app-routing.module';
8 | import { BrowserModule, bootstrapApplication } from '@angular/platform-browser';
9 |
10 |
11 | bootstrapApplication(AppComponent, {
12 | providers: [
13 | importProvidersFrom(BrowserModule, AppRoutingModule),
14 | provideAnimations()
15 | ]
16 | })
17 | .catch(err => console.error(err));
18 |
--------------------------------------------------------------------------------
/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
3 | html { height: 100%; }
4 |
5 | body {
6 | background: linear-gradient(to bottom right, #0f172a, #1a2f49);
7 | color: #ffffff;
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | justify-content: center;
12 | height: 100vh;
13 | margin: 0;
14 | overflow-x: hidden;
15 | background-attachment: fixed;
16 | }
17 |
18 | code {
19 | font-family: 'Consolas', 'Courier New', monospace;
20 | background-color: #334155;
21 | color: #d4d4d4;
22 | padding: 0.2rem 0.4rem;
23 | border-radius: 3px;
24 | display: inline-block;
25 | }
26 | .container {
27 | position: absolute;
28 | top: 50px;
29 | left: 50%;
30 | transform: translateX(-50%);
31 | width: 80%;
32 | }
33 |
34 | .container {
35 | max-width: 70%;
36 | margin: auto;
37 | }
38 |
39 | .bloc {
40 | display: flex;
41 | align-items: center;
42 | justify-content: space-between;
43 | padding: 10px;
44 | background-color: #334155;
45 | border-radius: 5px;
46 | margin-bottom: 10px;
47 | cursor: pointer;
48 | }
49 |
50 | .bloc mat-icon {
51 | font-size: 1.2rem;
52 | transition: transform 0.6s ease-in-out;
53 | }
54 |
55 | .bloc p {
56 | margin: 0;
57 | padding: 10px;
58 | background-color: #334155;
59 | border-radius: 5px;
60 | overflow: hidden;
61 | }
62 |
63 | .image-container {
64 | margin-bottom: 20px;
65 | text-align: center;
66 | overflow: auto;
67 | padding-bottom: 20px;
68 | }
69 |
70 | .bloc p.animate-show {
71 | animation: show 0.6s ease-in-out forwards;
72 | }
73 |
74 | .bloc p.animate-hide {
75 | animation: hide 0.6s ease-in-out forwards;
76 | }
77 |
78 | .response {
79 | margin: 2rem;
80 | margin-bottom: 20px;
81 | }
82 |
83 | .reactive-space {
84 | font-size: 0.8rem;
85 | padding: 2rem;
86 | max-width: 100%;
87 | height: 1000px;
88 | background-color: #07172e;
89 |
90 | }
91 | .info-container {
92 | display: flex;
93 | flex-wrap: wrap;
94 | justify-content: flex-start;
95 | }
96 |
97 | .row {
98 | display: flex;
99 | align-items: center;
100 | margin-bottom: 10px;
101 | flex-basis: 50%;
102 | }
103 |
104 | .row label {
105 | width: 120px;
106 | margin-right: 10px;
107 | }
108 |
109 | .column-container {
110 | display: flex;
111 | }
112 |
113 | .column-container .column {
114 | flex-basis: 50%;
115 | height: 30rem;
116 | padding: 0 10px;
117 | }
118 |
119 | .column-container .column p {
120 | height: 10rem;
121 | }
122 |
123 | @media only screen and (max-width: 800px) {
124 | .row {
125 | display: block;
126 | }
127 | .row > div {
128 | display: inline-block;
129 | margin-bottom: 10px;
130 | }
131 | }
132 |
133 | @media only screen and (max-width: 1100px) {
134 | div {
135 | display: flex;
136 | flex-direction: column;
137 | }
138 |
139 | p {
140 | margin-bottom: 1rem;
141 | }
142 | }
143 |
144 |
145 |
146 |
147 | @keyframes show {
148 | from {
149 | height: 0;
150 | opacity: 0;
151 | }
152 | to {
153 | height: auto;
154 | opacity: 1;
155 | }
156 | }
157 |
158 | @keyframes hide {
159 | from {
160 | height: auto;
161 | opacity: 1;
162 | }
163 | to {
164 | height: 0;
165 | opacity: 0;
166 | }
167 | }
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ]
26 | },
27 | "angularCompilerOptions": {
28 | "enableI18nLegacyMessageIdFormat": false,
29 | "strictInjectionParameters": true,
30 | "strictInputAccessModifiers": true,
31 | "strictTemplates": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------