Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Angular training upgrade angular 17 #413

Merged
merged 30 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
905087e
upgrade angular training to angular 17
fabioemoutinho Jan 2, 2024
fa15611
fix
fabioemoutinho Jan 2, 2024
e05dabe
fix
fabioemoutinho Jan 2, 2024
5bfc4b0
update why angular bundler text
fabioemoutinho Jan 4, 2024
266b15d
typo fix
fabioemoutinho Jan 4, 2024
c520c20
update why-angular and building-first-app
fabioemoutinho Jan 4, 2024
cbc68ae
update adding-routing
fabioemoutinho Jan 4, 2024
d1e466b
update spec file for creating navigation
fabioemoutinho Jan 4, 2024
5870960
update restaurant-service.md
fabioemoutinho Jan 7, 2024
98adf7a
update app.component.spec
fabioemoutinho Jan 7, 2024
aa7f761
update state-city-options
fabioemoutinho Jan 7, 2024
39a9258
update form-value-changes and updating-service-params
fabioemoutinho Jan 7, 2024
cc1f2d6
update declarative-state
fabioemoutinho Jan 7, 2024
c1ece8b
update nested-routes
fabioemoutinho Jan 7, 2024
f9ea1dc
update building-order-form
fabioemoutinho Jan 7, 2024
0cf09f0
update directive
fabioemoutinho Jan 7, 2024
fedc10c
update order-service
fabioemoutinho Jan 8, 2024
8128945
update order-history-component
fabioemoutinho Jan 8, 2024
dcbfd03
update item-total-pipe
fabioemoutinho Jan 8, 2024
b77b418
update real-time-connections and deploy-app
fabioemoutinho Jan 8, 2024
d178b1f
remove comma
fabioemoutinho Jan 9, 2024
7738223
patchValue -> setValue
fabioemoutinho Jan 9, 2024
bd0c294
update version that stopped generating environment files by default
fabioemoutinho Jan 9, 2024
72c77e4
update FormArray patchValue
fabioemoutinho Jan 10, 2024
6fd58ce
fix loading
fabioemoutinho Jan 13, 2024
6b1a497
fix multiple typos
fabioemoutinho Jan 18, 2024
f29edc2
fix typos
fabioemoutinho Jan 18, 2024
5648bf7
Merge branch 'main' into angular-training-upgrade-angular-17
fabioemoutinho Jan 18, 2024
7f813ee
fixes
fabioemoutinho Jan 18, 2024
05c6e0a
fixes
fabioemoutinho Jan 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions src/angular/1-why-angular/why-angular.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,31 @@

## Angular in the Modern Web Development Ecosphere

Angular was first released in 2010 as a new framework in the MV* space alongside libraries like Backbone, Knockout, and Dojo.
Angular was first released in 2010 as a new framework in the MV\* space alongside libraries like Backbone, Knockout, and Dojo.
Some of the best things about Angular are that it is opinionated, has built-in testing, streamlines the Webpack build complexity with its own CLI, and is a Google-backed and supported product.

## The Pros

### 1. An Opinionated Framework

Developers have to make a lot of decisions on a daily basis, which can often create decision fatigue. The great part about using an opinionated framework is being able to shift your focus from build configs, high level architecture decisions, to writing the actual client-specific functioning pieces of the application.
Developers have to make a lot of decisions on a daily basis, which can often create decision fatigue. The great part about using an opinionated framework is being able to shift your focus from build configs, high level architecture decisions, to writing the actual client-specific functioning pieces of the application.

The use of TypeScript to force type checking improves workflow by catching errors at compile time allowing teams to catch potential errors much faster.

### 2. Testing Built In

Spinning up a new Angular Workspace automatically creates a test suite, with a working karma config, and new test spec files for any component generated.
Spinning up a new Angular Workspace automatically creates a test suite, with a working karma config, and new test spec files for any component generated.

### 3. Harnesses the Power of Modern Bundlers

### 3. Harnesses the Power of Webpack
Angular streamlines the build process by masking bundler complexity with the Angular CLI.

Webpack is a module bundler that also handles transforming resources, like Less or Typescript.
Angular streamlines the build process by masking Webpack config complexity with the Angular CLI.
Up until Angular 16, Angular's build process used Webpack. Webpack is a module bundler that also handles transforming resources like Less or Typescript.

