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
Input changes → name changes ⇒ check commit → need
⇒ two-way binding!
[(ngModule)]
← from FormsModule
⇒ two-way binding!
Getting started → The basics → Components & Databinding → Directives → Services & Dependency Injection → Routing → Observables → Forms → Pipes → Http → Authentication → Optimizations & NgModules → Deployment → Animations & Testing
- Superset for JS → define type as you want + check when coding
- TS doesn't run in the browser → compiled to JS before that
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 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
Things I understand more from the course!
- 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)
← fromapp.module.ts
← there isbootstrap: []
(this one is only in app root)
- 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 = 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 = 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}
Containing elements can be used across different features in the project!
- 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!
- From a big app → how could we split this app (vid)? → We wanna exchange info between child components with parent components.
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!
👉 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"
- 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})
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){}
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!
- Project content into component.
- Everything between
<app-server>Text</app-server>
will be lost → angular will not take care about it! → usingng-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
- 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 (afterconstructor
)
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}
@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 = instructions in the DOM
- You could/should create a separated folder containing all your custom 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>
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)
👉 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.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
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}
👉 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}
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.