Note for course Angular 1: Basics & Components & Databinding & Directives

Anh-Thi Dinh

Docs

  1. Angular - Introduction to the Angular Docs
  1. Learn RxJS

Installation

1// install nodejs
2
3// install angular CLI
4sudo npm install -g @angular/cli@latest

Problems of version?

1// npm version 7.5.4 detected.
2// The Angular CLI currently requires npm version 6.
3
4// SOLUTION
5// using nvm
6<https://github.com/nvm-sh/nvm>
7
8// Check the corresponding versions between nodejs - npm
9<https://nodejs.org/en/download/releases/>
1// install a specific version
2nvm install 14
3// check installed versions
4nvm ls
5// use
6nvm use 14 // node version
1// check version
2npm -v
3// if npm version is greater than usual? -> downgrade it
4npm install -g npm@6 // eg. for node 14
5// check where npm installed
6which npm // should return /Users/thi/.nvm/versions/node/v14.15.5/bin/npm

CLI

1// new app
2ng new my-new-app
3
4// serve
5ng serve
6ng serve --port 4300
7
8// create component
9ng generate component <name>
10ng g c <name> # shorthand
11ng g c <name> --skipTests true # without test files
12ng g c <name> --selector <app-name> # with selector
13
14// create directive
15ng generate directive <name>
16ng g d <name>

My first app

1// crate an app / project
2// for a moment, choose "No" for first 2 options + CSS
3ng new my-first-app
4cd my-first-app
5ng serve // port 4200
6ng serve --port 4300 // custom
Input changes → name changes ⇒ check commit → need [(ngModule)] ← from FormsModule
⇒ two-way binding!

Course Structure

Getting started → The basics → Components & Databinding → Directives → Services & Dependency Injection → Routing → Observables → Forms → Pipes → Http → Authentication → Optimizations & NgModules → Deployment → Animations & Testing

TypeScript

  • Superset for JS → define type as you want + check when coding
  • TS doesn't run in the browser → compiled to JS before that

Integrate Bootstrap

1// cd to project
2npm i --save bootstrap
3
4// angular.json
5// -> changes "styles"
6"styles": [
7	"node_modules/bootstrap/dist/css/bootstrap.min.css",
8  "src/styles.css"
9],
10
11// rerun
12ng serve --port 4300
13// Check?
14// Open page > Inspect > Sources > styles.css -> there is bootstrap there!

Strict mode

Strict mode forces you to write more verbose code in some places (especially when it comes to class properties). ⇒ Disable it!!!!
1// Disable "strict mode"
2// tsconfig.json
3strict: false

Basics

Things I understand more from the course!

How Angular loaded and started?

  • All things are in /index.html ← Single Page Application!!!
  • After ng serve, there will be a script at the end of the page will be injected by CLI automatically!
  • First code is executed → main.tsbootstrapModule(AppModule) ← from app.module.ts ← there is bootstrap: [] (this one is only in app root)

