Add content from cmds. and GitHub repo notes

This commit is contained in:
James Skemp 2021-09-06 22:29:52 -05:00
parent 234851aa91
commit 79cb0aafbe
10 changed files with 793 additions and 3 deletions

8
.editorconfig Normal file
View File

@ -0,0 +1,8 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
charset = utf-8
insert_final_newline = true

View File

@ -1,4 +1,4 @@
# Angular Notes
# Angular Framework Notes
Notes on developing with the Angular framework.
Powered by mdBook.

View File

@ -4,3 +4,8 @@ language = "en"
multilingual = false
src = "src"
title = "Angular Framework Notes"
[output.html]
git-repository-url = "https://gitlab.com/strivinglife/book-angular"
edit-url-template = "https://gitlab.com/strivinglife/book-angular/-/edit/main/{path}"
git-repository-icon = "fa-gitlab"

View File

@ -1,3 +1,8 @@
# Summary
- [Chapter 1](./chapter_1.md)
- [Basics](./basics.md)
- [Testing](./testing.md)
- [Course notes]()
- [Angular: Getting Started](./courses/angular-getting-started.md)
- [Angular Component Communication](./courses/angular-component-communication.md)
- [Related: Creating Layouts with CSS Grid](./courses/related/creating-layouts-css-grid.md)

76
src/basics.md Normal file
View File

