Thi's avatar
HomeAboutNotesBlogTopicsToolsReading
About|My sketches |Cooking |Cafe icon Support Thi
πŸ’Œ [email protected]

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

Anh-Thi Dinh
AngularMOOCmooc-angular
Left aside

Docs

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

Installation

Problems of version?

CLI

My first app

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

Strict mode

Strict mode forces you to write more verbose code in some places (especially when it comes to class properties). β‡’ Disable it!!!!

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.ts β†’ bootstrapModule(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)
  • 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.
  • For selector
  • constructor in class β†’ called whenever component created ← a function of TypeScript!

Databinding

Databinding = communication
Dynamic binding DOM's properties
Event binding
Two-way binding β†’ enable ngModel directive!!!

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

"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

By default, all properties of a components can be only used by this component only (not the outside) β†’ That's why we need @Input()

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.

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)
  • 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.

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!!!).

@ViewChild()

We can access local reference from component.ts by using this decorator!
DON'T manipulate the DOM from the component.ts!!!, like
β‡’ 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
USING MULTIPLE ng-content?

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
Whenever there are changes!!!! (event when clicked / Promise back from loaded data,....) > ngDoCheck

@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)

Directives

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

Attribute vs Structural

Structural directives

We cannot have more than 2 structurual directives on the same element!
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!

Attribute directives

Unlike structural directive, attribute directives don't add or remove elements. They only change the element they were placed on!
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,...)
Don't wanna use Renderer2?
For example, we can let the users choose the color they want instead of 'blue' like above!
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

Use *appUnless

ngSwitch

Project: Dropdown

πŸ‘‰ Check this.

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, ....
πŸ‘‰ Codes for this section.
(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.
β—†Docsβ—†Installationβ—‹Problems of version?β—†CLIβ—†My first appβ—†Course Structureβ—†TypeScriptβ—†Integrate Bootstrapβ—†Strict modeβ—†Basicsβ—‹How Angular loaded and started?β—‹Componentsβ—‹Databindingβ—‹Modelβ—‹"shared" folderβ—†Debuggingβ—†Components & Databinding deep diveβ—‹Binding to custom propertiesβ—‹Binding to custom eventsβ—‹View Encapsulationβ—‹Local References in templateβ—‹@ViewChild()β—‹ng-contentβ—‹Component lifecycleβ—‹@ContentChild()β—†Directivesβ—‹Attribute vs Structuralβ—‹Structural directivesβ—‹Attribute directivesβ—‹Custom attribute directiveβ—‹Custom structure directiveβ—‹ngSwitchβ—‹Project: Dropdownβ—†Dynamic Components
About|My sketches |Cooking |Cafe icon Support Thi
πŸ’Œ [email protected]
1// install nodejs
2
3// install angular CLI
4sudo npm install -g @angular/cli@latest
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
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>
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
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!
1// Disable "strict mode"
2// tsconfig.json
3strict: false
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
1template: `
2	<app-server></app-server>
3	<p>abc</p>
4`
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>
1// the same
2<p>Server with ID...</p>
3<p>{{ 'Server' }} with ID...</p>
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>
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}
1import { FormsModule } from '@angular/forms'
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}
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)
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!
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"
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
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})
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){}
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
1this.serverContentInput.nativeElement.value = 'something';
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>
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
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}
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}
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!
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>
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>
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>
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
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 .... {}
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}
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}
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
1// if using *ngIf
2<div *ngIf="!onlyOdd"></div>
3
1// if using *appUnless
2<div *appUnless="onlyOdd"></div>
3
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}
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}