Components

  • Key feature in angular!
  • After creating a new component ⇒ Don't forget to add it in module!!!! (if using ng generate component <module-name>, we don't need to do that manually)
    • 1ng generate component <name>
      2ng g c <name> # shorthand
      3ng g c <name> --skipTests true # without test files
      4ng g c <name> --selector <app-name> # with selector
  • Split up your complex app into reusable components.
  • Good practice: having folder name = component name
  • (Convention) Name of component ServerComponent ← normal typescript class with decorator @Component()
    • Make sure unique selector <app-server>
  • Can use inline in selector, template, styles of @Component() → using backtick ``` for multiple lines.
    • 1template: `
      2	<app-server></app-server>
      3	<p>abc</p>
      4`
  • For selector
    • 1// as a selector
      2selector: "app-servers"
      3// then
      4<app-servers></app-servers>
      5
      6// as a class
      7selector: ".app-servers"
      8// then
      9<div class="app-servers"></div>
      10
      11// as an attribute
      12selector: "[app-server]"
      13// then
      14<div app-servers></div>
  • constructor in class → called whenever component created ← a function of TypeScript!

Databinding

Databinding = communication
1// the same
2<p>Server with ID...</p>
3<p>{{ 'Server' }} with ID...</p>
Dynamic binding DOM's properties
1<button
2	class="btn"
3	[disabled]="allowNewServer">Add Server</button>
4// disabled is a DOM property
1// the same
2<p>{{ allowNewServer }}</p>
3<p [innerText]="allowNewServer"></p>
Event binding
1<button
2	(click)="onCreateServer()">Add Server</button>
3// we don't use usual JS onclick event!
4
5<input
6	type="text"
7	class="form-control" // bootstrap's class
8	(input)="onUpdateServerName($event)">
9														//^ event created by (input)
10
11// .component.ts
12onUpdateServerName(event: Event){ // "Event" can by "any" but in this case we know it "Event"
13	this.serverName = (<HTMLInputElement>event.target).value // can use Inspect to check these child element
14									// ^Just tell typescript that our event will be HTMLInputElement
15}
Two-way binding → enable ngModel directive!!!
1import { FormsModule } from '@angular/forms'

Model

  • Model = a type of file, eg. recipe.model.ts
  • Can be used multiple times → write a class → can be extended later!
  • Những type chung chung về cái gì đó thì nên làm thành 1 model
1// recipe.model.ts
2export class Recipe { // <- TYPE!!!
3	public name: string;
4	public description: string;
5	public imagePath:
6
7	constructor(name: string, desc: string, imagePath: string) {
8		this.name = name;
9		this.description = des;
10		this.imagePath = imagePath;
11	}
12}
13
14// use it?
15// .component.ts
16export class RecipeListComponent implements OnInit {
17	recipes: Recipe[] = [ // using our model "Recipe" but array of Recipe(s)
18				// ^ just a type
19		new Recipe('Test', 'A test recipe', 'http//img/url'),
20		// there can be more...
21	];
22}
23
24// then use in .html
25{{ .name }}, {{ .description }} ... // with *ngFor
26
27// below are the same
28<img
29	src="{{ recipe.imagePath }}  // string interpolationing
30	[src]="recipe.imagePath"     // property binding
31>
1// ANOTHER WAY OF DECLARING MODEL
2// Typescript Constructor Shorthand
3export class Recipe { // <- TYPE!!!
4	constructor(public name: string, public description: string, public imagePath: string) {}
5}

"shared" folder

Containing elements can be used across different features in the project!

Debugging

  • Read and understand the error messgaes.
  • Sourcemaps ⇒ Open debugger tool in browser (Chrome) > main.bundle.js > click some checkpoint > jump/open a new file containing the line of code (eg. app.component.ts) ⇒ however, if bundle gets bigger → hard to find
  • Go to webpack// > ./ > src > all your files with the same structure on your project!!!
  • Using Angular Augury (browser extension) > Add another tab in Browser Inspect tool.
    • Don't forget to click on "refresh" button on the top-right bar!

Components & Databinding deep dive

  • From a big app → how could we split this app (vid)? → We wanna exchange info between child components with parent components.

Binding to custom properties

1export class ... {
2	element: {
3		type: string,
4		name: string,
5		content: string
6	}; // :{} -> not value, it's define a type -> make sure element may only have this type
7}
8
9// and if
10element = {...} // this's assign value (like normal JS syntax)
By default, all properties of a components can be only used by this component only (not the outside) → That's why we need @Input()
1export class ... {
2	@Input element: {}
3}
4// then
5<app-server-element
6	[element]="serverElement"></app-server-element>
1// USING ALIAS?
2export class ... {
3	@Input('srvElement') element: {}
4}
5// then
6<app-server-element
7	[srvElement]="serverElement"></app-server-element>
8// ^has to be "srv...", "element" not working now!

Binding to custom events

👉 If the communication between components (by using EventEmitter, input, output,..) are too complicated → check the use of Service! (section "Services with cross-components")
Something changes in the child, we wanna inform parent to know about them.
1// parent .html
2<app-cockpit
3	(serverCreated)="onServerAdded($event)"></app-cockpit>
1// parent .component
2export class ... {
3	serverElements = [...];
4
5	onServerAdded(serverData: {serverName: string, serverContent: string}) {
6		...
7	}
8}
1// child component -> need to EMIT our event
2export class ... {
3	@Output() serverCreated = new EventEmitter<{serverName: string, serverContent: string}>;
4																				//  ^just defind the type of event
5
6	onAddServer() {
7		this.serverCreated.emit(...) // .emit is called with EventEmitter object!
8	}
9}
1new EventEmitter<void>(); // event without any information to emit!
2
3onSelected() {
4	this.recipeSelected.emit(); // without element to emit
5}
1// USING ALIAS?
2// parent .html
3<app-cockpit
4	(srvCreated)="onServerAdded($event)"></app-cockpit>
5// child component
6export class ... {
7	@Output('srvCreated') serverCreated = new EventEmitter<...>;
8}
9// Only alias can be used outside the component!!!
10// Inside the component, 'serverCreated' can be used as usual!
1// Receive an event and then assign directly (no need any method)?
2<app-recipe-list
3	(recipeWasSelected)="selectedRecipe = $event"></app-recipe-list>
4// event comes from "recipeWasSelected" will be assigned to "selectedRecipe"

View Encapsulation

  • Encapsulation = súc tích / ngắn gọn!
  • CSS classed defined in parent can be used only in parent → they are not applied to child components! ← behavior enforce by angular (not by browser)
    • 1// When inspect element, we can see some "strange" attribute
      2// auto added to tag
      3<p _ngcontent-eoj-0>....</p>
      4// make sure this style belongs only to this component
  • Every styles in css file can be applied to the component they belong to!
  • Turn off "Encapsulation"? → so classes in parent can affect child component as usual css behavior, i.e. classes defined are applied globally! ← there is no "strange" attribut like _ngcontent-eoj-0 anymore.
    • 1import { ViewEncapsulation } from '@angular/core';
      2@Component({
      3	encapsulation: ViewEncapsulation.None // turn off capsulation
      4	_______________ViewEncapsulation.ShadowDom // read more on Mozilla
      5  _______________ViewEncapsulation.Emulated // default, only used inside component
      6})

Local References in template

Sometimes, we don't need 2-way binding ([(ngModel)]) → using local reference (eg. #input)
Local references can be use with any type of element (not only input) + anywhere in your template (only the template!!!).
1// with ngModel
2<input [(ngModel)]="newServerName">
3<button (click)="onAddServer()"></button>
4// component.ts
5onAddServer(){}
6
7// with local reference
8<input #serverNameInput>
9<button (click)="onAddServer(serverNameInput)"></button>
10// component.ts
11onAddServer(nameInput: HTMLInputElement){}

@ViewChild()

We can access local reference from component.ts by using this decorator!
1// in .html
2<input #serverContentInput></input>
3
4// in component.ts
5
6// if wanna access selected element inside ngOnInit()
7@ViewChild('serverContentInput', {static: true}) serverContentInput: ElementRef;
8
9// if DON'T wanna ... ngOnInit()
10@ViewChild('serverContentInput', {static: false}) serverContentInput: ElementRef;
11// in Angular 9+, no need to add {static: false} in this case!
12
13// Then we can use property "serverContentInput" in other place in .component.ts
DON'T manipulate the DOM from the component.ts!!!, like
1this.serverContentInput.nativeElement.value = 'something';
⇒ Use string interpolation or property binding if you wanna output something to the DOM!

ng-content

  • Project content into component.
  • Everything between <app-server>Text</app-server> will be lost → angular will not take care about it! → using ng-content
1// child html
2// put where you wanna to display "Text"
3<ng-content></ng-content>
4
5// parent html
6<app-server>Text</app-server>
USING MULTIPLE ng-content?
1// parent html
2<app-child>
3	<div header>Text for header</div>
4	<div body>Text for body</div>
5</app-child>
6
1// child html
2<ng-content select="[header]"></ng-content>
3<ng-content select="[body]"></ng-content>
4
5// => Think of using ng-container
6

Component lifecycle

  • A new component created → Angular go into some phases (processes) ← we can hook into these phases
  • ngOnChanges → called after bound input property changes (remember @Input()) ← listen to the changes
  • ngOnInit → called once component is initialized (after constructor)
  • ngDoCheck → called during every change detection run → useful when you wanna do something on evert change detection cycle
  • "Content" in above life cycle hooks ← comes from <ng-content></ng-content>
  • Rarely need to use all of them!
  • ngAfterViewinit → before that, there is no some view, we cannot do anything with it. Using this hook to make sure that some component is presented in DOM (View)!
  • We have different "ContentInit" and "ViewInit" here because "Content" and "View" are different. Check more on section "ViewChild" and "@ContentChild" to see the different!
Order on starting
1constructor > ngOnChanges > ngOnInit > ngDoCheck
2> ngAfterContentInit > ngAfterContentChecked
3> ngAfterViewInit > ngAfterViewChecked
4// whenever input changes -> ngOnchanges
5// whenever there are changes -> ngDoCheck
1// How to use?
2export class ... implements OnInit, OnChanges, OnDestroy {
3	ngOnInit() {}
4	ngOnChanges() {}
5	ngOnDestroy() {}
6}
1// testing ngOnChanges
2import { OnChanges, SimpleChanges } from '@angular/core';
3export class ... implements OnChanges {
4	@Input() element: ....
5
6	OnChanges(changes: SimpleChanges) {
7		console.log('ngOnChanges called!');
8		console.log(changes);
9	}
10}
11
12// then open Inspect
13// in object element: SimpleChange
14// there are "currentValue", "firstChange", "previousValue",...
1// DoCheck
2// -> wanna do something on evert change detection cycle
3import { DoCheck } from '@angular/core';
4export class ... implements DoCheck {
5	ngDoCheck() {
6
7	}
8}
Whenever there are changes!!!! (event when clicked / Promise back from loaded data,....) > ngDoCheck
1// onDestroy()
2// Called when component is destroyed -> when removed from the DOM
3// Wanna make a test?
4
5// in child component.ts
6ngOnDestroy() {
7	console.log("ngOnDestroy called!");
8}
9
10// in parent html
11<button (click)="onDestroyFirst()>Destroy component</button>
12
13// in parent component.ts
14onDestroyFirst() {
15	this.serverElements.splice(0, 1);
16}

@ContentChild()

@ViewChild() is used to access to "local reference" in the same component.
If you wanna access "local reference" in a content → use @ContentChild() (It's not part of the view, it's a part of the content)
1// parent html
2<app-server-element
3	<p #contentParagraph>Text</p>
4></app-server-element>
1// child html
2<ng-content></ng-content>
1// child component.ts
2export class ...{
3	@ContentChild('contentParagraph') paragraph: ElementRef;
4}
5
6// In case you wanna call "contentParagraph" in parent component.ts
7// => use @ViewChild() in parent component.ts as usual!

Directives

  • Directives = instructions in the DOM
  • You could/should create a separated folder containing all your custom directives.

Attribute vs Structural

Structural directives

1<!-- else -->
2<p *ngIf="serverCreated; else noServer">Text</p>
3<ng-template #noServer>
4	<p>Other text</p>
5<ng-template>
1// toggle show button
2<button (click)="showSecret = !showSecret">Show password</button>
3<p *ngIf="showSecret">Password</p>
We cannot have more than 2 structurual directives on the same element!
1// WRONG
2<li *ngFor="..." *ngIf="..."></li>
1// *ngFor
2<div *ngFor="let logItem of log">{{ logItem }}</div>
3// log has type array, is in .component.ts
1//index of for
2<div *ngFor="let i = index">{{ i }}</div>
Behind the scene of *? ← let angular know it's a structural directive → it will transform them into something else (different from property binding, event binding, 2-way binding, string interpolation) ← without *, it cannot separate!
1// instead of using * like
2<div *ngIf="!onlyOdd"></div>
3
4// we can use (angular will translate *ngIf to something like that)
5<ng-template [ngIf]="!onlyOdd">
6	<div></div>
7</ng-template>

Attribute directives

Unlike structural directive, attribute directives don't add or remove elements. They only change the element they were placed on!
1<p [ngStyle]="{backgroundColor: getColor()}">Text</p>
2 // ^	  		// ^we can use background-color
3 // | "[]" is not a part of directive -> it just tell us that we wanna bind
4 //        some property on this directive!
5
6<p [ngClass="{online: serverStatus == 'online'}">Text</p>
7//            ^.online class
Unlike structural directives, we can use ngStyle and ngClass in the same element (with another structural directive)

Custom attribute directive

👉 Using Renderer2
In this section, we learn a way to access/modify DOM element inside a directive (or component, class,...)
1// CUSTOM DIRECTIVE
2<p appHighlight>Text</p>
3
4// .component.ts
5// or something.directive.ts (and then import it in .component.ts)
6import { Directive, OnInit, ELementRef, Renderer2, HostListener } from '@angular/core';
7												//  ^important: use this to not touch directly on the DOM
8												//   elements ('cause it's bad practice!)
9@Directive({
10	selector: '[appHighlight]'
11})
12export class HighlightDirective implement OnInit {
13	constructor(private elRef: ELementRef, private renderer: Renderer2) { }
14
15	ngOnInit() {
16		this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
17	}
18
19	// or listen to some event
20	@HostListener('mouseenter') mouseover(eventData: Event) {
21							// ^JS event    ^custom name
22		...
23	}
24}
1// then add it in .module.ts
2@NgModule({
3	declarations: [appHighlight]
4})
5export class .... {}
Don't wanna use Renderer2?
1@Directive({
2	selector: '[appHighlight]'
3})
4export class ... {
5	@HostBinding('style.backgroundColor') bgColor: string = 'transparent';
6						//	^like normal JS					^custom var		  ^ gives it initial value
7
8	// then access it likes
9	ngOnInit() {
10		this.bgColor = 'blue';
11	}
12
13}
For example, we can let the users choose the color they want instead of 'blue' like above!
1// if we wanna use input for custom directive?
2<p appHighlight [defaultColor]="'yellow'">Text</p>
3// |            ^ we could use defaultColor="yellow" but be careful, we must
4// |              be sure that there is no conflict with already-have attributes!
5// ^ we could use [appHighlight]="'red'" if there is any alias in the directive
6
7// in .directive.ts
8@Directive({...})
9export ... {
10	@Input() defaultColor: string = 'transparent';
11	@Input('appHighlight') highlightColor: string = 'blue';
12
13	// some commands using above 2 inputs.
14}
How angular knows defaultColor is an input of directive appHighlight or property of <p>? → it checks your custom directives first, then check the native properties after that.

Custom structure directive

1// unless directive
2import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
3
4@Directive({
5	selector: '[appUnless]'
6})
7export class UnlessDirective {
8	@Input() set appUnless(condition: boolean) {
9				// |   ^make sure the same as the selector!
10				// ^a setter of a property which is a method which get executed whenever
11				//  the property changes
12		if (!condition) {
13			this.vcRef.createEmbeddedView(this.templateRef);
14		} else {
15			this.vcRef.clear(); // remove everything from this place in the DOM
16		}
17	}
18
19	constructor(private templateRef: TemplateRef<any>, private vcRef: ViewContainerRef {
20						   //     ^ what to be applied?                  ^ where to be applied?
21														//     ^ look like ElementRef but for ng-template
22	}
23}
24
25// don't forget to add it to declaration in module.ts
Use *appUnless
1// if using *ngIf
2<div *ngIf="!onlyOdd"></div>
3
1// if using *appUnless
2<div *appUnless="onlyOdd"></div>
3

ngSwitch

1<div [ngSwitch]="value">
2	<div *ngSwitchCase="5"></div>
3	<div *ngSwitchCase="10"></div>
4	<div *ngSwitchDefault></div>
5</div>
1// in component.ts
2export class ... {
3	value = 10;
4}

Project: Dropdown

👉 Check this.
1@Directive({
2	selector: '[appDropdown]'
3})
4export class DropdownDirective {
5	@HostBinding('class.open') isOpen = false;
6	@HostListener('click') toggleOpen() {
7		this.isOpen = !this.isOpen;
8	}
9}
1// closing dropdown from anywhere
2@Directive({
3	selector: '[appDropdown]'
4})
5export class DropdownDirective {
6	@HostBinding('class.open') isOpen = false;
7	@HostListener('document:click', ['$event']) toggleOpen(event: Event) {
8		this.isOpen = this.elRef.nativeElement.contains(event.target)
9				? !this.isOpen : false;
10	}
11	constructor(private elRef: ElementRef) {}
12}

Dynamic Components

This part is vert later than above parts. However, it talks about components, I put it here. For example, we wanna show error, modal, ....
(Video) → what are "dynamic components"? → it means that they are not always there but they will be there if there are something happens with your codes.
Loading comments...