Run ng generate component component-name
to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module
.
Run ng build
to build the project. The build artifacts will be stored in the dist/
directory.
export interface MyTable {
name: string;
email?: string; // ? for optional field.
}
const tableData: MyTable[] = [
{name: "john", email: "[email protected]"}
{name: "maria", email: "[email protected]"}
]
button [class.active]="isActive"
<img [src]="imageUrl">
<td [colspan]="colSpan">
<p [textContent]="text">
button [style.color]="isActive ? 'blue': 'white"
button (click)="clickBtn($event)"
// clickBtn(e) => e.preventDefault()....
input (keyup.enter)="onKeyUp($event)"
input #email (keyup.enter)="onKeyUp(email.value)"
// onKeyUp(email) => console.log(email)
input [value]="email" (keyup.enter)="onKeyUp()"
// email = "[email protected]";
// onKeyUp() => console.log(this.email)
input [(ngModel)]="email" (keyup.enter)="onKeyUp()"
email = "[email protected]";
onKeyUp() => console.log(this.email)
imports: [..., FormsModule]
- uppercase
- lowercase
- number // 30,123
- number:'2.2-3' // minDigit.minDec-maxDec => 05.001 or 05.123
- currency:'AUD':true:'3.2-3' // AUD - currency type, true to show symbol, minDigit.minDec-maxDec
- date:'shortdate' // datepipe for more examples
{{ text | custom:10 }}
+custom.pipe.ts
@Pipe({
name: 'custom'
})
export class CustomPipe implements PipeTransform {
transform(value: any, args?: any ) {
let limit = args ? args: 50
return value.substr(0, limit) + '...';
}
}
@NgModule({
declarations: [CustomPipe]
<component [isActive]="isActive"></component>
1. @Input isActive: boolean;
2. @component ({
....,
inputs: [isActive]
})
isActive: boolean;
alias:
<component [isSelected]="isSelected"></component>
@Input(isSelected) isActive: boolean;
<component [isActive]="isActive" (change)="isActiveChange()"></component>
@Output() change = new EventEmitter();
this.change.emit();
alias:
@Output('isSelectedChange')
<ng-template [ngTemplateOutlet]="MsgRef"></ng-template>
<ng-template #MsgRef >
<p>Some content to re-use</p>
</ng-template>
// pass some value to template
<div *ngFor="let item of movieList;">
<ng-template *ngTemplateOutlet="itemPrice; context: {$implicit: item}"></ng-template>
// pass multiple value
<ng-template *ngTemplateOutlet="itemDetail; context: {$implicit: item, index:idx}"></ng-template>
</div>
<ng-template #itemPrice let-item>
<p>{{item.name}}</p>
<p>{{item.year}}</p>
</ng-template>
<ng-template #itemDetail let-item let-idx="index">
<p>{{ idx }} {{item.quantity}}</p>
</ng-template>
<app-component>
<div class="title">Title</div>
</app-component>
app.component.html:
<div class="container">
<div class="head">
<ng-content select=".title"></ng-content>
</div>
</div>
<app-component>
<ng-container class="title">Title</ng-container>
</app-component>
app.component.html:
<div class="container">
<div class="head">
<ng-content select=".title"></ng-content>
</div>
</div>
hidden
<div [hidden]="data.list == 0"></div>
<div *ngIf="data.length > 0; then showItem else noItem"></div>
<ng-template #showItem>List of item</ng-template>
<ng-template #noItem>No Items</ng-template>
<div [ngSwitch]="alphabet">
<div *ngSwitchCase="a">A</div>
....
<div *ngSwitchDefault>Default</div>
</div>
alphabet:string = "a"
<ul *ngFor="let item of datas; index as idx; even as evenItem">
<li [class.even]="evenItem ? isEven : null">{{idx}} - {{item.name}}</li>
</ul>
TrackBy is used for optimization. When a set of Data is loaded in DOM, it will be memorised by angular. If same data is fetched loaded again, say on a click, then it won't rerender.
<ul *ngFor="let item of datas; trackBy: trackItem">
<li>{{item.name}}</li>
</ul>
trackItem(index, item) {
return item ? item.id : undefined;
}
<div
[ngClass]="{
"isActive": isActive,
'isSelected': isSelected
}"
[ngStyle]="{
"backgroundColor": error ? 'red' : 'transparent',
'color': error ? 'red' : 'transparent'
}"
>
some text...
</div>
<input type="text" [inputFormat]="'lowercase'">
+InputFormatDirective:
@Directive({
selector: '[inputFormat]'
})
constructor(private element: ElementRef) {}
@Input('inputFormat') case;
@HostListner('blur') onBlur() {
let value: string = this.el.nativeElement.value;
if(case == "lowercase") {
this.el.nativeElement.value = value.toLowerCase();
} else {
this.el.nativeElement.value = value.toUpperCase();
}
}
input value can normally be a string. ngValue is used to bind a complex value ex:object.
<select>
<option *ngFor="let option of selectOptions" [ngValue]="option">
For building a simple form
<form class="p-3" (ngSubmit)="onSubmit(sampleForm)" #sampleForm="ngForm">
<input
name="email"
ngModel
#email="ngModel"
minlength="8"
....
/>
<div *ngIf="email?.errors?.required">...</div>
<div *ngIf="email?.errors?.minlength">
Please enter atleast {{ email?.errors?.minlength.requiredLength }} characters
</div>
<button [disabled]="sampleForm.form.invalid" >
onSubmit(sampleForm: any) {
if (sampleForm.form.valid) { .. }
let email = sampleForm.value.email
}
for building a complex form with validations and more controls
<form [formGroup]="myForm">
<input
formControlName="email"
....
>
<div
class="error"
*ngIf="myForm.get('email').invalid"
(or)
*ngIf="getEmail.invalid && getEmail.touched"
>
Required
</div>
myForm = new FormGroup({
email: new FormControl("", Validators.required),
password: new FormControl("", [
Validators.required,
Validators.minLength(3),
CustomValidators.noSpace
]),
....
});
get getEmail() {
return this.myForm.get('email');
}
+custom.validator.ts
export class CustomValidators {
static noSpace(control: AbstractControl) : ValidationErrors | null {
if((control.value as string).indexOf(' ') >= 0 ) {
return { noSpace : true };
}
return null;
}
}
<input
formControlName="name"
....
>
<div *ngIf="name.pending">Checking...</div>
<div class="error" *ngIf="name.errors.uniqueName">
Not Unique Name
</div>
myForm = new FormGroup({
name: new FormControl(
"",
Validators.required,
CustomValidators.uniqueName,
),
....
});
+custom.validator.ts
export class CustomValidators {
static uniqueName(control: AbstractControl) : Promise<ValidationErrors | null> {
return new Promise((resolve, reject) => {
if(...Call API to check) {
resolve ({ uniqueName : true });
} else {
reject null;
}
});
}
}
Add a form error after the form submit.
<form [formGroup]="myForm" (ngSubmit)="login()">
<div class="error" *ngIf="myForm.errors.invalidLogin">
Invalid login details...
</div>
myForm = new FormGroup({ ... })
login() {
this.myForm.setErrors({
invalidLogin: true,
})
}
<form [formGroup]="myForm">
<input formControlName="name" ...>
<div formGroupName="personal">
<input formControlName="age" ...>
</div>
myForm = new FormGroup({
name: new FormControl(''),
personal: new FormGroup({
age: new FormControl(''),
})
})
<form [formGroup]="myForm">
<div *ngFor="let batch_items of t.controls; let i = index">
<div [formGroup]="batch_items">
<div class="form-group">
<label>Name</label>
<input formControlName="batch_name" ... />
</div>
<p *ngIf="batch_items.controls.batch_name.errors?.required">
This field is required
</p>
........
</div>
</div>
</form>
constructor(private fb: FormBuilder) {}
this.myForm = this.fb.group({
batch_items: this.fb.array([this.buildBatch()])
});
get f() { return this.myForm.controls; }
get t() { return this.f.batch_items as FormArray; }
onAddBatch() {
this.t.push(this.buildBatch());
}
buildBatch() {
return this.fb.group({
batch_name: ['', Validators.required],
....
})
}
onDeleteBatch(i) {
this.t.removeAt(i);
}
this.myForm.disable(); // disable all fields
this.myForm.controls.email.disable(); // disabled particular field
this.myForm.controls.email.disable({ onlySelf: true }); // disable by preserving value during submission
this.myForm = new FormGroup({
email: new FormControl(
{ value: "", disabled: true },
Validators.min(1)
),
...
});
this.myForm.get("email").valueChanges.subscribe((v) => {
if (v) {
this.itemForm.get("phone").enable();
} else {
this.itemForm.get("phone").disable();
}
});
this.myForm.get("email");
this.myForm.getRawValue(); // also output disabled fields
this.myForm.controls.email.setValue("[email protected]");
this.myForm.patchValue({
email: '[email protected]'
});
this.myForm.reset();
this.myForm.setControl("locations", this.fb.array([])); // reset a form array
Patch -> Send only the key to be updated from the body to the API ex: update only name from {name: '', age: '', dob: ''} Put -> Send full body to the API for update
import { Http, HttpClient, HttpParams } from '@angular/http';
constructor(public http: Http, public httpClient: HttpClient) { }
this.http.get("url").subscribe(res => res.json());
this.http.post("url", JSON.stringify(body)).subscribe(res => res.json());
this.http.patch("url" + id, JSON.stringify( { name: "John" } )).subscribe(res => res.json());
this.http.put("url" + id, JSON.stringify(body)).subscribe(res => res.json());
this.http.delete("url" + id).subscribe(res => res.json());
this.httpClient.get(url, { params: params });
this.httpClient.post(url, data, { params: params });
this.httpClient.request("DELETE", url, {
body: {
id: id,
name: name,
},
});
// form data
let data = new FormData();
data.append("file", file);
let params = new HttpParams();
params = params.append("type", "formdata");
return this.httpClient.post(url, data, { params: params });
// pass params -? api/list?search=text
let params = new HttpParams();
params = params.append("search", 'text');
return this.httpClient.get(url, { params: params });
// multiple params
params = params.append("search", 'text').append("id", id);
+ app.module.ts
imports: [
HttpModule
]
constructor(private api: ApiService) {}
this.api.getData().subscribe(res => ... )
this.api.postData(id, value).subscribe(
res => {
console.log(res.json())
},
error: Response => {
console.log("error ", error)
}
);
@Injectable()
export class ApiService {
private url: "www.baseurl.com/";
constructor(private http: Http) {}
getData() {
return this.http.get(this.url)
.catch((error: Response) => {
return Observable.throw(new AppError(error))
})
}
postData(id, body) {
return this.http.post('this.url' + id, JSON.stringify(body));
}
}
+ app.module.ts
providers: [
ApiService,
....
]
path: 'post/:id'
constructor(private route: ActivatedRoute) {}
this.route.paramMap.subscribe(param => {
param.get('id');
})
this.route.snapshot.params.id;
// query params /post?id=2&type=latest
this.route.queryParamMap.subscribe(param => {
param.get('id');
});
this.route.snapshot.queryParamMap.get('id')
Url: www.example.com/post/2/latest
<a [routerLink]="['/post', post.id, post.type]" >
Url: www.example.com/post/?id=2&type=latest
<a [routerLink]="/post" [queryParams]="{ id: 2, type: 'latest' }" >
Url: www.example.com/post?id=1&order=newest
constructor(private route: ActivatedRoute) {}
this.router.navigate(['post'],{
queryParams: { id: 1, type: 'latest' }
})
+auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
canActivate() {
if (localStorage.getItem('token')) {
return true;
}
this.router.navigate(['']);
return false;
}
}
+app.module.ts
providers: [AuthGuard]
// define route
const routes: Routes = [
{
path: '',
component: ExampleComponent,
canActivate: [AuthGuard],
children: [
{ path: 'blog', component: BlogComponent },
....
],
},
];
+package.json
"dependencies": {
...
},
"engines": {
"node": "6.10.1",
"npm": "5.3.0"
}
ng serve // default dev environment
ng serve --configuration=production // new version 12
ng serve --environment=production // earlier version
+header.ts
import { environment } from 'src/environments/environment';
backgroundColor = environment.color;
+header.component.html
<div class="header" [style.backgroundColor]="backgroundColor" >
+environment.test.ts
export const environment = {
production: false,
color: 'red',
};
+angular.json // copy and replace
"build": {
"configurations": {
...
"test": {
...
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.test.ts"
}
]
}
},
},
"serve": {
"configurations": {
...
"test": {
"browserTarget": "myproject:build:test"
},
}
}
ng serve --configuration=test // new version 12
import { ..., ViewEncapsulation } from "@angular/core";
@Component({
selector: "",
templateUrl: "",
styleUrls: [""],
encapsulation: ViewEncapsulation.None,
})
(or)
::ng-deep .multi-dropdown {
...css
}
Unit Test: Only component test
Integration Test: Component and also template
End to End Test : Full functionality working (ex: Login, add data, check if updated)
// Integration Test:
<p>{{count}}</p>
<button (click)="onBtnClick()">Click</button>
// Unit Test:
count:number = 0;
onBtnClick() {
this.count++;
}
Priciple: Arrange, Act, Assert
File structure:
src/app
>auth-layout
> dashboard
> ....
>common
> shared.module.ts
+auth-layout.routing.module.ts
export const authRoutes: Routes = [
{ path: "dashboard", component: DashboardComponent },
];
@NgModule({
imports: [RouterModule.forChild(authRoutes)],
exports: [RouterModule],
providers: [],
})
export class AuthLayoutRoutingModule {}
+auth-layout.component.html
<div>
<sidebar-app></sidebar-app>
<router-outlet></router-outlet>
</div>
+auth-layout.module.ts
@NgModule({
declarations: [
DashboardComponent,
],
imports: [
SharedModule,
AuthLayoutRoutingModule
]
})
export class AuthLayoutModule { }
+shared.module.ts
@NgModule({
declarations: [],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
...
],
exports: [
...same as import
]
})
export class SharedModule { }
+app.routing.module.ts
const routes: Routes = [
....,
{
path: "",
component: AuthLayoutComponent,
canActivate: [...],
loadChildren: () => import('./auth-layout/auth-layout.module').then(x => x.AuthLayoutModule)
},
{ path: '**', redirectTo: AppConstants.ERROR_URL },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [CanViewDashboard],
})
export class AppRoutingModule {}
+app.module.ts
@NgModule({
declarations: [
AuthLayoutComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
SharedModule,
AppRoutingModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
@NgModule({
imports: [RouterModule.forRoot(routes, {scrollPositionRestoration: 'enabled'})],
Error: More than one module matches. Use skip-import option to skip importing the component into the closest module
ng g c componentname --module app