Set of basic functionalities from Angular in one place
Command line inferface for Angular - set of commands that will help us during development.
1. Setup
| Command | Description |
|---|---|
| npm install -g @angular/cli | Install Angular CLI globally |
2. New application
| Command | Description |
|---|---|
| ng new best-practises --dry-run | just simulate ng new |
| ng new best-practises --skip-install | skip install means don't run npm install |
| ng new best-practises --prefix best | set prefix to best |
| ng new --help | check available command list |
3. Lint - check and make sure that our code if free of code smells/ bad formatting
| Command | Description |
|---|---|
| ng lint my-app --help | check available command list |
| ng lint my-app --format stylish | format code |
| ng lint my-app --fix | fix code smells |
| ng lint my-app | show warnings |
4. Blueprints
| Command | Description |
|---|---|
| ng g c my-component --flat true | don't create new folder for this component |
| --inline-template (-t) | will the template be in .ts file? |
| --inline-style (-s) | will the style be in .ts file? |
| --spec | generate spec? |
| --prefix | assign own prefix |
| ng g d directive-name | create directive |
| ng g s service-name | create service |
| ng g cl models/customer | create customer class in models folder |
| ng g i models/person | create create interface in models folder |
| ng g e models/gender | create create ENUM gender in models folder |
| ng g p init-caps | create create pipe |
5. Building&Serving
| Command | Description |
|---|---|
| ng build | build app to /dist folder |
| ng build --aot | build app without code that we don't need (optimatization) |
| ng build --prod | build for production |
| ng serve -o | serve with opening a browser |
| ng serve --live-reload | reload when changes occur |
| ng serve -ssl | serving using SSL |
6. Add new capabilities
| Command | Description |
|---|---|
| ng add @angular/material | add angular material to project |
| ng g @angular/material:material-nav --name nav | create material navigation component |
Components are the most basic UI building block of an Angular app. An Angular app contains a tree of Angular components.
import { Component } from '@angular/core';
@Component({
// component attributes
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less']
})
export class AppComponent {
title = 'my-dogs-training';
}** Component attributes**
| Attribute | Description |
|---|---|
| changeDetection | The change-detection strategy to use for this component. |
| viewProviders | Defines the set of injectable objects that are visible to its view DOM children |
| moduleId | The module ID of the module that contains the component |
| encapsulation | An encapsulation policy for the template and CSS styles |
| interpolation | Overrides the default encapsulation start and end delimiters ({{ and }} |
| entryComponents | A set of components that should be compiled along with this component. |
| preserveWhitespaces | True to preserve or false to remove potentially superfluous whitespace characters from the compiled template. |
| Life cycle | Description |
|---|---|
| ngOnInit | Called once, after the first ngOnChanges() |
| ngOnChanges | Called before ngOnInit() and whenever one of input properties change. |
| ngOnDestroy | Called just before Angular destroys the directive/component |
| ngDoCheck | Called during every change detection run |
| ngAfterContentChecked | Called after the ngAfterContentInit() and every subsequent ngDoCheck() |
| ngAfterViewChecked | Called after the ngAfterViewInit() and every subsequent ngAfterContentChecked(). |
| ngAfterContentInit | Called once after the first ngDoCheck(). |
| ngAfterViewInit | Called once after the first ngAfterContentChecked(). |
| Syntax | Description |
|---|---|
| {{user.name}} | Interpolation - just generate user name here |
| <img [src] = "user.imageUrl"> | property binding - bind image url for user to src attribute |
| <button (click)="do()" ... /> | Event - assign function to click event |
| <button *ngIf="user.showSth" ... /> | Show button when user.showSth is true |
| *ngFor="let item of items" | Iterate through items list |
| <div [ngClass]="{green: isTrue(), bold: itTrue()}"/> | Angular ngClass attribute |
| <div [ngStyle]="{'color': isTrue() ? '#bbb' : '#ccc'}"/> | Angular ngStyle attribute |
Input() To pass value into child component
Sample child component implementation
export class SampleComponent {
@Input() value: any/string/object/…;
...
}Sample parent component usage
<app-sample-component [value]="myValue"></app-sampe-component>Output() Emiting event to parent component
Sample child component
@Output() myEvent: EventEmitter<MyModel> = new EventEmitter();
onRemoved(item: MyModel) {
this.myEvent.emit(item);
}Sample parent component
<app-my-component
(myEvent)="someFunction()"></app-my-component>onRemoved in child component is calling someFunction in parent component
Content projection is injection inner html into child component
Example:
Parent component template
<component>
<div>
(some html here)
</div>
</component>Child component template
<ng-content></ng-content>(some html here) will be injection into
Two differents htmls
<component>
<div well-title>
(some html here)
</div>
<div well-body>
(some html here)
</div>
</component><ng-content select="title"></ng-content>
<ng-content select="body"></ng-content>In order to have access to child component/directive/element
@ViewChild(NumberComponent)
private numberComponent: NumberComponent;
increase() {
this.numberComponent.increaseByOne(); //method from child component
}
decrease() {
this.numberComponent.decreaseByOne(); //method from child component
}Sample for element: html:
<div #myElement></div>component:
@ViewChild('myElement') myElement: ElementRefInstead of ElementRef can be used specific element like FormControl for forms.
Reference to element in html:
<button (click)="doSth(myElement)"></button>The Angular Router enables navigation from one view to the next as users perform application tasks.
Sample routing ts file
const appRoutes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'hero/:id', component: HeroDetailComponent },
{
path: 'heroes',
component: HeroListComponent,
data: { title: 'Heroes List' }
},
{ path: '',
redirectTo: '/heroes',
pathMatch: 'full'
},
{ path: '**', component: PageNotFoundComponent }
];Then this should be added inside Angular.module imports
RouterModule.forRoot(appRoutes)Usage
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a> routerLinkActive="active" will add active class to element when the link's route becomes active
//Navigate from code
this.router.navigate(['/heroes']);
// with parameters
this.router.navigate(['/heroes', { id: heroId, foo: 'foo' }]);
// Receive parameters without Observable
let id = this.route.snapshot.paramMap.get('id'); Interface that a class can implement to be a guard deciding if a route can be activated. If all guards return true, navigation will continue.
class AlwaysAuthGuard implements CanActivate {
canActivate() {
return true;
}
}and assing it in routing module:
{
path: 'artist/:artistId',
component: ArtistComponent,
canActivate: [AlwaysAuthGuard],
children: [
{path: '', redirectTo: 'tracks'},
{path: 'tracks', component: ArtistTrackListComponent},
{path: 'albums', component: ArtistAlbumListComponent},
]
}Angular apps are modular and Angular has its own modularity system called NgModules. NgModules are containers for a cohesive block of code dedicated to an application domain, a workflow, or a closely related set of capabilities.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent], // components, pipes, directives
imports: [BrowserModule, AppRoutingModule], // other modules
providers: [], // services
bootstrap: [AppComponent] // top component
})
export class AppModule { }Components shouldn't fetch or save data directly and they certainly shouldn't knowingly present fake data. They should focus on presenting data and delegate data access to a service.
Sample service with one function
@Injectable()
export class MyService {
public items: Item[];
constructor() { }
getSth() {
// some implementation
}
}Usage It should be injected before usage
constructor(private dogListService: MyService) and add in module:
providers: [MyService]To handle and consume http requests
- Add import to module
import { HttpClientModule} from "@angular/common/http";- Usage
import {HttpClient} from '@angular/common/http';
...
// GET
public getData(): Observable<MyResponseModel> {
return this.http.get<MyResponseModel>('api/users/2');
}
// POST
public send(val1: any, val2: any): Observable<any> {
const object = new SendModel(val1, val2);
const options = {headers: new HttpHeaders({'Content-type': 'application/json'})};
return this.http.post<string>(environment.apiUrl + 'api/login', object, options);
}
Transform data/value to specific format, for example:
Show date in shortDate format:
{{model.birthsDay | date:'shortDate'}}Pipe implementation
@Pipe({name: 'uselessPipe'})
export class uselessPipe implements PipeTransform {
transform(value: string, before: string, after: string): string {
let newStr = `${before} ${value} ${after}`;
return newStr;
}
}usage
{{ user.name | uselessPipe:"Mr.":"the great" }}An Attribute directive changes the appearance or behavior of a DOM element. For example [ngStyle] is a directive
Custom directive
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) { }
@Input('appHighlight') highlightColor: string;
@Input('otherPar') otherPar: any; //it will be taken from other attribute named [otherPar]
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.highlightColor || 'red');
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}Usage
<p [appHighlight]="color" [otherPar]="someValue">Highlight me!</p>Animations - moving from style state to another style state. Before add BrowserModule and BrowserAnimationsModule to module
Implementation:
animations: [
trigger('openClose', [
state('open', style({
height: '400px',
opacity: 1.5,
})),
state('closed', style({
height: '100px',
opacity: 0.5,
})),
transition('open => closed', [
animate('1s')
]),
transition('closed => open', [
animate('1s')
])
])
]usage
<div [@openClose]="isShowed ? 'open' : 'closed'">Form logic (validation, properties) are kept in template
sample html
<form name="form" (ngSubmit)="f.form.valid && onSubmit()" #f="ngForm" novalidate>
<div class="form-group">
<label for="firstName">First Name</label>
<input type="text" class="form-control" name="firstName" [(ngModel)]="model.firstName" #firstName="ngModel" [ngClass]="{ 'is-invalid': f.submitted && firstName.invalid }" required />
<div *ngIf="f.submitted && firstName.invalid" class="invalid-feedback">
<div *ngIf="firstName.errors.required">First Name is required</div>
</div>
</div>
<div class="form-group">
<button class="btn btn-primary">Register</button>
</div>
</form>sample component
@ViewChild("f") form: any;
firstName: string = "";
langs: string[] = ["English", "French", "German"];
onSubmit() {
if (this.form.valid) {
console.log("Form Submitted!");
this.form.reset();
}
}Form logic (validation, properties) are kept in component
sample html
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label>Email</label>
<input type="text" formControlName="email" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.email.errors }" />
<div *ngIf="submitted && f.email.errors" class="invalid-feedback">
<div *ngIf="f.email.errors.required">Email is required</div>
<div *ngIf="f.email.errors.email">Email must be a valid email address</div>
</div>
</div>
<div class="form-group">
<button [disabled]="loading" class="btn btn-primary">Register</button>
</div>
</form>sample component
registerForm: FormGroup;
submitted = false;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.registerForm = this.formBuilder.group({
firstName: [{{here default value}}, Validators.required],
lastName: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(6)]]
});
}
// convenience getter for easy access to form fields
get f() { return this.registerForm.controls; }
onSubmit() {
this.submitted = true;
// stop here if form is invalid
if (this.registerForm.invalid) {
return;
}
alert('SUCCESS!! :-)')
}Function
validateUrl(control: AbstractControl) {
if (!control.value || control.value.includes('.png') || control.value.includes('.jpg')) {
return null;
}
return { validUrl: true };
}Usage
this.secondFormGroup = this._formBuilder.group({
imageCtrl: ['', [Validators.required, this.validateUrl]]
});Multi-field validation
validateNameShire(group: FormGroup) {
if (group) {
if (group.get('isShireCtrl').value && !group.get('nameCtrl').value.toString().toLowerCase().includes('shire')) {
return { nameShire : true };
}
}
return null;
}Multi-field validation usage*
this.firstFormGroup.setValidators(this.validateNameShire);Error handling
<div *ngIf="firstFormGroup.controls.nameCtrl.errors.maxlength">Name is too long</div>
<div *ngIf="firstFormGroup.errors.nameShire">Shire dogs should have "shire" in name</div>To register our custom validation directive to NG_VALIDATORS service we have to do it like this: (thanks to multi parameter we won't override NG_VALIDATORS but just add CustomValidator to NG_VALIDATORS)
@Directive({
selector: '[CustomValidator]',
providers: [{provide: NG_VALIDATORS, useExisting: CustomValidator, multi:true}]
})Example:
@Directive({
selector: '[customValidation]',
providers: [{provide: NG_VALIDATORS, useExisting: EmailValidationDirective, multi: true}]
})
export class CustomValidation implements Validator {
constructor() { }
validate(control: AbstractControl): ValidationErrors {
return (control.value && control.value.length <= 300) ?
{myValue : true } : null;
}
}For multiple fields:
validate(formGroup: FormGroup): ValidationErrors {
const passwordControl = formGroup.controls["password"];
const emailControl = formGroup.controls["login"];
if (!passwordControl || !emailControl || !passwordControl.value || !emailControl.value) {
return null;
}
if (passwordControl.value.length > emailControl.value.length) {
passwordControl.setErrors({ tooLong: true });
} else {
passwordControl.setErrors(null);
}
return formGroup;
}- Add to module:
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TextAreaComponent),
multi: true
}
]- Implement ControlValueAccessor interface
interface ControlValueAccessor {
writeValue(obj: any): void
registerOnChange(fn: any): void
registerOnTouched(fn: any): void
setDisabledState(isDisabled: boolean)?: void
}| Function | Description |
|---|---|
| registerOnChange | Register a function to tell Angular when the value of the input changes |
| registerOnTouched | Register a function to tell Angular when the value was touched |
| writeValue | tell Angular how to Write a value to the input |
Sample implementation:
@Component({
selector: 'app-text-area',
templateUrl: './text-area.component.html',
styleUrls: ['./text-area.component.less'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TextAreaComponent),
multi: true
}
]
})
export class TextAreaComponent implements ControlValueAccessor, OnInit {
@Input() value: string;
private _onChange = (data: any) => { console.log('changed: ' + data); };
private _onTouched = (data?: any) => {console.log('touched: ' + data); };
ngOnInit(): void {
const self = this;
}
constructor() {}
writeValue(obj: any): void {
this.value = obj;
}
registerOnChange(fn) {
this._onChange = fn;
}
registerOnTouched(fn: any): void {
this._onTouched = fn;
}
}