Starting on Angular 17, to keep up with latest technology and for its improved build times, Angular switched to esbuild by default.

### 4. Google-Backed and Supported Product

Having a heavy hitting tech titan backing a library can make it a very sustainable choice to use.


For more information see the <a href="https://docs.angularjs.org/guide/introduction" >Angular Docs</a>
For more information see the <a href="https://angular.io" >Angular Docs</a>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { Subject, takeUntil } from 'rxjs';
import { Restaurant } from './restaurant';
import {
Expand All @@ -20,7 +20,10 @@ export interface Data<T> {
styleUrls: ['./restaurant.component.less'],
})
export class RestaurantComponent implements OnInit, OnDestroy {
form: FormGroup = this.createForm();
form: FormGroup<{
state: FormControl<string>;
city: FormControl<string>;
}> = this.createForm();

restaurants: Data<Restaurant> = {
value: [],
Expand Down Expand Up @@ -54,48 +57,49 @@ export class RestaurantComponent implements OnInit, OnDestroy {
this.onDestroy$.complete();
}

createForm(): FormGroup {
return this.fb.group({
createForm(): FormGroup<{
state: FormControl<string>;
city: FormControl<string>;
}> {
return this.fb.nonNullable.group({
state: { value: '', disabled: true },
city: { value: '', disabled: true },
});
}

onChanges(): void {
fabioemoutinho marked this conversation as resolved.
Show resolved Hide resolved
let state: string = this.form.get('state')?.value;
let state: string = this.form.controls.state.value;

this.form
.get('state')
?.valueChanges.pipe(takeUntil(this.onDestroy$))
this.form.controls.state.valueChanges
.pipe(takeUntil(this.onDestroy$))
.subscribe((val) => {
this.restaurants.value = [];
if (val) {
// only enable city if state has value
this.form.get('city')?.enable({
this.form.controls.city.enable({
onlySelf: true,
emitEvent: false,
});

// if state has a value and has changed, clear previous city value
if (state !== val) {
this.form.get('city')?.patchValue('');
this.form.controls.city.setValue('');
fabioemoutinho marked this conversation as resolved.
Show resolved Hide resolved
}

// fetch cities based on state val
this.getCities(val);
} else {
// disable city if no value
this.form.get('city')?.disable({
this.form.controls.city.disable({
onlySelf: true,
emitEvent: false,
});
}
state = val;
});

this.form
.get('city')
?.valueChanges.pipe(takeUntil(this.onDestroy$))
this.form.controls.city.valueChanges
.pipe(takeUntil(this.onDestroy$))
.subscribe((val) => {
if (val) {
this.getRestaurants(state, val);
Expand All @@ -104,13 +108,14 @@ export class RestaurantComponent implements OnInit, OnDestroy {
}

getStates(): void {
this.states.isPending = true;
this.restaurantService
.getStates()
.pipe(takeUntil(this.onDestroy$))
.subscribe((res: ResponseData<State>) => {
this.states.value = res.data;
this.states.isPending = false;
this.form.get('state')?.enable();
this.form.controls.state.enable();
});
}

Expand All @@ -122,14 +127,15 @@ export class RestaurantComponent implements OnInit, OnDestroy {
.subscribe((res: ResponseData<City>) => {
this.cities.value = res.data;
this.cities.isPending = false;
this.form.get('city')?.enable({
this.form.controls.city.enable({
onlySelf: true,
emitEvent: false,
});
});
}

getRestaurants(state: string, city: string): void {
this.restaurants.isPending = true;
this.restaurantService
.getRestaurants(state, city)
.pipe(takeUntil(this.onDestroy$))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Now that we are able to capture a user's state and city preferences, we want to

## Technical Requirements

In the **src/app/restaurant/restaurant.component.ts** file, update the call to the `getRestaurants` service method to use the city and state values capture from the user's form input.
In the **src/app/restaurant/restaurant.component.ts** file, update the call to the `getRestaurants` service method to use the city and state values captured from the user's form input.

## How to Verify Your Solution is Correct

Expand Down
2 changes: 1 addition & 1 deletion src/angular/11-declarative-state/declarative-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ You'll also add new single-responsibility streams:
✏️ Update __src/app/restaurant/restaurant.component.ts__

@sourceref ./restaurant.component.ts
@highlight 3-15, 29-34, 44-51, 59-137, 141-143
@highlight 3-15, 29-34, 47-54, 62-140, 144-146

✏️ Update **src/app/restaurant/restaurant.component.html**

Expand Down
34 changes: 20 additions & 14 deletions src/angular/11-declarative-state/restaurant.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import {
combineLatest,
map,
Expand Down Expand Up @@ -39,7 +39,10 @@ const toData = map(
styleUrls: ['./restaurant.component.less'],
})
export class RestaurantComponent implements OnInit, OnDestroy {
form: FormGroup = this.createForm();
form: FormGroup<{
state: FormControl<string>;
city: FormControl<string>;
}> = this.createForm();

states$: Observable<Data<State>>;
cities$: Observable<Data<City>>;
Expand All @@ -56,13 +59,13 @@ export class RestaurantComponent implements OnInit, OnDestroy {
private restaurantService: RestaurantService,
private fb: FormBuilder
) {
this.selectedState$ = this.form
.get('state')!
.valueChanges.pipe(startWith(''));
this.selectedState$ = this.form.controls.state.valueChanges.pipe(
startWith('')
);

this.selectedCity$ = this.form
.get('city')!
.valueChanges.pipe(startWith(''));
this.selectedCity$ = this.form.controls.city.valueChanges.pipe(
startWith('')
);

this.states$ = this.restaurantService
.getStates()
Expand All @@ -76,7 +79,7 @@ export class RestaurantComponent implements OnInit, OnDestroy {
takeUntil(this.onDestroy$),
tap((states) => {
if (states.value.length > 0) {
this.form.get('state')!.enable();
this.form.controls.state.enable();
}
})
);
Expand All @@ -98,12 +101,12 @@ export class RestaurantComponent implements OnInit, OnDestroy {
takeUntil(this.onDestroy$),
tap((cities) => {
if (cities.value.length === 0) {
this.form.get('city')!.disable({
this.form.controls.city.disable({
onlySelf: true,
emitEvent: false,
});
} else {
this.form.get('city')!.enable({
this.form.controls.city.enable({
onlySelf: true,
emitEvent: false,
});
Expand All @@ -116,7 +119,7 @@ export class RestaurantComponent implements OnInit, OnDestroy {
pairwise(),
tap(([previous, current]) => {
if (current && current !== previous) {
this.form.get('city')!.patchValue('');
this.form.controls.city.setValue('');
}
})
);
Expand Down Expand Up @@ -148,8 +151,11 @@ export class RestaurantComponent implements OnInit, OnDestroy {
this.onDestroy$.complete();
}

createForm(): FormGroup {
return this.fb.group({
createForm(): FormGroup<{
state: FormControl<string>;
city: FormControl<string>;
}> {
return this.fb.nonNullable.group({
state: { value: '', disabled: true },
city: { value: '', disabled: true },
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ describe('AppComponent', () => {
fixture.detectChanges();
tick();
fixture.detectChanges();
let homeLinkLi = fixture.debugElement.query(By.css('li'));
const homeLinkLi = fixture.debugElement.query(By.css('li'));
expect(homeLinkLi.nativeElement.classList).toContain('active');
expect(compiled.querySelectorAll('.active').length).toBe(1);
fixture.destroy();
Expand All @@ -109,7 +109,7 @@ describe('AppComponent', () => {
fixture.detectChanges();
tick();
fixture.detectChanges();
let restaurantsLinkLi = fixture.debugElement.query(By.css('li:nth-child(2)'));
const restaurantsLinkLi = fixture.debugElement.query(By.css('li:nth-child(2)'));
expect(restaurantsLinkLi.nativeElement.classList).toContain('active');
expect(compiled.querySelectorAll('.active').length).toBe(1);
fixture.destroy();
Expand Down
2 changes: 1 addition & 1 deletion src/angular/12-writing-unit-tests/writing-unit-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ In the next section we're going to be creating a restaurant detail view. We'll n

});
```
- Refer to the existing tests on the service to see how to use `HttpMock` to test `HttpClient` calls.
- Refer to the existing tests on the service to see how to use `httpMock` (`HttpTestingController`) to test `HttpClient` calls.

## Solution

Expand Down
3 changes: 3 additions & 0 deletions src/angular/13-nested-routes/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ describe('AppComponent', () => {
});

it('should render the HomeComponent with router navigates to "/" path', fakeAsync(() => {
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
router.navigate(['']).then(() => {
expect(location.path()).toBe('');
Expand All @@ -247,6 +248,7 @@ describe('AppComponent', () => {
}));

it('should render the RestaurantsComponent with router navigates to "/restaurants" path', fakeAsync(() => {
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
router.navigate(['restaurants']).then(() => {
expect(location.path()).toBe('/restaurants');
Expand All @@ -255,6 +257,7 @@ describe('AppComponent', () => {
}));

it('should render the DetailComponent with router navigates to "/restaurants/slug" path', fakeAsync(() => {
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
router.navigate(['restaurants/crab-shack']).then(() => {
expect(location.path()).toBe('/restaurants/crab-shack');
Expand Down
2 changes: 1 addition & 1 deletion src/angular/13-nested-routes/detail.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
import { ImageUrlPipe } from 'src/app/image-url.pipe';
import { ImageUrlPipe } from '../../image-url.pipe';
import { RestaurantService } from '../restaurant.service';
import { DetailComponent } from './detail.component';

Expand Down
4 changes: 2 additions & 2 deletions src/angular/13-nested-routes/nested-routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ To solve this exercise you will need to know:

## Route Parameters & Child Views

A common pattern in SPA architecture is to serve a view of an individual piece of data from a path with an identifying parameter. Previously we've defined static paths with Angulars <a href="https://angular.io/guide/router" >router</a>. To create a nested route, we'll need the slug after the slash to be dynamic. We can set a token for the router parameter with `:`. To get the slug from the route in a component, we use the <a href="https://angular.io/api/router/ActivatedRoute" >ActivatedRoute</a> interface.
A common pattern in SPA architecture is to serve a view of an individual piece of data from a path with an identifying parameter. Previously we've defined static paths with Angular's <a href="https://angular.io/guide/router" >router</a>. To create a nested route, we'll need the slug after the slash to be dynamic. We can set a token for the router parameter with `:`. To get the slug from the route in a component, we use the <a href="https://angular.io/api/router/ActivatedRoute" >ActivatedRoute</a> interface.

@sourceref ./nested-route.html
@codepen
Expand All @@ -54,7 +54,7 @@ ng g component restaurant/detail

When you click the detail button on a restaurant from the restaurant list view you'll see the detail view of that restaurant.

✏️ Update the spec file **src/app/restaurant/detail.component.spec.ts** to be:
✏️ Update the spec file **src/app/restaurant/detail/detail.component.spec.ts** to be:

@sourceref ./detail.component.spec.ts

Expand Down
4 changes: 4 additions & 0 deletions src/angular/14-building-order-form/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ describe('AppComponent', () => {
});

it('should render the HomeComponent with router navigates to "/" path', fakeAsync(() => {
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
router.navigate(['']).then(() => {
expect(location.path()).toBe('');
Expand All @@ -249,6 +250,7 @@ describe('AppComponent', () => {
}));

it('should render the RestaurantsComponent with router navigates to "/restaurants" path', fakeAsync(() => {
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
router.navigate(['restaurants']).then(() => {
expect(location.path()).toBe('/restaurants');
Expand All @@ -257,6 +259,7 @@ describe('AppComponent', () => {
}));

it('should render the DetailComponent with router navigates to "/restaurants/slug" path', fakeAsync(() => {
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
router.navigate(['restaurants/crab-shack']).then(() => {
expect(location.path()).toBe('/restaurants/crab-shack');
Expand All @@ -265,6 +268,7 @@ describe('AppComponent', () => {
}));

it('should render the OrderComponent with router navigates to "/restaurants/slug/order" path', fakeAsync(() => {
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
router.navigate(['restaurants/crab-shack/order']).then(() => {
expect(location.path()).toBe('/restaurants/crab-shack/order');
Expand Down
Loading
Loading