@ -0,0 +1,76 @@
# Basics
Written as of Angular 12+.
## Install the Angular CLI
```bash
npm install -g @angular/cli
```
## Help/available commands
```bash
ng help
ng ___ --help
```
## Create a new site/project
```bash
ng new new-project-name
# This option can be used if there's an issue during npm install.
ng new new-project-name --skip-install
# Use a custom prefix for selector names.
ng new new-project-name --prefix ___
```
## Start a development server
Runs on port 4200 by default.
```bash
ng serve
# Opens the site in the default browser.
ng serve --open
ng serve -o
```
## Code scaffolding
See [ng generate](https://angular.io/cli/generate).
```bash
# Create a new component.
ng generate component heroes
ng generate component hero-detail
ng g c input-button-unit
ng g c products/product-detail --flat
ng generate component messages
# Create a new service.
ng generate service hero
ng generate service message
ng g s services/todo-list
# Create an interface
ng g i interfaces/todo-item
# Create a new module, putting it into AppModule.
ng g m products/product --flat -m app
```
## Build the project
```bash
# Puts in dist/ by default.
ng build
```
## Add an in-memory web API
For mocking up a backend.
```bash
npm install angular-in-memory-web-api --save
```
```bash
# Generate a module in src/app (flat) and register it in AppModule imports (module=app)
ng generate module app-routing --flat --module=app
ng generate component dashboard
# In-memory Web API
npm install angular-in-memory-web-api --save
ng generate service InMemoryData
ng generate component hero-search
```

View File

@ -1 +0,0 @@
# Chapter 1

View File

@ -0,0 +1,489 @@
# Angular Component Communication
My notes from Deborah Kurata's excellent [Angular Component Communication](https://www.pluralsight.com/courses/angular-component-communication).
# Module 3
## Binding and structural directives
### Interpolation
```typescript
// Component
pageTitle: string = 'Product List';
```
```html
<!-- Template -->
<div>{{pageTitle}}</div>
```
Can also use functions (`{{ getPageTitle() }}`) but may run into performance issues due to how Angular checks for changes.
### Property binding
```typescript
// Component
imageWidth: number = 50;
```
```html
<!-- Template -->
<img [style.width.px]="imageWidth" />
```
### Event binding
```typescript
// Component
toggleImage(): void {
this.showImage = !this.showImage;
}
```
```html
<!-- Template -->
<button (click)="toggleImage()">Toggle Image</button>
```
### Two-way binding
```typescript
// Component
listFilter: string;
```
```html
<!-- Template -->
<input type="text" [(ngModel)]="listFilter" />
```
### *ngIf
```typescript
// Component
showImage: boolean = false;
```
```html
<!-- Template -->
<img *ngIf="showImage" [src]="product.imageUrl" />
```
### *ngFor
```typescript
// Component
products: IProduct[];
```
```html
<!-- Template -->
<tr *ngFor="let product of products">
```
### Two-way binding, long way
`[(ngModel)]="listFilter"` is shorthand for `[ngModel]="listFilter" (ngModelChange)="listFilter=$event"`.
So you could do `[ngModel]="listFilter" (ngModelChange)="onFilterChange($event)"`, where `onFilterChange` updates `listFilter`.
### Getters and setters
```typescript
// Component
listFilter: string;
```
```typescript
// Component
private _listFilter: string;
get listFilter(): string {
return this._listFilter;
}
set listFilter(value: string) {
this._listFilter = value;
}
```
# Module 4
In my opinion, using these has more downsides than benefits.
## `ViewChild`
```
// Angular Directive
@ViewChild(NgModel) filterInput: NgModel; // <input type="text" [(ngModel)]="listFilter" />
// Custom Directive or Child Component
@ViewChild(StarComponent) star: StarComponent;
// Template Reference Variable
@ViewChild('divElementVar') divElementRef: ElementRef; // <div #divElementVar>{{pageTitle}}</div>
// Above is available during/after ngAfterViewInit(), which is after constructor() and ngOnInit().
// However, if within a *ngIf you may run into an issue.
// ElementRef has a nativeElement which allows for access to any HTML element properties or methods.
@ViewChild(NgForm) editForm: NgForm; // if using template-driven forms.
```
With `NgModel` we can for example:
```
@ViewChild(NgModel) filterInput: NgModel;
this.filterInput.valueChanges.subscribe(() => this.performFilter(this.listFilter));
```
Otherwise, `NgModel` and `NgForm` are read-only.
## `ViewChildren`
```
@ViewChildren(NgModel) inputs: QueryList<NgModel>;
// Above would support checking for status.
@ViewChildren(StarComponent) stars: QueryList<StarComponent>;
@ViewChildren('divElementVar' divElementRefs: QueryList<ElementRef>;
@ViewChildren('filterElement, nameElement' divElementRefs: QueryList<ElementRef>;
// Tracks changes in the DOM.
this.divElementRefs.changes.subscribe(() => { /* act */ });
```
# Module 5
## Parent to child component communication
- Child: `@Input`, getters/setters, `OnChanges`
- Parent: Template reference variable, `@ViewChild`
- Use a service
## `@Input()`
Child:
```typescript
@Input() propertyName: string;
```
Parent:
```html
<app-child propertyName="binding source"></app-child>
```
Or:
```typescript
parentProperty: string;
```
```html
<app-child [propertyName]="parentProperty"></app-child>
```
## Getter and setter
Child component:
```typescript
private _propertyName: string;
get propertyName(): string {
rerturn this._propertyName;
}
@Input() set propertyName(value: string) {
this._propertyName = value;
}
```
## `OnChanges`
Child component:
```typescript
@Input() propertyName: string;
ngOnChanges(changes: SimpleChanges): void {
// Note: values start at undefined.
if (changes['propertyName']) {
changes['propertyName'].currentValue
}
}
```
## Template reference value
```html
<app-child #childReference [propertyName]="parentProperty"></app-child>
{{ childReference.propertyName }}
{{ childReference.methodName() }}
```
## `@ViewChild`
```html
<app-child #childReference [propertyName]="parentProperty"></app-child>
```
```typescript
@ViewChild('childReference') childComponent: ChildComponent;
```
or
```html
<app-child [propertyName]="parentProperty"></app-child>
```
```typescript
@ViewChild(ChildComponent) childComponent: ChildComponent;
parentPropertyName: string;
ngAfterViewInit(): void {
this.parentVariable = this.childComponent.propertyName;
}
```
## Summary
1. Use a child component when:
- for a specific task
- complex
- reusable
2. `@Input`, getter/setter, and `OnChanges` are easier for parent to child
- Favor getter/setter if you only need to react to changes to specific properties
- Favor `OnChanges` if you want to react to any input property changes, or if you need current/previous values
- The key here is that it's `@Input()` property changes.
3. Template reference variable if you want to use it in the parent's template
4. `ViewChild` if you want to use it in the class
- but it won't receive notification of changes
# Module 6
## Child to parent component communication
- Event notification: `@Output`
- Provide information: Template reference variable, `@ViewChild`
- Service
## `@Output`
Child:
```typescript
@Output() valueChange = new EventEmitter<string>(); // in @angular/core
this.valueChange.emit(value);
```
Parent:
```html
<app-child (valueChange)="onValueChange($event)"></app-child>
```
```typescript
onValueChange(value: string): void {
// ...
}
```
# Module 7 - Services
## Managing state options
From simple to complex:
1. Property bag
2. Basic state management
3. State management with notifications
4. ngrx (inspired by Redux)
## Property bag
Service that just contains properties.
Service:
```typescript
@Injectable()
export class ThingService {
propertyName1: string;
propertyName2: boolean;
}
```
Component:
```typescript
get propertyName1(): string {
return this.thingService.propertyName1;
}
set propertyName1(value: string) {
this.thingService.propertyName1 = value;
}
constructor(private thingService: ThingService) {
}
```
Great for 'stashing away properties for itself or other components.'
## Service scope
Register the service based upon what you want to be able to use it (scope), and how long it is retained for (lifetime).
- Register in the component - `@Component({ providers: [ ThingService ]})` - for that component and children (template or via router).
- Good if you need multiple instances of the service for different component instances.
- Register in a module - `@NgModule({ providers: [ ThingService ] })` - no matter which module it's registered in (unless lazy-loaded) it will be available to all components.
- Lazy-loaded module services are only available to components declared in that module, but is then available for the entire application lifetime.
`ngOnDestroy(): void { }` if you want to see the lifetime of a service.
## Guidelines
Property bag for:
- Retaining view state
- Retaining user selections
- Sharing data or other state
- Communicating state changes
- Okay if any component can read or change the values
- Components are only notified of state changes if they use template binding
# Module 8
## Basic state management
Essentially store data on the service in a private property, and populate it from the server only when needed.
1. Provide state values
2. Maintain and update state
3. Observe state changes
```typescript
@Injectable()
export class ThingService {
private things: IThing[];
getThings(): Observable<IThing[]> {
if (this.things) {
return of(this.things);
}
// get things from the data store and save to this.things
}
getThing(id: number): Observable<IThing> {
if (this.things) {
const foundThing = this.things.find(item => item.id === id);
if (foundThing) {
return of(foundThing);
}
}
// get thing from the server and return it
}
}
```
For create/update/delete either post to the server and update the item in the property list, or pull fresh content from the server, depending upon need.
May want to always pull fresh data when doing an edit.
You may also want to store a pull or expiration date so that you don't have stale data.
## Concurrent components
Could put a public property in a property bag or state management service that could be read/updated.
In the component that reads the property, use a getter to `return this.thingService.currentThing;` if you want it to update every time `currentThing` is changed.
- Define a property in the service
- Bind that property in a template
- Use a getter in the component class
Note that you'll either need to use binding in the template to have Angular pick up changes or have a timer (`import { timer } from 'rxjs/observable/timer';` has one) in `ngOnInit` that polls for changes. Timers are not ideal.
```typescript
ngOnInit() {
timer(0, 1000).subscribe(t => console.log(this.thing));
// unsubscribe on destroy
}
```
# Module 9
## Service notifications
Can add notifications to any service, not just a state management service.
`EventEmitter` only works child to parent, and you don't want to force it for service notifications.
Use `Subject` or a variant like `BehaviorSubject` instead. `Subject` is a type of `Observable`, and an `Observer`. Don't necessarily need, if you can use binding.
Service:
```typescript
@Injectable()
export class ThingService {
private selectedThingSource = new Subject<IThing | null>();
// source = source of knowledge about selected thing
selectedThingChanges$ = this.selectedThingSource.asObservable();
// $ convention = observable (that can be subscribed to)
// could make the source public but then anyone could push to it
// instead the asObservable makes it read-only externally
changeSelectedThing(selectedThing: IThing | null): void {
this.selectedThingSource.next(selectedThing);
}
}
```
Component, updating:
```typescript
// ...
this.thingService.changeSelectedThing(thing);
// ...
```
Component, subscribing:
```typescript
export class ThingDetailComponent implements OnInit, OnDestroy {
thing: IThing | null;
thingSub: Subscription;
constructor(private thingService: ThingService) { }
ngOnInit(): void {
this.thingSub = this.thingService.selectedThingChanges$.subscribe(
selectedThing => this.thing = selectedThing
);
}
ngOnDestroy(): void {
this.thingSub.unsubscribe();
}
}
```
## `BehaviorSubject`
Many variants of subject, but this one:
- requires an initial value
- provides the current value on a new subscription
Service:
```typescript
//private selectedThingSource = new Subject<IThing | null>();
private selectedThingSource = new BehaviorSubject<IThing | null>(null);
```
Works even when components are destroyed. However, will want to make sure the component that updates also subscribes, if it needs to.
## Summary
Don't need to use `Subject` if notifications are not required, or the only notifications are for changes to bound properties.
Subjects can also be used to sync multiple observables (advanced, not covered by this course).
There is a `Subject` variant that can provide all previous messages.
# Module 10
## Route parameters
- Required
- Optional
- Query
### Required parameters
Define:
```typescript
{ path: 'products/:id', component: ProductDetailComponent }
```
Activate:
```html
<a [routerLink]="['/products', product.id]">...</a>
```
```typescript
this.router.navigate(['/products', this.product.id]);
```
Read:
```typescript
this.route.snapshot.paramMap.get('id');
```
### Optional parameters
In the URL, uses `;` as the delimiter. Can be lost during navigation (versus standard query parameters).
Define:
```typescript
{ path: 'products', component: ProductListComponent }
```
Activate:
```html
<a [routerLink]="['/products', { name: cart, code: g }]">...</a>
```
```typescript
this.router.navigate(['/products', { name: 'cart', code: 'g' }]);
```
Read:
```typescript
this.route.snapshot.paramMap.get('name');
```
### Query parameters
In the URL, uses standard `?` and `&`. Can be retained across routes.
Define:
```typescript
{ path: 'products', component: ProductListComponent }
```
Activate:
```html
<a [routerLink]="['/products']" [queryParams]="{ name: cart, code: g }">...</a>
```
```typescript
this.router.navigate(['/products'], { queryParams: { name: 'cart', code: 'g' }});
```
Read:
```typescript
this.route.snapshot.queryParamMap.get('name');
```
## Summary
- Simple
- Bookmarkable and sharable
- Good for small amounts of data

View File

@ -0,0 +1,4 @@
# Angular: Getting Started
My notes from Deborah Kurata's [Angular: Getting Started](https://www.pluralsight.com/courses/angular-2-getting-started-update).
[My repo with pull request with training notes](https://github.com/JamesSkemp/Angular-GettingStarted/pull/1).

View File

@ -0,0 +1,191 @@
# Related: Creating Layouts with CSS Grid
Playing around while following [Matt Henry's course on Pluralsight](https://app.pluralsight.com/library/courses/css-grid-creating-layouts/table-of-contents).
[My repo from the course](https://github.com/JamesSkemp/css-grid).
## Notes
### Basics
- grid-column-start
- grid-column-end
- grid-template-columns
- grid-template-rows
```css
html, body {
height: 100%;
}
body {
/* Changes all direct children into grid items. */
/* By default 1 column 1 row grid. */
display: grid;
/* Number of columns and width. */
grid-template-columns: 15em auto 15em;
/* Number of rows and heights. */
/* min-content is minimum amount to fit content, and no more. */
/* auto only works here because of 100% height on html and body. */
grid-template-rows: min-content auto min-content;
/* Above is the same as the following. rows / columns. */
/*
grid-template: min-content auto min-content / 15em auto 15em;
*/
/* Can also use the more powerful grid, with just what we want. */
/* grid is also recommended, while grid-template is not. */
/*
grid: min-content auto min-content / 15em auto 15em;
*/
}
header, footer {
/* Grid line to start at. */
grid-column-start: 1;
/* Grid line to end at. */
grid-column-end: 4;
}
```
### Sizing
Viewport-responsive:
- Percentage
- fr
- These will be min-content until all other tracks reach their growth limit (for example, `minmax()`).
- auto
- Growth limit is `max-content`.
- This means if a `1fr` is used it won't get any larger than `max-content`.
Content-responsive:
- min-content
- max-content
- fit-content()
- `fr` fractional unit.
- `minmax(max-content, 50%)` define minimum and maximum values for a track.
- `fit-content(30em)` try to fit the content, but don't go any larger than an amount.
- `repeat(3, 20em)` repeats x times a track size of a certain amount.
- `repeat(6, 1fr 2fr)`
- `repeat(auto-fill, 10em)` fills up available space with tracks of set size.
- `repeat(auto-fill, minmax(15em, 1fr))` is an example of combining for a responsive design.
- `auto-fit` tries to fill empty columns, versus `auto-fill`.
/* Default. Fills it row-by-row. */
- `grid-auto-flow: row;` is the default and tries to fill the grid row-by-row.
- `grid-auto-flow: column;` fills column-by-column instead.
- Alternative is to put it in the `grid` definition. For example `grid: auto-flow 10em / 10em 10em`.
- `writing-mode: vertical-rl;` and `writing-mode: horizontal-tb` are examples of ways to get `grid-auto-flow` to start a certain way, but may also make columns look like rows.
- `grid-auto-rows` can impact implicit rows created if the grid definition doesn't account for all grid items.
- Supports more than one size, in which case it will alternate.
- `grid-auto-columns` is the other.
- Depends upon `grid-auto-flow` definition.
#### Determing track size (slide)
1. All tracks start at their base size.
2. Extra space is allocated evenly to tracks which haven't reached their growth limit.
3. Additional remaining space is given to fractional unit (`fr`) tracks.
4. If there are no fractional unit tracks, additional space is given to `auto` tracks.
### Gaps
- `column-gap`, `row-gap`, and `gap` (shorthand) are the recommended properties.
- Makes it consistent with CSS Grid, Flexbox, and Multi-column Layout.
- `grid-` prefix is also acceptable.
- `margin` on grid container for outer margin.
### Positioning
- Can use -1 for end position (or any negative numbers) instead of a positive number. Negative numbers count from the end of the row to the start.
- However, avoid micromanaging.
- On a grid item, can use span x to span a certain number of tracks.
- `grid-column-end: span 2;`
- `grid-column: start / end;`
- `grid-column: 3 / 5;`
- `grid-column: 3 / span 2;`
- `grid-column: span 2;`
- `grid-row`
- `grid-area: 2 / 1 / 5 / 6;` is grid row start, column start, row end, column end.
- May want to just use `grid-column` and `grid-row` for readability.
### Alignment
- Can align a grid on a web page or the content within a grid cell, and there must be extra space to align within.
- Properties:
- `justify-content` default is start.
- Other options are center, end, space-around, space-between, and space-evenly. Controls entire grid.
- `align-content` default is start.
- Other options are center, end, space-around, space-between, and space-evenly. Controls entire grid.
- `justify-items` default is stretch.
- Other options (for this and next three properties) are start, center, and end. Controls content within grid item.
- `align-items` default is stretch. Controls content within grid item.
- `justify-self` default is stretch. Controls an individual item.
- `align-self` default is stretch. Controls an individual item.
- Justify = left to right.
- Align = top to bottom.
### Accessibility
- Can use `dense` in `grid-auto-flow` and it will try to fill in empty gaps (in the case of spanning grid items).
- Can also use `order` to force items before/after items. Default is `order: 0;`.
- However, try to keep the source order = display order.
- Can also overlaps grid items by overlapping row/column placements. Combine with `z-index` as needed.
### Naming
- When defining track sizes, you can name them.
- `grid-template-columns: [left-edge] 1fr 1fr [midpoint] 1fr 1fr [right-edge];`
- Then use like `grid-column: left-edge / right-edge;`.
- Something like `body { display: grid; grid-template-rows: [header-start] 2em 5em [header-end body-start] 10em 10em [body-end]; }` is a bit more conventional.
- Can also use names within `repeat()` which can cause multiple named lines.
- When used for grid items, this will cause them to jump to the closest named line.
- Can put a number afte the name to use that instance.
- Can also use `span x` before to span over multiple, but be careful when repeating.
#### Simple layouts
```
body {
display: grid;
grid-template-areas: "header header header"
"nav main aside"
"footer footer footer";
grid-template-rows: min-content auto min-content;
grid-template-columns: 15em 1fr 1fr;
}
header { grid-area: header; }
nav { grid-area: nav; }
main { grid-area: main; }
aside { grid-area: aside; }
footer { grid-area: footer; }
```
Good for simple layouts.
- Must be rectangular shape.
- Must have same number of cells.
- `...` to define empty areas.
- Lines automatically get names based upon area names.
- Named lines automatically create named areas.
Shorter version of the above:
```
body {
display: grid;
grid: "header header header" min-content "nav main aside" auto "footer footer footer" min-content / 15em 1fr 1fr;
}
```
Or to improve readability:
```
body {
display: grid;
grid: "header header header" min-content
"nav main aside" auto
"footer footer footer" min-content /
15em 1fr 1fr;
}
```
### Responsive design
- Start small.
- Use media queries. :|
### Subgrids
- May not be well supported (as of me typing this, still only Firefox supported).
- You can of course put a grid within a grid.

13
src/testing.md Normal file
View File

@ -0,0 +1,13 @@
# Testing
## Run unit tests
By default uses [Karma](https://karma-runner.github.io/)/[Jasmine](https://jasmine.github.io/).
```bash
ng test
```
## Add eslint
See [angular-eslint on GitHub](https://github.com/angular-eslint/angular-eslint) for current information. As of 9/5/2021:
```bash
ng add @angular-eslint/schematics
```