Component
Component
Component
Component
Component
Component
Component
User info
ArticleList
Article
Today Weather
Article
I am smart 💡
Service
data down
events up
run:
create:
without routing for now
remove everything from app.component.html
export class AppComponent {
myVariable = 'My name is Martin';
...
}
app.component.ts
<p>
This is my variable: {{ myVariable }}
</p>
app.component.html
<button type="button" (click)="switchMood()">Switch mood</button>
class AppComponent {
switchMood() {
this.happy = !this.happy;
}
}
.html
.ts
<div *ngIf="happy">
I am happy
</div>
<div *ngIf="!happy">
I am sad
</div>
UsersModule
OrdersModule
SharedModule
UsersListComponent
UsersDetailComponent
AddressFormComponent
TodayMenuComponent
ShoppingCartComponent
NavbarComponent
BeautifulButtonComponent
UserBadgeComponent
@NgModule({
declarations: [
UsersListComponent,
UserDetailComponent,
AddressFormComponent,
UserBadgeComponent
],
imports: [
FormsModule,
UsersRoutingModule
],
exports: [
UserBadgeComponent
]
})
export class UsersModule { }
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-article',
templateUrl: './article.component.html',
styleUrls: ['./article.component.css']
})
export class ArticleComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
Parent
Component
Component
Child
I am smart 💡
DATA
EVENTS
Service
import {Input} from '@angular/core';
export class ChildComponent {
@Input()
someNumber = 0;
}
<app-child-component [someNumber]="propertyInParent">
</app-child-component>
parent-component.html
child-component.ts
<ul>
<li *ngFor="let name of names">
{{ name }}
</li>
</ul>
array in the class
import {Output, EventEmitter} from '@angular/core';
export class ChildComponent {
@Output()
onClicked = new EventEmitter<void>();
click() {
this.onClicked.emit();
}
}
<app-child-component (onClicked)="methodInParent()">
</app-child-component>
child-component.ts
parent-component.html
import {Output, EventEmitter} from '@angular/core';
export class ChildComponent {
@Output()
onClicked = new EventEmitter<string>();
click() {
this.onClicked.emit('a message');
}
}
<app-child-component (onClicked)="methodInParent($event)">
</app-child-component>
child-component.ts
parent-component.html
root
article
articles []
removeArticle()
@Output()
{{timestamp | date: 'medium'}}
<pre>{{complexObject | json}}</pre>
@Pipe({
name: 'wordsCounter'
})
export class WordsCounterPipe implements PipeTransform {
transform(value: unknown, ...args?: unknown[]): unknown {
return null;
}
}
it('counts words in empty text', () => {
const pipe = new WordsCounterPipe();
const emptyText = '';
const result = pipe.transform(emptyText);
expect(result).toBe(0);
});
export class Component {
stylesObject = {
green: true,
monday: this.isMonday()
}
}
<div [ngClass]="stylesObject"></div>
<!-- turns into: -->
<div class="green monday"></div>
.ts
.html
<button mat-raised-button color="primary">
Switch color
</button>
<form [formGroup]="myForm">
...
</form>
Form Group
Form Control
Form Control
Form Group
Form Control
Form Control
<input type="text" formControlName="firstName">
<input type="text" formControlName="surname">
<fieldset formGroupName="address">
<input type="text" formControlName="street">
<input type="text" formControlName="city">
</fieldset>
import { FormGroup, FormControl } from '@angular/forms';
class Compoennt {
myForm: FormGroup;
constructor() {
this.myForm = new FormGroup({
firstName: new FormControl(''),
surname: new FormControl(''),
address: new FormGroup({
street: new FormControl(''),
city: new FormControl(''),
})
});
}
}
{
firstName: '',
surname: '',
address: {
street: '',
city: ''
}
}
<form [formGroup]="myForm" (ngSubmit)="saveForm()">
...
<button type="submit">save</button>
</form>
component.ts
saveForm() {
console.log(this.myForm.value);
}
import { Validators } from '@angular/forms';
...
this.myForm = new FormGroup({
firstName: new FormControl('', Validators.required),
email: new FormControl('', [Validators.required, Validators.email]),
});
<form [formGroup]="myForm">
<div>Name: <input type="text" formControlName="firstName"></div>
<div>Email: <input type="text" formControlName="email"></div>
<div>Is email field valid? {{myForm.get('email').valid}}</div>
<div *ngIf="myForm.get('email').hasError('required')">
This field is required
</div>
<div *ngIf="myForm.get('email').hasError('email')">
Enter valid email
</div>
<div>Is whole form valid? {{myForm.valid}}</div>
</form>
constructor(fb: FormBuilder) {
this.myForm = fb.group({
firstName: [''],
surName: [''],
address: fb.group({
street: [''],
city: ['Prague', [Validators.required]]
})
});
}
<button [disabled]="true" (click)="save()">I am disabled</button>
import { Injectable } from '@angular/core';
@Injectable({providedIn: 'root'})
export class CalculatorService {
propertyInService = 'hello';
constructor() { }
add(a: number, b: number): number {
return a + b;
}
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(public calculatorService: CalculatorService) {}
someMethod() {
return this.calculatorService.add(3, 5);
}
}
<div>
{{calculatorService.property}}
</div>
.html
describe('ArticlesService', () => {
beforeEach(() => TestBed.configureTestingModule({
providers: [HttpClient]
}));
it('should be created', () => {
const service: ArticlesService = TestBed.inject(ArticlesService);
expect(service).toBeTruthy();
});
});
beforeEach(() => TestBed.configureTestingModule({
providers: [{
provide: HttpClient,
useValue: {
get(url) {
return Promise.reject();
}
}
}]
}));
ngOnChanges(changes: SimpleChanges) {
if (
(changes.a.currentValue &&
changes.a.currentValue !== changes.a.previousValue)
||
(changes.b.currentValue &&
changes.b.currentValue !== changes.b.previousValue)
) {
this.recalculate();
}
}
.html
.ts
export class Component implements AfterViewInit {
@ViewChild('counterButton')
counterButton: ElementRef<HTMLButtonElement>;
ngAfterViewInit() {
this.counterButton.nativeElement.disabled = true;
}
}
<button #counterButton (click)="increment()">
{{ counter }}
</button>
.html (local variable)
.ts (ViewChild)
export class Component implements AfterViewInit {
@ViewChild('counterComponent')
// or @ViewChild(CounterComponent)
counterButton: CounterComponent;
ngAfterViewInit() {
setTimeout(() => {
this.counterButton.counter++
}, 0);
}
}
<app-counter #counterComponent></app-counter>
{{counterComponent.counter}}
<app-button (click)="doSomething()">
<span class="highlight">Click me!</span>
</app-button>
<button type="button">
<ng-content></ng-content>
</button>
parent.html
child.html
HTML:
<h1>{{something}}</h1>
TS:
export class Component {
something = 'hello';
}
it('should display hello', () => {
const title = fixture.nativeElement.querySelector('h1');
fixture.detectChanges();
expect(title.textContent).toContain('hello');
});
HTML:
<h1>{{something}}</h1>
TS:
export class Component {
something = 'hello';
}
import { By } from '@angular/platform-browser';
it('should display hello', () => {
const title = fixture.debugElement.query(By.css('h1'));
fixture.detectChanges();
expect(title.nativeElement.textContent).toContain('hello');
});
// using nativeElement
const button = fixture.nativeElement.querySelector('.increment');
button.click();
// using debugElement
const button = fixture.debugElement.query(By.css('.increment'));
button.triggerEventHandler('click', null);
const articleComponents = fixture.debugElement.queryAll(
By.directive(ArticleComponent)
);
expect(articleComponents.length).toBe(2);
TestBed.configureTestingModule({
declarations: [...],
providers: [
{
provide: ArticleService,
useValue: {
articles: [],
addArticle() {},
removeArticle() {}
}
],
]
}).compileComponents();
const articleService = TestBed.inject(ArticleService);
spyOn(articleService, 'addArticle');
/// do work related to test
expect(articleService.addArticle).toHaveBeenCalled();
// original behavior
spyOn(articleService, 'addArticle').and.callThrough();
// custom return value
spyOn(articleService, 'addArticle').and.returnValue(5);
// replaced by another function
spyOn(articleService, 'addArticle').and.callFake(() => {
return 5;
});
const promise = new Promise((resolve, reject) => {
... long running code
resolve(data);
});
const obs$ = new Observable(observer => {
... long running code
observer.next(data);
observer.next(data);
observer.complete();
});
let obs$ = new Observable(() =>
console.log('work');
);
obs$.subscribe()
obs$.toPromise()
let promise = new Promise(() =>
console.log('work');
);
const promise = new Promise((resolve, reject) => {
... long running code
reject('It went wrong');
});
const obs$ = new Observable(observer => {
... long running code
observer.error('It went wrong');
});
obs$.subscribe(nextCallback);
obs$.subscribe({
next: () => { ... },
error: () => { ... },
complete: () => { ... }
});
interval(1000).subscribe(x => console.log(x));
function interval(time) {
return new Observable(observer => {
let counter = 1;
setInterval(() =>
observer.next(counter++);
}, time);
});
}
const subscription = interval$.subscribe(onTick);
subscription.unsubscribe();
const subscription = interval(1000)
.subscribe(val => console.log(val));
subscription.unsubscribe();
function interval(time) {
return new Observable(observer => {
let counter = 1;
setInterval(() => {
console.log('XXX');
observer.next(counter++);
}, time);
});
}
const subscription = interval(1000)
.subscribe(val => console.log(val));
subscription.unsubscribe();
function interval(time) {
return new Observable(observer => {
let counter = 1;
const interval = setInterval(() => {
console.log('XXX');
observer.next(counter++);
}, time);
return () => clearInterval(interval);
});
}
const interval$ = interval(1000);
let subscription = interval$.subscribe(onTick);
subscription = interval$.subscribe(onTick);
subscription.unsubscribe();
const obs$ = from([1,2,3,4]);
console.log('one');
obs$.subscribe({
next: x => console.log(x),
complete: () => console.log('done')
});
console.log('two');
const derived$ = interval(1000).pipe(
map(x => x * 2)
);
derived$.subscribe(x => console.log(x));
import { from } from 'rxjs';
import { map } from 'rxjs/operators';
from([1,2,3,4,5])
.pipe(
map(val => val * 10)
)
.subscribe(val => console.log(val));
const interval$ = interval(1000);
const derived$ = interval$.pipe(
map(x => x * 2)
);
interval$.subscribe(x => console.log(x));
[1,2,3,4,5].reduce((acc, curr) =>
acc + curr
);
Arrays
from([1,2,3,4,5]).pipe(reduce((acc, curr) =>
acc + curr
));
Observables
from([1,2,3,4,5]).pipe(scan((acc, curr) =>
acc + curr
, 0));
function isPrime(n) {
for (let i = 2; i < n; i++) {
if (n % i === 0) {
return false;
}
}
return n !== 1 && n !== 0;
}
const intervalOne$ = interval(1000);
const intervalTwo$ = interval(500);
merge(intervalOne$, intervalTwo$).subscribe(...)
const intervalOne$ = timer(1000, 1000);
const intervalTwo$ = timer(5000, 500);
combineLatest(intervalOne$, intervalTwo$).subscribe(...)
// 4, 0 <- depends on the first one to emit
// 5, 0
// 5, 1
// 5, 2
// 6, 3
// 6, 4
const intervalOne$ = timer(1000, 1000).pipe(take(3));
const intervalTwo$ = timer(5000, 500).pipe(take(5));
forkJoin(intervalOne$, intervalTwo$).subscribe(...)
// 2, 4
interval(5000).pipe(
switchMap(() => interval(500))
)
const subject = new Subject();
subject.subscribe(x => console.log(x));
subject.next(1);
subject.next(2);
<div>{{ interval$ | async }}</div>
<div *ngIf="subj | async as value; else loading">
{{ value }}
</div>
<ng-template #loading>
waiting for a value...
</ng-template>
@Component({
...
changeDetection: ChangeDetectionStrategy.OnPush
})
class Component {
constructor(private cd: ChangeDetectorRef) { }
detectChanges(): void {
this.cd.detectChanges();
}
}
@Injectable({providedIn: 'root'})
class ArticlesService {
private articles: Articles[] = [];
private changes$ = new ReplaySubject<Articles[]>(1);
constructor() {
this.changes$.next(this.articles);
}
addAdrticle() {
this.articles.push({...});
this.changes$.next(this.articles);
}
getChanges(): Observable<Article[]> {
return this.changes$.asObservable();
}
}
✋
ng new jokes
? Would you like to add Angular routing? (y/N) y
// in the main module... app.module.ts
import { HttpClientModule } from '@angular/common/http';
@NgModule({
...
imports: [
HttpClientModule
],
...
})
// in the code...
import { HttpClient } from '@angular/common/http';
class Component {
constructor(http: HttpClient) {
http.get<ResponseInterface>('/api/items').subscribe(data => {
console.log(data);
});
}
}
url
import { HttpClient } from '@angular/common/http';
@Injectable({providedIn: 'root'})
export class DataSourceService {
constructor(public http: HttpClient) {}
fetch(): Observable<ResponseInterface> {
return this.http.get<ResponseInterface>('/api/items');
}
}
@Component{...}
export class SomeComponent {
data: ResponseInterface;
constructor(dataService: DataSourceService) {
dataService.fetch().subscribe(response => this.data = response);
}
}
Component:
Service:
GET https://api.chucknorris.io/jokes/random
// app-routing.module.ts
const routes: Routes = [
{ path: 'articles', component: ArticleListComponent },
{ path: 'article/:articleId', component: ArticleComponent },
{ path: '',
redirectTo: '/articles',
pathMatch: 'full'
},
{ path: '**', component: PageNotFoundComponent }
];
class Component {
constructor(route: ActivatedRoute) {
route.pathParam.subscribe(params => console.log(params));
}
}
<a [routerLink]="['/users', id]">
in code:
constructor(public router: Router) {
this.router.navigate(['/users', id ]);
}
export class JokeResolverService implements Resolve<Joke> {
resolve(route: ActivatedRouteSnapshot): Observable<Joke> {
const category = route.paramMap.get('category');
return this.service.fetchJokeFromCategory(category);
}
}
this.activatedRoute.data.subscribe(
data => console.log(data.joke)
);
{
path: 'joke/:category',
component: JokeComponent,
resolve: {
joke: JokeResolverService
}
}
app-routing.module.ts
$ ng generate module jokes --route jokes --module app.module
Index
Categories
Lazy jokes module
"compilerOptions": {
"paths": {
"@angular/*": ["./node_modules/@angular/*"]
}
},
import { MyLibModule } from 'my-lib';
@NgModule({
imports: [
...
MyLibModule
]
})
export class AppModule {}