INF354 EXAM NOTES — PAGE 1: ANGULAR FUNDAMENTALS
ANGULAR 8 BUILDING BLOCKS COMPONENT ANATOMY (MUST KNOW)
Block Purpose Example import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core";
@Component({
selector: "app-my",
Module Organises app into cohesive units AppModule, SharedModule
templateUrl: "./[Link]",
styleUrls: ["./[Link]"]
Component View + logic unit @Component({selector,template}) })
export class MyComponent implements OnInit {
Template HTML view for component <app-root></app-root> @Input() title: string = ""; // Parent→Child
@Output() clicked = new EventEmitter<string>(); // Child→Parent
Directive Extends HTML behaviour *ngIf, *ngFor, [ngClass] items: string[] = [];
constructor(private svc: MyService) {}
Service Shared business logic @Injectable({providedIn:"root"}) ngOnInit(): void { [Link]().subscribe(d => [Link] = d); }
send() { [Link]([Link]); }
}
Pipe Transforms display values {{ val | currency:"ZAR" }}
LIFECYCLE HOOKS (IN ORDER)
Router Navigation between views [Link](routes)
Hook When called
DATA BINDING (4 TYPES — MCQ FAVOURITE) ngOnChanges Input properties change (before init)
Type Syntax Direction Example
ngOnInit Component initialised — USE FOR API CALLS
Interpolation {{ }} TS→HTML {{ title }}
ngDoCheck Every change detection cycle
Property [prop] TS→HTML [src]="imgUrl"
ngAfterViewInit View & child views initialised
Event (event) HTML→TS (click)="fn()"
ngOnDestroy Before component destroyed — unsubscribe here
Two-way [(ngModel)] Both [(ngModel)]="name"
ROUTING
■ [(ngModel)] needs FormsModule imported in module!
// [Link]
const routes: Routes = [
STRUCTURAL DIRECTIVES { path: "", component: HomeComponent },
*ngIf: <div *ngIf="isLoggedIn">...</div> { path: "events/:id", component: DetailComponent },
*ngFor: <li *ngFor="let item of items; let i=index">{{i}} {{[Link]}}</li> { path: "**", redirectTo: "" } // wildcard
*ngSwitch: <div [ngSwitch]="color"> <p *ngSwitchCase="'red'">Red</p> <p ];
*ngSwitchDefault>Other</p></div> // Navigate programmatically:
constructor(private router: Router) {}
ATTRIBUTE DIRECTIVES [Link](["/events", [Link]]);
[ngClass]="{ 'active': isActive, 'error': hasError }" // Read route param:
[ngStyle]="{ 'color': textColor, 'font-size': fontSize }" constructor(private route: ActivatedRoute) {}
[Link]("id");
PIPES // Template links:
Pipe Example Output <a routerLink="/events">Events</a>
<router-outlet></router-outlet> // renders matched component
date {{ d | date:"dd/MM/yyyy" }} 01/06/2026
HTTP CLIENT & SERVICE PATTERN
// 1. Import HttpClientModule in AppModule
currency {{ p | currency:"ZAR" }} R 100.00
// 2. Service:
@Injectable({ providedIn: "root" })
uppercase {{ s | uppercase }} HELLO export class EventService {
private url = "[Link]
number {{ n | number:"1.2-2" }} 1.50 constructor(private http: HttpClient) {}
getAll(): Observable<Event[]> { return [Link]<Event[]>([Link]); }
json {{ obj | json }} { "a": 1 } getById(id:number): Observable<Event> { return
[Link]<Event>(`${[Link]}/${id}`); }
create(e:Event): Observable<Event> { return [Link]<Event>([Link], e); }
async {{ obs$ | async }} unwraps Observable
update(id:number,e:Event) { return [Link](`${[Link]}/${id}`, e); }
delete(id:number) { return [Link](`${[Link]}/${id}`); }
}
ANGULAR CLI COMMANDS // 3. In component — subscribe:
Command Effect [Link]().subscribe({ next: d => [Link]=d, error: e => [Link](e) });
ng new app-name Create new Angular project TYPESCRIPT ESSENTIALS
Concept Syntax
ng serve Run dev server (localhost:4200)
Types string | number | boolean | any | void | null
ng generate component name Create component (g c shorthand)
Interface interface User { id: number; name: string; email?: string; }
ng generate service name Create service (g s shorthand)
Array items: string[] = []; OR items: Array<string> = [];
ng generate module name Create module
Arrow fn const add = (a:number, b:number): number => a + b;
ng build --prod Production build
Optional ? function greet(name?: string) { ... }
ng test Run unit tests (Karma)
Type assert const x = val as string;
Enum enum Role { Admin=1, User=2 }
Generic function identity<T>(arg:T): T { return arg; }
INF354 EXAM NOTES — PAGE 2: .NET WEB API + ENTITY FRAMEWORK
REST PRINCIPLES & HTTP VERBS ENTITY FRAMEWORK CORE SETUP
Verb Action URL Pattern Status // 1. [Link]
public class AppDbContext : DbContext {
public AppDbContext(DbContextOptions<AppDbContext> o) : base(o){}
GET Read all GET /api/events 200 OK
public DbSet<Event> Events {get;set;}
public DbSet<User> Users {get;set;}
GET Read one GET /api/events/1 200 / 404 }
// 2. [Link] registrations
POST Create POST /api/events 201 Created [Link]<AppDbContext>(opt =>
[Link]("Data Source=[Link]")); // or UseSqlServer(connStr)
PUT Update all fields PUT /api/events/1 200 / 204 [Link]();
[Link]();
PATCH Update partial PATCH /api/events/1 200 / 204 [Link]();
// JWT auth — see page 3
DELETE Delete DELETE /api/events/1 200 / 204 var app = [Link]();
[Link](); [Link]();
[Link](); // MUST be before UseAuthorization
Code Meaning [Link]();
[Link]();
200 OK [Link]();
// 3. Migrations (CLI)
201 Created dotnet ef migrations add InitialCreate
dotnet ef database update
204 No Content dotnet ef migrations remove // undo last migration
400 Bad Request
[Link] FULL STRUCTURE (ORDER MATTERS)
var builder = [Link](args);
// === REGISTER SERVICES ===
401 Unauthorized
[Link]<>(...);
[Link]([Link])
403 Forbidden .AddJwtBearer(opt => { [Link] = new() {
ValidateIssuer=true, ValidateAudience=true, ValidateLifetime=true,
404 Not Found ValidateIssuerSigningKey=true,
ValidIssuer=[Link]["Jwt:Issuer"],
409 Conflict ValidAudience=[Link]["Jwt:Audience"],
IssuerSigningKey=new SymmetricSecurityKey(
500 Server Error [Link]([Link]["Jwt:Key"]!))};});
[Link](o => [Link]("AllowAll", b =>
[Link]().AllowAnyMethod().AllowAnyHeader()));
FULL CRUD CONTROLLER (C#) var app = [Link]();
// === MIDDLEWARE PIPELINE ===
[ApiController]
[Link]("AllowAll"); // BEFORE auth
[Route("api/[controller]")]
[Link](); // BEFORE authorization
public class EventsController : ControllerBase {
[Link]();
private readonly AppDbContext _ctx;
[Link]();
public EventsController(AppDbContext ctx) { _ctx = ctx; }
[Link]();
[HttpGet]
public async Task<ActionResult<IEnumerable<EventDto>>> GetAll() UNIT TESTING (.NET — TEST PROJECT)
=> Ok(await _ctx.[Link](e => new EventDto{...}).ToListAsync());
// xUnit test class
[HttpGet("{id}")]
public class EventsControllerTests {
public async Task<ActionResult<EventDto>> Get(int id) {
private AppDbContext GetContext() {
var e = await _ctx.[Link](id);
var opts = new DbContextOptionsBuilder<AppDbContext>()
return e == null ? NotFound() : Ok(new EventDto{...}); }
.UseInMemoryDatabase([Link]().ToString()).Options;
[HttpPost] return new AppDbContext(opts); }
public async Task<ActionResult<EventDto>> Create(CreateEventDto dto) {
[Fact]
var e = new Event { Name=[Link], Date=[Link] };
public async Task GetAll_ReturnsOk() {
_ctx.[Link](e); await _ctx.SaveChangesAsync();
using var ctx = GetContext();
return CreatedAtAction(nameof(Get), new{id=[Link]}, new EventDto{...}); }
[Link](new Event{Name="Test"});
[HttpPut("{id}")] await [Link]();
public async Task<IActionResult> Update(int id, UpdateEventDto dto) { var ctrl = new EventsController(ctx);
var e = await _ctx.[Link](id); var result = await [Link]();
if (e==null) return NotFound(); var ok = [Link]<OkObjectResult>([Link]);
[Link]=[Link]; [Link]=[Link]; [Link]([Link]); }
await _ctx.SaveChangesAsync(); return NoContent(); } }
[HttpDelete("{id}")]
Test attributes: [Fact] (no params) | [Theory] + [InlineData(val)] (params)
public async Task<IActionResult> Delete(int id) {
Assert methods: .Equal() .NotNull() .IsType<T>() .True() .Throws<T>()
var e = await _ctx.[Link](id);
if (e==null) return NotFound();
_ctx.[Link](e); await _ctx.SaveChangesAsync();
return NoContent(); }
}
ENTITY vs DTO — KEY EXAM CONCEPT
Entity = DB table representation. DTO = data shape for API input/output.
Entity (DB Model) DTO (API shape)
public class Event { public class EventDto {
public int Id {get;set;} public int Id {get;set;}
public string Name {get;set;}=[Link]; public string Name {get;set;}=[Link];
public DateTime Date {get;set;} public string Date {get;set;}=[Link]; //formatted
public User User {get;set;}=null!; //nav prop // NO nav properties in DTO
} }
■ Never expose Entities directly — circular refs, over-posting attacks!
INF354 EXAM NOTES — PAGE 3: JWT SECURITY + IONIC
SECURITY CONCEPTS ANGULAR AUTH INTERCEPTOR
Concept Definition // [Link]
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
CIA Triad Confidentiality, Integrity, Availability
constructor(private auth: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
Authentication WHO are you? (login, verify identity) const token = [Link]();
if (token) {
Authorisation WHAT can you do? (permissions/roles) const cloned = [Link]({
setHeaders: { Authorization: `Bearer ${token}` }
Accounting/Audit WHAT did you do? (logging) });
return [Link](cloned);
Hashing One-way transform; BCrypt for passwords }
return [Link](req);
}
Encryption Two-way; symmetric (AES) or asymmetric (RSA)
}
// Register in AppModule providers:
JWT JSON Web Token — stateless auth token { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
[Authorize] vs [AllowAnonymous] on controller/action
HTTPS Encrypts transport layer (TLS)
[Authorize(Roles="Admin")] for role-based auth
JWT STRUCTURE (3 PARTS, BASE64 ENCODED) IONIC FRAMEWORK
[Link] → eyJ... eyJ... abc123... Concept Detail
Part Contents
Type Hybrid mobile — Angular + Capacitor/Cordova
Header {"alg":"HS256","typ":"JWT"}
Renders WebView on iOS/Android, browser on web
Payload {"sub":"userId","email":"x@[Link]","role":"Admin","exp":1234567890}
vs React Native Ionic uses WebView; RN uses native components
Signature HMACSHA256(base64Header + "." + base64Payload, secretKey)
File ext .[Link], .[Link], .[Link], .[Link]
■ Payload is NOT encrypted — only signed! Never put passwords in JWT.
AUTH FLOW (6 STEPS) IONIC CLI COMMANDS
Command Effect
Step Action
ionic start myApp tabs Create app with tab template
1. Register Hash password (BCrypt), save User to DB
ionic serve Run in browser (localhost:8100)
2. Login Find user, verify [Link](plain, hash)
ionic generate page name Create new page
3. Issue token Create JWT with claims, sign with secret key
ionic generate component name Create component
4. Store token Client stores in memory / localStorage
ionic build Build for production
5. Send token Add to every request: Authorization: Bearer {token}
ionic cap add android Add Android platform
6. Validate API middleware validates signature + expiry
ionic cap run android Run on Android device/emulator
AUTH CONTROLLER (C#)
[ApiController][Route("api/[controller]")]
public class AuthController : ControllerBase {
KEY IONIC COMPONENTS
private readonly AppDbContext _ctx; Component HTML Tag Notes
private readonly IConfiguration _cfg;
// Register: Header <ion-header> Top of page
[HttpPost("register")]
public async Task<IActionResult> Register(RegisterDto dto) { Toolbar <ion-toolbar> Inside header/footer
if (await _ctx.[Link](u => [Link]==[Link]))
return Conflict("Email already exists");
Content <ion-content> Scrollable main area
var user = new User { Email=[Link],
PasswordHash=[Link]([Link]) };
_ctx.[Link](user); await _ctx.SaveChangesAsync(); List <ion-list> / <ion-item> Scrollable list
return Ok("Registered"); }
// Login: Card <ion-card> Card with header/content
[HttpPost("login")]
public async Task<IActionResult> Login(LoginDto dto) { Button <ion-button> fill="solid|outline|clear"
var user = await _ctx.[Link](u=>[Link]==[Link]);
if(user==null || ) Input <ion-input> With ion-label
return Unauthorized();
var token = GenerateToken(user);
Toggle <ion-toggle> Boolean switch
return Ok(new { token }); }
private string GenerateToken(User user) {
Fab <ion-fab> Floating action button
var key = new SymmetricSecurityKey([Link](_cfg["Jwt:Key"]!));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var claims = new[] { new Claim("sub",[Link]()), Modal [Link]() Overlay page
new Claim("email",[Link]), new Claim("role",[Link]) };
var token = new JwtSecurityToken(issuer:_cfg["Jwt:Issuer"], Toast [Link]() Brief notification
audience:_cfg["Jwt:Audience"], claims:claims,
expires:[Link](1), signingCredentials:creds); Alert [Link]() Confirmation dialog
return new JwtSecurityTokenHandler().WriteToken(token); }
}
Loading [Link]() Spinner overlay
IONIC NAVIGATION & ROUTING
// Tabs ([Link]):
{ path: 'tabs', component: TabsPage, children: [
{ path: 'watchlist', component: WatchlistPage },
{ path: 'watched', component: WatchedPage },
{ path: '', redirectTo: 'tabs/watchlist', pathMatch: 'full' }
]},
// NavController (forward/back stack):
[Link]("/tabs/details/"+id);
[Link]("/tabs/watchlist");
// Tab bar HTML:
<ion-tab-bar slot="bottom">
<ion-tab-button tab="watchlist">
<ion-icon name="bookmark"></ion-icon>
<ion-label>Watchlist</ion-label>
</ion-tab-button>
</ion-tab-bar>
INF354 EXAM NOTES — PAGE 4: IONIC API INTEGRATION + ANGULAR MATERIAL
IONIC API SERVICE PATTERN (HW03) [Link] IN ANGULAR (REPORTING)
// [Link] // 1. Install: npm install [Link]
@Injectable({ providedIn: "root" }) // 2. Template:
export class ApiService { <canvas id="myChart"></canvas>
private baseUrl = "[Link] // Android emulator // 3. Component:
// Use localhost:5000 for web, [Link] for Android emulator import { Chart } from "[Link]/auto";
constructor(private http: HttpClient, private auth: AuthService) {} export class ReportComponent implements AfterViewInit {
getWatchlist(): Observable<Movie[]> { chart: any;
return [Link]<Movie[]>(`${[Link]}/watchlist`); ngAfterViewInit() { // MUST use AfterViewInit — DOM must exist
} [Link] = new Chart("myChart", {
addToWatchlist(movie: Movie): Observable<any> { type: "bar", // bar | line | pie | doughnut | radar | polarArea
return [Link](`${[Link]}/watchlist`, movie); data: {
} labels: ["Jan","Feb","Mar","Apr"],
} datasets: [{
// In page component — with loading + error handling: label: "Sales 2026",
async loadMovies() { data: [120, 150, 180, 200],
const loading = await [Link]({ message: "Loading..." }); backgroundColor: ["#1a237e","#3949ab","#7986cb","#c5cae9"],
await [Link](); borderColor: "#0d47a1",
[Link]().subscribe({ borderWidth: 1
next: data => { [Link] = data; [Link](); }, }]
error: err => { [Link](); [Link](err); } },
}); options: { responsive: true, scales: { y: { beginAtZero: true } } }
} });
}
HW02 vs HW03 — KEY DIFFERENCES // Update chart data:
updateChart(newData: number[]) {
Feature HW02 (Local) HW03 (Full Stack) [Link][0].data = newData;
[Link]();
Data storage Ionic Storage (local) SQLite via .NET API }
}
Auth None JWT tokens
ANGULAR MATERIAL TABLE (MAT-TABLE)
HTTP No backend HttpClient + interceptor // 1. Install & import MatTableModule, MatSortModule, MatPaginatorModule
// 2. Component:
dataSource = new MatTableDataSource<Event>();
Movies TMDB API direct Via backend proxy
displayedColumns = ["id","name","date","actions"];
@ViewChild(MatSort) sort!: MatSort;
Offline Yes No (needs API) @ViewChild(MatPaginator) paginator!: MatPaginator;
ngAfterViewInit() {
[Link] = [Link];
IONIC STORAGE (HW02 LOCAL STORAGE) [Link] = [Link]; }
import { Storage } from "@ionic/storage-angular"; // Filter: [Link] = [Link]().toLowerCase();
// [Link]: [Link]({}) in imports // 3. Template:
constructor(private storage: Storage) {} <mat-form-field><input (keyup)="applyFilter($event)"
async ngOnInit() { await [Link](); } placeholder="Search"></mat-form-field>
await [Link]("key", value); <table mat-table [dataSource]="dataSource" matSort>
const val = await [Link]("key"); <ng-container matColumnDef="name">
await [Link]("key"); <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
await [Link](); <td mat-cell *matCellDef="let row">{{[Link]}}</td>
</ng-container>
CORS (CROSS-ORIGIN RESOURCE SHARING) <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
Problem: Browser blocks requests to different origin (domain/port) <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
Solution: API must send Access-Control-Allow-Origin header </table>
// [Link] — add BEFORE auth middleware: <mat-paginator [pageSizeOptions]="[5,10,25]"></mat-paginator>
[Link](opt => [Link]("AllowAll", b =>
[Link]().AllowAnyMethod().AllowAnyHeader())); DECORATORS QUICK REFERENCE
[Link]("AllowAll"); Decorator Where used Purpose
Production: restrict to specific origin:
[Link]("[Link]
@NgModule Module class Declares module with imports/exports/declarations
WEB ARCHITECTURE CONCEPTS
@Component Class Marks as component, sets selector/template
Concept Detail
@Injectable Service class Marks for DI; providedIn:"root" = singleton
SPA Single Page App — Angular loads once, updates DOM dynamically
@Input Property Receive data from parent component
MVC Model-View-Controller pattern (backend)
@Output EventEmitter Emit events to parent component
Repository Abstracts data access — interface + concrete class
@ViewChild Property Get ref to child component/DOM element
Dependency Injection Framework provides dependencies — no new keyword
@Pipe Class Create custom pipe with transform()
Middleware Pipeline of functions processing HTTP req/res
[ApiController] C# class Enables auto model validation, routing
ORM Object-Relational Mapper — EF Core maps C# classes to DB tables
[HttpGet/Post/Put/Delete] C# method Maps HTTP verb to action method
Lazy loading Load feature modules only when navigated to (Angular)
[Authorize] C# class/method Requires JWT auth; add role with (Roles="Admin")
[FromBody] C# param Reads from request body (auto with [ApiController])
[FromRoute] C# param Reads from URL segment {id}
INF354 EXAM NOTES — PAGE 5: ANGULAR ADVANCED + MCQ TRAPS + TEST FIXES
MCQ TRAPS — COMMON EXAM MISTAKES REACTIVE FORMS (ALTERNATIVE TO TEMPLATE FORMS)
Question / Trap Correct Answer // In component — import ReactiveFormsModule
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
form: FormGroup;
Purpose of [(ngModel)] Two-way binding — needs FormsModule
constructor(private fb: FormBuilder) {
[Link] = [Link]({
*ngIf vs [hidden] *ngIf removes from DOM; [hidden] just hides (CSS) name: ["", [[Link], [Link](3)]],
email: ["", [[Link], [Link]]],
ngOnInit vs constructor Constructor = DI only; ngOnInit = logic/API calls age: [null, [[Link](0), [Link](120)]]
});
Observable vs Promise Observable = multiple values, lazy, cancellable }
submit() {
HttpClient returns Observable — must subscribe() to execute if ([Link]) {
[Link]([Link]); // { name, email, age }
}
JWT payload encryption NOT encrypted — only base64 encoded + signed
}
// Template:
201 vs 200 for POST POST → 201 Created; GET/PUT → 200 OK <form [formGroup]="form" (ngSubmit)="submit()">
<input formControlName="name">
[Authorize] missing Returns 401 Unauthorized (not 403 Forbidden) <div *ngIf="[Link]('name')?.errors?.required">Required</div>
<button type="submit" [disabled]="[Link]">Submit</button>
CORS error location Server-side fix; not client-side </form>
[Link] params (plainText, hashedPassword) — ORDER MATTERS OBSERVABLES & RXJS ESSENTIALS
Operator Purpose Example
selector in @Component Matches HTML tag; must be kebab-case (app-name)
map Transform each value obs$.pipe(map(x => [Link]))
[Link] vs forChild forRoot = app root; forChild = feature modules
filter Filter values obs$.pipe(filter(x => [Link] > 0))
providedIn:"root" Singleton — one instance for whole app
catchError Handle errors obs$.pipe(catchError(e => of([])))
mat-table dataSource type MatTableDataSource<T> not T[]
tap Side effects, no transform obs$.pipe(tap(x => [Link](x)))
AfterViewInit for [Link] Chart must init AFTER view renders — not OnInit
switchMap Cancel prev, use new search$.pipe(switchMap(q => [Link](q)))
async pipe Subscribes AND unsubscribes automatically
forkJoin Combine multiple obs (all complete) forkJoin([obs1$, obs2$]).subscribe(([a,b])=>...)
ion-content Must wrap scrollable content in Ionic pages
of Create obs from value of([1,2,3])
HttpInterceptor Intercepts ALL http requests — ideal for auth headers
from Create obs from promise/array from(promise)
EF .FindAsync(id) Returns null if not found (returns null not throw)
ANGULAR GUARDS (ROUTE PROTECTION)
ANGULAR MODULE IMPORTS — WHAT GOES WHERE // [Link]
Need Import in Module @Injectable({ providedIn: "root" })
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) {}
[(ngModel)] two-way binding FormsModule canActivate(): boolean {
if ([Link]()) return true;
HTTP requests HttpClientModule [Link](["/login"]); return false;
}
Routing RouterModule (forRoot/forChild) }
// In routes:
Reactive forms ReactiveFormsModule { path:"dashboard", component:DashboardComponent, canActivate:[AuthGuard] }
Material table MatTableModule
CUSTOM PIPE
@Pipe({ name: "truncate" })
export class TruncatePipe implements PipeTransform {
Material sort MatSortModule
transform(value: string, limit: number = 50): string {
return [Link] > limit ? [Link](0, limit) + "..." : value;
Material paginator MatPaginatorModule }
}
Material form field MatFormFieldModule, MatInputModule Usage: {{ description | truncate:100 }}
JWT interceptor Provide in providers: array [Link] — JWT CONFIG
{
"Jwt": {
"Key": "YourSuperSecretKeyMustBe32CharsOrMore!!",
"Issuer": "[Link]
"Audience": "[Link]
},
"ConnectionStrings": {
"DefaultConnection": "Data Source=[Link]"
}
}
Read in C#: _cfg["Jwt:Key"] or _cfg.GetConnectionString("DefaultConnection")
INF354 EXAM NOTES — PAGE 6: FULL-STACK FLOW + QUICK REFERENCE
COMPLETE FULL-STACK FLOW (HW03) COMPLETE CONCEPT SUMMARY TABLE
Layer Technology Responsibility # Concept Key point
Mobile UI Ionic/Angular Pages, components, forms, navigation 1 Angular SPA One HTML page; components swap in router-outlet
HTTP Interceptor Angular Auto-attach JWT to all requests 2 Component @Component + class + template + style — 4 files
API Service Angular/Ionic HTTP calls, return Observables 3 Data binding { } prop [] event () two-way [()]
Transport HTTPS + CORS Secure, cross-origin comms 4 *ngFor let item of items; track with trackBy for perf
.NET Middleware [Link] Auth, CORS, routing pipeline 5 Service @Injectable singleton; injected via constructor
Controller [Link] Web API Handle HTTP, validate, respond 6 Observable Lazy, cancellable, multi-value stream; .subscribe()
Service/Repo C# classes Business logic, data access 7 HttpClient Returns Observable — import HttpClientModule
EF Core ORM LINQ to SQL queries 8 Router [Link]; routerLink; navigate()
Database SQLite/SQL Server Persistent data store 9 Guard CanActivate — protect routes from unauthorised access
10 Interceptor Modifies all HTTP requests — add auth header
DEPENDENCY INJECTION — .NET
Lifetime Method When to use 11 Pipe {{ value | pipeName:args }} — transform display
Transient AddTransient<>() New instance each time requested 12 Lifecycle ngOnChanges→OnInit→DoCheck→AfterViewInit→OnDestroy
Scoped AddScoped<>() One per HTTP request (default for DbContext) 13 REST Stateless; HTTP verbs; JSON; status codes
Singleton AddSingleton<>() One for app lifetime (config, caching) 14 EF Core DbContext + DbSet; LINQ queries; migrations
15 DTO Separate API shape from DB entity; prevent over-posting
LINQ QUERIES (ENTITY FRAMEWORK)
// Basic queries: 16 JWT 3 parts; signed not encrypted; stateless auth
await _ctx.[Link]();
await _ctx.[Link](id);
17 BCrypt One-way hash; HashPassword() + Verify() pair
await _ctx.[Link](e => [Link] == name);
await _ctx.[Link](e => [Link] > [Link]).ToListAsync();
await _ctx.[Link](e => [Link]).ToListAsync(); 18 CORS Server must allow cross-origin; UseCors middleware
await _ctx.[Link](e => [Link]).ToListAsync(); // eager load
await _ctx.[Link](e => [Link] == name); // bool check 19 Ionic Angular + Capacitor; WebView; ion- prefix components
await _ctx.[Link](); // count
// Select/project to DTO: 20 DI (.NET) AddScoped/Transient/Singleton; constructor injection
await _ctx.[Link](e => new EventDto { Id=[Link], Name=[Link] }).ToListAsync();
21 [Link] new Chart(id, {type, data, options}); AfterViewInit
ANGULAR LAZY LOADING
// [Link] 22 Mat-Table MatTableDataSource; displayedColumns; MatSort/Paginator
{ path: "events", loadChildren: () =>
import("./events/[Link]").then(m => [Link]) }
23 Lazy load loadChildren: () => import(...); reduces bundle size
// Benefit: reduces initial bundle size — loads only when navigated to
.NET MODEL VALIDATION 24 LINQ .Where() .Select() .Include() .FirstOrDefault() .Any()
public class CreateEventDto {
[Required] [MaxLength(100)] 25 Validation .NET: [Required][MaxLength] on DTO; Angular: Validators
public string Name { get; set; } = [Link];
[Required] [DataType([Link])]
public DateTime Date { get; set; } DOTNET COMMANDS REFERENCE
[Range(0, 10000)] Command Effect
public decimal Price { get; set; }
}
dotnet new webapi -n MyApi Create Web API project
[ApiController] auto validates — returns 400 if [Link] = false
dotnet run Run the API (default port 5000/5001)
SCSS IN IONIC
// CSS Variables (Ionic theming):
dotnet build Compile project
--ion-color-primary: #3880ff;
// Utility: display flex, padding, margin via ion- prefix
// Responsive: Ionic grid system (ion-grid, ion-row, ion-col) dotnet test Run unit tests
<ion-col size="12" size-md="6" size-lg="4"> // breakpoints
dotnet add package PackageName Add NuGet package
dotnet ef migrations add Name Create EF migration
dotnet ef database update Apply migrations to DB
dotnet ef database drop Delete database
EXAM KEYWORD DECODER
If exam says... They mean...
"stateless authentication" JWT — no session stored on server
"cross-platform mobile" Ionic / hybrid app approach
"data binding" Template ↔ Component sync (4 types)
"separation of concerns" DTOs, services, repositories, MVC
"change detection" Angular re-renders when data changes
"lazy evaluation" Observable not executed until subscribed
"navigation guard" CanActivate preventing unauthorised access
"token expiry" JWT exp claim; check ValidateLifetime=true
"model validation" [Required] etc. + [Link]
"eager loading" EF .Include() — loads related entities
"single responsibility" Each class/component does ONE thing