Tips

  1. Separate words within a file name with hyphens (-). For example, a component named UserProfile has a file name user-profile.ts.



  2. For unit tests, end file names with .spec.ts. For example, the unit test file for the UserProfile component has the file name user-profile.spec.ts.



  3. All of your Angular UI code (TypeScript, HTML, and styles) should live inside a directory named src.

    Code that's not related to UI, such as configuration files or scripts, should live outside the src directory.



  4. The code to start up, or bootstrap, an Angular application should always live in a file named main.ts.

    This represents the primary entry point to the application.



  5. Angular components consist of a TypeScript file and, optionally, a template and one or more style files. You should group these together in the same directory.



  6. Avoid creating subdirectories based on the type of code that lives in those directories.

    For example, avoid creating directories like components/, directives/, and services/.



  7. Prefer the inject() function over constructor parameter injection.



  8. Use protected on class members that are only used by a component's template.

  9. @Component({
        ...,
        template: `

    {{ fullName() }}

    `, }) export class UserProfile { firstName = input(); lastName = input(); // `fullName` is not part of the component's public API, but is used in the template. protected fullName = computed(() => `${this.firstName()} ${this.lastName()}`); }



  10. Use readonly for properties that shouldn't change.

  11. @Component({/* ... */})
    export class UserProfile {
        readonly userId    = input();
        readonly userSaved = output();
        readonly userName  = model();
    }
    



Basics about Signals

  1. What is a signal?

    A signal is a reactive value container that automatically notifies Angular when its content changes.

    Unlike normal class properties, signals are tracked by Angular's change detection system — any component using them updates automatically.

    Example:
    import { signal, Component } from '@angular/core';
    
    @Component({
        selector: 'counter-example',
        template: `
            <h3>Count: {{ count() }}</h3>
            <button (click)="increment()">+</button>
        `,
        standalone: true,
    })
    export class CounterExample {
        count = signal(0);
        increment() {
            this.count.update(v => v + 1);
        }
    }
    

    Signals are functions — you read them by calling like count(), not count.



  2. Writable signals

    A writable signal is a normal signal created with signal(). It can be changed with:
    Example:
    const counter = signal(0);
    
    counter.set(10);          // sets value to 10
    counter.update(v => v+1); // now 11
    console.log(counter());   // prints 11
    



  3. Computed signals

    Computed signals are read-only values derived from one or more other signals. They automatically recalculate when any of their dependencies change.

    Example:
    import { signal, computed } from '@angular/core';
    
    const first = signal('Hwang');
    const last  = signal('Fu');
    
    const fullName = computed(() => `${first()} ${last()}`);
    
    console.log(fullName());   // "Hwang Fu"
    
    last.set('Fucius');
    console.log(fullName());   // "Hwang Fucius" – updated automatically
    



  4. Effects

    An effect() runs a piece of code automatically whenever the signals it reads change. Useful for logging, animations, or syncing values.

    Example:
    import { signal, effect } from '@angular/core';
    
    const count = signal(0);
    
    effect(() => {
        console.log('Count changed:', count());
    });
    
    count.set(1);  // console logs: Count changed: 1
    count.set(2);  // console logs: Count changed: 2
    

    Angular automatically cleans up effects when their owner component is destroyed.



  5. Inputs and signals

    You can also create component inputs that behave like signals using input().

    Example:
    @Component({
        selector: 'user-card',
        template: `<p>Hello, {{ name() }}!</p>`,
    })
    export class UserCard {
        name = input('Anonymous');   // signal-style input with default
    }
    




  6. Readonly vs writable signals

    A computed signal is readonly by design, meaning you cannot call .set() or .update() on it.

    Writable signals (signal()) can both be read and updated.



  7. Accessing signals from the template

    In templates, signals behave like functions — you must call them:
    <h2>{{ count() }}</h2>   <!-- ✅ correct -->
    <h2>{{ count }}</h2>     <!-- ❌ won't update -->
    

    Angular automatically re-renders that part of the template when the signal changes.



  8. When to use signals?



Linked Signals

  1. What is a linked signal?

    A linkedSignal() is a special kind of signal that stays connected to another signal.

    When the source signal changes, the linked one also updates automatically.

    Think of it as a "live copy" or a "mirror" of another signal — but one that can also transform or filter the value.

    Example:
    import { signal, linkedSignal } from '@angular/core';
    
    const name = signal('Hwangfu');
    
    // Linked signal that always follows `name`
    const upperName = linkedSignal(() => name().toUpperCase());
    
    console.log(upperName()); // "HWANGFU"
    
    name.set('Fucius');
    console.log(upperName()); // "FUCIUS" – updated automatically
    



  2. Why not just use computed()?

    computed() and linkedSignal() look similar, but there's one key difference:


    Example:
    const count = signal(0);
    
    // Computed: only reads from count
    const double = computed(() => count() * 2);
    
    // Linked: can read and write
    const mirror = linkedSignal(() => count(), {
        // define how writing back should work
        set: (newVal) => count.set(newVal),
    });
    
    mirror.set(5);
    console.log(count()); // 5
    



  3. When to use linked signals?

    Linked signals are useful when two parts of your code need to share the same data, but maybe in a slightly different form.

    For example, if you want to edit a copy of a user object without losing the original right away:
    const activeUser = signal({ id: 1, name: 'Alice' });
    
    // Create a linked version for editing
    const editUser = linkedSignal(() => activeUser(), {
        // treat users with the same id as the same object
        equal: (a, b) => a.id === b.id,
    });
    
    console.log(editUser());  // { id: 1, name: 'Alice' }
    
    editUser.update(u => ({ ...u, name: 'Alicia' }));
    console.log(editUser().name);  // "Alicia"
    
    // if `activeUser` changes, `editUser` also updates
    activeUser.set({ id: 1, name: 'Alina' });
    console.log(editUser().name);  // "Alina"
    



  4. Basic syntax patterns

    You can define a linked signal in two common ways:

    1. Simple form
    const copy = linkedSignal(() => source());
    
    2. Detailed form
    const copy = linkedSignal({
        source: source,
        computation: (value) => value,
        equal: (a, b) => a.id === b.id,
    });
    

    The detailed form gives you more control: you can define how values are compared, or how they're computed.


What does a CSS selector component do?

  1. When you write @Component({ selector: 'button[type="reset"]' }), Angular matches all elements in the DOM that fit this CSS selector.


  2. If an element matches, Angular creates your component and attaches it to that element.


  3. The element is NOT replaced. It stays the original tag. Angular just uses it as the host.


  4. Example:
    @Component({
        selector: 'button[type="reset"]',
        template: '',
    })
    export class ResetButton {}
    

    Any <button type="reset"> becomes a ResetButton component host.


Extend the example: make the reset button actually do things

  1. Below is a ResetButton component that:


  2. TypeScript (component):
    import { Component, HostBinding, HostListener, Input } from '@angular/core';
    
    @Component({
        selector: 'button[type="reset"]',
        // The host element IS the <button>. We only render inside it.
        template: `
            <span class="content" [class.hidden]="loading">
                <ng-content></ng-content>
            </span>
            <span class="spinner" *ngIf="loading" aria-hidden="true">⏳</span>
        `,
        styles: [`
            :host {
                /* base look */
                font: inherit;
                border-radius: 6px;
                padding: .5rem .9rem;
                cursor: pointer;
                border: 1px solid transparent;
                display: inline-flex;
                align-items: center;
                gap: .5rem;
                transition: background-color .15s ease, border-color .15s ease, color .15s ease;
            }
    
            /* variants via classes on :host */
            :host(.primary) {
                background: #0d6efd;
                color: white;
            }
            :host(.primary:hover) { background: #0b5ed7; }
    
            :host(.ghost) {
                background: transparent;
                color: #0d6efd;
                border-color: #0d6efd;
            }
            :host(.ghost:hover) { background: rgba(13,110,253,.08); }
    
            /* disabled look */
            :host(.disabled) {
                opacity: .55;
                cursor: not-allowed;
                pointer-events: none;
            }
    
            .spinner { display: inline-flex; }
            .content.hidden { visibility: hidden; } /* reserve space so width doesn't jump */
        `],
        standalone: true,
    })
    export class ResetButton {
        /** Visual variant */
        @Input() variant: 'primary' | 'ghost' = 'primary';
    
        /** Loading state: blocks clicks, shows spinner, updates aria */
        @Input() loading = false;
    
        /** Disable state (useful if you want to disable the reset button) */
        @Input() disabled = false;
    
        /** Reflect variant and states as CSS classes on the host <button> */
        @HostBinding('class')
        get hostClasses(): string {
            const v = this.variant ?? 'primary';
            return [v, this.disabled || this.loading ? 'disabled' : ''].filter(Boolean).join(' ');
        }
    
        /** Keep native semantics but add A11y hints */
        @HostBinding('attr.aria-disabled') get ariaDisabled() { return String(this.disabled); }
        @HostBinding('attr.aria-busy')     get ariaBusy()     { return String(this.loading); }
    
        /** Optional: ensure the host has type="reset" (safety if HTML missed it) */
        @HostBinding('attr.type') readonly type = 'reset';
    
        /** Block clicks when disabled/loading */
        @HostListener('click', ['$event'])
        onClick(ev: MouseEvent) {
            if (this.disabled || this.loading) {
                ev.preventDefault();
                ev.stopImmediatePropagation();
            }
        }
    }
    


  3. Usage examples:
    <!-- Matches selector because it's a button[type="reset"] -->
    <button type="reset" variant="primary">Reset form</button>
    
    <!-- Switch variant -->
    <button type="reset" variant="ghost">Clear</button>
    
    <!-- Loading state -->
    <button type="reset" variant="primary" [loading]="true">Resetting…</button>
    
    <!-- Disabled state -->
    <button type="reset" [disabled]="true">Disabled reset</button>
    
    <!-- With projected rich content -->
    <button type="reset" variant="primary">
        <span class="hl-grey-fg">↺</span>
        Reset filters
    </button>
    


  4. What changed compared to the minimal skeleton:


What does selector: 'drop-zone, [dropzone]' mean?

  1. This selector matches two things:



  2. Both become hosts for the DropZone component.


  3. Example:
    @Component({
        selector: 'drop-zone, [dropzone]',
        template: '',
    })
    export class DropZone {}
    


  4. The host element is NOT replaced, the component renders inside it.


How to define a custom attribute (Angular Input)?

  1. You define a custom attribute using @Input().


  2. This does not create a real DOM attribute unless you bind it.


  3. Example:
    @Component({
        selector: 'drop-zone',
        template: '',
    })
    export class DropZone {
        @Input() mode: 'files' | 'links' | 'images' = 'files';
    }
    
    Usage:
    <drop-zone></drop-zone>                (uses default: mode = "files")
    <drop-zone mode="links"></drop-zone>   (sets mode = "links")
    


How to show the custom Input as a real HTML attribute?

  1. Use @HostBinding('attr.<attribute-name>').


  2. This makes the Input appear in the DOM inspector.


  3. Example:
    @Directive({
        selector: '[dropzone]',
    })
    export class DropzoneDirective {
        @Input() dropzone: string = 'files';
    
        @HostBinding('attr.dropzone')
        get attrValue() {
            return this.dropzone;
        }
    }
    
    Resulting DOM:
    <div dropzone="files"></div>


Default boolean attribute with true/false behavior

  1. Use booleanAttribute transform.


  2. This makes appResizable behave like a proper boolean.


  3. Example:
    @Directive({
        selector: '[appResizable]',
    })
    export class ResizableDirective {
        @Input({ alias: 'appResizable', transform: booleanAttribute })
        resizable = true;
    
        @HostBinding('attr.data-resizable')
        get data() {
            return String(this.resizable);
        }
    }
    
    Usage:
    <div appResizable></div>            (true by default)
    <div appResizable="true"></div>     (true)
    <div [appResizable]="false"></div>  (false)
    


Setting a default DOM attribute directly

  1. If you just want an element to always have an attribute, bind it with HostBinding.


  2. Example: default type="reset" for any button with a directive.
    @Directive({
        selector: 'button[appResetDefault]',
    })
    export class ResetDefaultDirective {
        @HostBinding('attr.type') type = 'reset';
    }
    


  3. Usage:
    <button appResetDefault>Clear</button>
    This becomes:
    <button type="reset">Clear</button>


Can multiple components be applied to one element?

  1. No — only one component can attach to a single element.


  2. Directives can stack, but components cannot.


Component Selectors

  1. What is a component selector?

  2. @Component({
        selector: 'app-user-card',
        template: `<p>User card works</p>`,
    })
    export class UserCard {}
    



  3. Element selectors

  4. @Component({
        selector: 'app-profile',
        template: `<h2>Profile</h2>`,
    })
    export class ProfileComponent {}
    
    <!-- Usage in another template -->
    <app-profile></app-profile>
    



  5. Attribute selectors

  6. @Component({
        selector: '[appPanel]',
        template: `<ng-content></ng-content>`,
    })
    export class PanelComponent {}
    
    <!-- Usage -->
    <section appPanel>
        Panel content
    </section>
    



  7. Multiple selectors

  8. @Component({
        selector: 'drop-zone, [dropzone]',
        template: '<ng-content></ng-content>',
    })
    export class DropZoneComponent {}
    
    <!-- Both usages match the same component -->
    <drop-zone>Drop files here</drop-zone>
    
    <div dropzone>Drop files here</div>
    



  9. Using CSS selectors

  10. @Component({
        selector: 'button[type="reset"]',
        template: '<ng-content></ng-content>',
    })
    export class ResetButtonComponent {}
    
    <button type="reset">Reset form</button>
    



  11. One component per element



  12. Naming and best practices



  13. How Angular uses the selector




Basics on Styling Components

  1. Where do component styles live?

  2. @Component({
        selector: 'app-user-card',
        template: `<div class="card">User card</div>`,
        styles: [`
            .card {
                padding: 1rem;
                border-radius: .5rem;
                border: 1px solid #ddd;
            }
        `],
    })
    export class UserCard {}
    

    @Component({
        selector: 'app-user-card',
        templateUrl: './user-card.component.html',
        styleUrls: ['./user-card.component.css'],
    })
    export class UserCard {}
    



  3. Component styles are local

  4. @Component({
        selector: 'app-a',
        template: `<h2 class="title">A</h2>`,
        styles: [`.title { color: red; }`],
    })
    export class AComponent {}
    
    @Component({
        selector: 'app-b',
        template: `<h2 class="title">B</h2>`,
        styles: [`.title { color: blue; }`],
    })
    export class BComponent {}
    
    <app-a></app-a>
    <app-b></app-b>
    



  5. Styling the host element with :host

  6. @Component({
        selector: 'app-pill-badge',
        template: `<ng-content></ng-content>`,
        styles: [`
            :host {
                display: inline-block;
                padding: .2rem .6rem;
                border-radius: 999px;
                background: #f0f4ff;
                color: #1f3b8f;
                font-size: .8rem;
            }
        `],
        standalone: true,
    })
    export class PillBadge {}
    
    <app-pill-badge>New</app-pill-badge>
    



  7. Styling based on host classes: :host(.some-class)

  8. @Component({
        selector: 'app-alert',
        template: `<ng-content></ng-content>`,
        styles: [`
            :host {
                display: block;
                padding: .75rem 1rem;
                border-radius: 4px;
            }
    
            :host(.info) {
                background: #e7f1ff;
                color: #0b4f9c;
            }
    
            :host(.error) {
                background: #ffe5e5;
                color: #a30000;
            }
        `],
        standalone: true,
    })
    export class AlertComponent {}
    
    <app-alert class="info">Information</app-alert>
    <app-alert class="error">Something went wrong</app-alert>
    



  9. Binding classes and styles from TypeScript

  10. <button
        class="btn"
        [class.btn-primary]="primary"
        [class.btn-disabled]="disabled"
    >
        Save
    </button>
    
    @Component({
        selector: 'app-save-button',
        templateUrl: './save-button.component.html',
    })
    export class SaveButtonComponent {
        primary = true;
        disabled = false;
    }
    

    <div [style.opacity]="disabled ? 0.5 : 1">Content</div>
    



  11. Component styles vs global styles

  12. /* styles.css */
    html, body {
        margin: 0;
        font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
        background: #fafafa;
    }
    



  13. Light introduction to :host-context()

  14. @Component({
        selector: 'app-box',
        template: `<ng-content></ng-content>`,
        styles: [`
            :host {
                display: block;
                padding: 1rem;
                background: white;
                color: black;
            }
    
            :host-context(.dark-mode) {
                background: #1f2933;
                color: #f9fafb;
            }
        `],
    })
    export class BoxComponent {}
    
    <div class="dark-mode">
        <app-box>This box appears in dark mode</app-box>
    </div>
    




Basics on Input Properties

  1. What are input properties?

  2. @Component({
        selector: 'app-greeting',
        template: `<h3>Hello, {{ name }}!</h3>`,
    })
    export class GreetingComponent {
        @Input() name = 'Guest';   // receives value from parent
    }
    
    <!-- Parent template -->
    <app-greeting name="Alice"></app-greeting>
    <app-greeting name="Bob"></app-greeting>
    



  3. Binding expressions to inputs

  4. @Component({
        selector: 'app-parent',
        template: `
            <app-greeting [name]="userName"></app-greeting>
        `,
    })
    export class ParentComponent {
        userName = 'Hwangfucius';
    }
    


  5. Setting default values

  6. @Component({
        selector: 'app-user-card',
        template: `<p>User: {{ name }} ({{ role }})</p>`,
    })
    export class UserCardComponent {
        @Input() name = 'Anonymous';
        @Input() role = 'Viewer';
    }
    
    <app-user-card></app-user-card>
    <app-user-card name="Alice" role="Admin"></app-user-card>
    



  7. Renaming an input

  8. @Component({
        selector: 'app-book',
        template: `<p>Title: {{ title }}</p>`,
    })
    export class BookComponent {
        @Input('bookTitle') title = '';
    }
    
    <app-book bookTitle="The Hobbit"></app-book>
    



  9. Input type conversion (transforms)

  10. import { Component, Input, booleanAttribute } from '@angular/core';
    
    @Component({
        selector: 'app-toggle',
        template: `<button>Enabled: {{ enabled }}</button>`,
    })
    export class ToggleComponent {
        @Input({ transform: booleanAttribute }) enabled = false;
    }
    
    <app-toggle enabled></app-toggle>
    <app-toggle enabled="false"></app-toggle>
    <app-toggle></app-toggle>
    



  11. Signal-style inputs (Angular 17+)

  12. import { Component, input } from '@angular/core';
    
    @Component({
        selector: 'app-profile',
        template: `<p>Welcome, {{ name() }}!</p>`,
    })
    export class ProfileComponent {
        name = input('Guest');
    }
    
    <app-profile name="Hwangfucius"></app-profile>
    



  13. Validating and reacting to input changes

  14. // 1. Use a setter
    @Input()
    set count(value: number) {
        console.log('Count changed to', value);
    }
    
    // 2. Use ngOnChanges (classic lifecycle hook)
    ngOnChanges(changes: SimpleChanges) {
        console.log(changes);
    }
    




Basics on Output Properties

  1. What are output properties?

  2. import { Component, Output, EventEmitter } from '@angular/core';
    
    @Component({
        selector: 'app-like-button',
        template: `<button (click)="like()">Like</button>`,
    })
    export class LikeButtonComponent {
        @Output() liked = new EventEmitter<void>();
    
        like() {
            console.log('Button clicked!');
            this.liked.emit();
        }
    }
    
    <!-- Parent component -->
    <app-like-button (liked)="onLiked()"></app-like-button>
    



  3. Sending data with the event

  4. @Component({
        selector: 'app-like-counter',
        template: `<button (click)="addLike()">{{ count }}</button>`,
    })
    export class LikeCounterComponent {
        count = 0;
        @Output() countChange = new EventEmitter<number>();
    
        addLike() {
            this.count++;
            this.countChange.emit(this.count);
        }
    }
    
    <!-- Parent component -->
    <app-like-counter (countChange)="onCountUpdated($event)"></app-like-counter>
    
    // Parent component
    onCountUpdated(newCount: number) {
        console.log('New like count:', newCount);
    }
    



  5. Renaming an output event

  6. @Component({
        selector: 'app-toggle',
        template: `
            <button (click)="toggle()">Toggle</button>
        `,
    })
    export class ToggleComponent {
        private on = false;
    
        @Output('toggled') stateChanged = new EventEmitter<boolean>();
    
        toggle() {
            this.on = !this.on;
            this.stateChanged.emit(this.on);
        }
    }
    
    <!-- Parent uses the alias name -->
    <app-toggle (toggled)="onToggled($event)"></app-toggle>
    



  7. Combining inputs and outputs

  8. @Component({
        selector: 'app-checkbox',
        template: `
            <label>
                <input type="checkbox" [checked]="checked" (change)="onChange($event)" />
                <ng-content></ng-content>
            </label>
        `,
    })
    export class CheckboxComponent {
        @Input()  checked = false;
        @Output() checkedChange = new EventEmitter<boolean>();
    
        onChange(event: Event) {
            const input = event.target as HTMLInputElement;
            this.checkedChange.emit(input.checked);
        }
    }
    
    <!-- Parent component -->
    <app-checkbox [(checked)]="subscribed">Subscribe</app-checkbox>
    



  9. Signal-style outputs (Angular 17+)

  10. import { Component, output } from '@angular/core';
    
    @Component({
        selector: 'app-delete-button',
        template: `<button (click)="onDelete()">Delete</button>`,
    })
    export class DeleteButtonComponent {
        deleted = output<string>();
    
        onDelete() {
            this.deleted.emit('item-42');
        }
    }
    
    <app-delete-button (deleted)="handleDelete($event)"></app-delete-button>
    




Content projection with <ng-content>

  1. What is content projection?

  2. @Component({
        selector: 'app-card',
        template: `
            <div class="card">
                <h3>Header</h3>
                <ng-content></ng-content>   <!-- projected content here -->
            </div>
        `,
        styles: [`
            .card {
                border: 1px solid #ddd;
                border-radius: .5rem;
                padding: 1rem;
                margin: .5rem 0;
            }
        `],
    })
    export class CardComponent {}
    
    <!-- Parent component -->
    <app-card>
        <p>This paragraph comes from the parent.</p>
    </app-card>
    



  3. Projecting multiple content sections

  4. @Component({
        selector: 'app-dialog',
        template: `
            <div class="dialog">
                <header>
                    <ng-content select="[dialog-title]"></ng-content>
                </header>
    
                <section>
                    <ng-content></ng-content>   <!-- default slot -->
                </section>
    
                <footer>
                    <ng-content select="[dialog-actions]"></ng-content>
                </footer>
            </div>
        `,
    })
    export class DialogComponent {}
    
    <app-dialog>
        <h2 dialog-title>Delete file?</h2>
    
        <p>This action cannot be undone.</p>
    
        <div dialog-actions>
            <button>Cancel</button>
            <button>Confirm</button>
        </div>
    </app-dialog>
    



  5. Using ngProjectAs for flexibility

  6. <app-dialog>
        <app-title ngProjectAs="[dialog-title]">Edit profile</app-title>
    
        <p>Form fields go here...</p>
    
        <app-footer ngProjectAs="[dialog-actions]">
            <button>Close</button>
        </app-footer>
    </app-dialog>
    



  7. Projecting multiple components

  8. <app-card>
        <app-user-info [user]="user"></app-user-info>
        <app-user-stats [user]="user"></app-user-stats>
    </app-card>
    




Component Host Elements

  1. What is a host element?

  2. @Component({
        selector: 'app-user-card',
        template: `<p>User: {{ name }}</p>`,
    })
    export class UserCardComponent {
        name = 'Hwangfucius';
    }
    
    <!-- Parent template -->
    <app-user-card></app-user-card>
    



  3. Host element vs child elements

  4. <!-- In the browser -->
    <app-user-card>                     <!-- 👈 host element -->
        <p>User: Hwangfucius</p>        <!-- 👈 child content -->
    </app-user-card>
    


  5. Styling the host element

  6. @Component({
        selector: 'app-alert',
        template: `<ng-content></ng-content>`,
        styles: [`
            :host {
                display: block;
                padding: 1rem;
                border-radius: .5rem;
                background: #f9f9f9;
            }
    
            :host(.warning) {
                background: #fff3cd;
                color: #856404;
            }
    
            :host(.error) {
                background: #f8d7da;
                color: #842029;
            }
        `],
    })
    export class AlertComponent {}
    
    <app-alert class="warning">Low battery</app-alert>
    <app-alert class="error">System failure</app-alert>
    



  7. Binding attributes and classes to the host element

  8. import { Component, HostBinding, Input } from '@angular/core';
    
    @Component({
        selector: 'app-status-indicator',
        template: `<ng-content></ng-content>`,
    })
    export class StatusIndicatorComponent {
        @Input() status: 'online' | 'offline' = 'offline';
    
        @HostBinding('class')
        get hostClass(): string {
            return this.status;
        }
    
        @HostBinding('attr.role') role = 'status';
    }
    
    <app-status-indicator status="online">Connected</app-status-indicator>
    <app-status-indicator status="offline">Disconnected</app-status-indicator>
    



  9. Listening to events on the host

  10. import { Component, HostListener } from '@angular/core';
    
    @Component({
        selector: 'app-clickable',
        template: `<ng-content></ng-content>`,
    })
    export class ClickableComponent {
        @HostListener('click', ['$event'])
        handleClick(event: MouseEvent) {
            console.log('Host element clicked!', event);
        }
    }
    
    <app-clickable>Click me!</app-clickable>
    



  11. Accessing the host element in code

  12. import { Component, ElementRef } from '@angular/core';
    
    @Component({
        selector: 'app-box',
        template: `<ng-content></ng-content>`,
    })
    export class BoxComponent {
        constructor(private host: ElementRef<HTMLElement>) {}
    
        ngAfterViewInit() {
            this.host.nativeElement.style.border = '2px solid blue';
        }
    }
    
    <app-box>Styled from code</app-box>
    



  13. Difference between host and parent elements

  14. <div class="wrapper">         <!-- parent element -->
        <app-card></app-card>     <!-- host element -->
    </div>
    




Component Lifecycle

  1. What is a component lifecycle?

  2. import { Component, OnInit, OnDestroy } from '@angular/core';
    
    @Component({
        selector: 'app-timer',
        template: `<p>Seconds: {{ seconds }}</p>`,
    })
    export class TimerComponent implements OnInit, OnDestroy {
        seconds = 0;
        private intervalId: any;
    
        ngOnInit() {
            console.log('Timer created');
            this.intervalId = setInterval(() => this.seconds++, 1000);
        }
    
        ngOnDestroy() {
            console.log('Timer destroyed');
            clearInterval(this.intervalId);
        }
    }
    
    <!-- When  is created, ngOnInit runs. When it's removed, ngOnDestroy runs. -->
    <app-timer *ngIf="showTimer"></app-timer>
    <button (click)="showTimer = !showTimer">Toggle Timer</button>
    



  3. The full lifecycle order

  4. 1. ngOnChanges()
    2. ngOnInit()
    3. ngDoCheck()
    4. ngAfterContentInit()
    5. ngAfterContentChecked()
    6. ngAfterViewInit()
    7. ngAfterViewChecked()
    8. ngOnDestroy()
    



  5. ngOnChanges()

  6. @Component({
        selector: 'app-counter',
        template: `<p>Count: {{ count }}</p>`,
    })
    export class CounterComponent implements OnChanges {
        @Input() count = 0;
    
        ngOnChanges(changes: SimpleChanges) {
            console.log('Changes:', changes);
        }
    }
    
    <app-counter [count]="num"></app-counter>
    <button (click)="num++">Increment</button>
    



  7. ngOnInit()

  8. ngOnInit() {
        console.log('Component ready to use!');
        this.loadData();
    }
    



  9. ngDoCheck()

  10. ngDoCheck() {
        console.log('Change detection running...');
    }
    


  11. ngAfterContentInit() and ngAfterContentChecked()

  12. ngAfterContentInit() {
        console.log('Projected content has been initialized.');
    }
    
    ngAfterContentChecked() {
        console.log('Projected content checked again.');
    }
    



  13. ngAfterViewInit() and ngAfterViewChecked()

  14. @ViewChild('box') box!: ElementRef;
    
    ngAfterViewInit() {
        console.log('Template view ready:', this.box.nativeElement);
    }
    
    <div #box>Hello box</div>
    



  15. ngOnDestroy()

  16. ngOnDestroy() {
        console.log('Cleaning up...');
        this.subscription.unsubscribe();
    }
    



  17. Visual summary
  18. 
     ┌──────────────────────────────────────┐
     │ Component Created                    │
     │  ├─ ngOnChanges()                    │
     │  ├─ ngOnInit()                       │
     │  ├─ ngDoCheck()                      │
     │  ├─ ngAfterContentInit()             │
     │  ├─ ngAfterContentChecked()          │
     │  ├─ ngAfterViewInit()                │
     │  └─ ngAfterViewChecked()             │
     │                                      │
     │ Component Updated                    │
     │  ├─ ngDoCheck()                      │
     │  ├─ ngAfterContentChecked()          │
     │  └─ ngAfterViewChecked()             │
     │                                      │
     │ Component Destroyed                  │
     │  └─ ngOnDestroy()                    │
     └──────────────────────────────────────┘
    




Referencing Component Children with Queries

  1. What are queries?



  2. @ViewChild() — getting a single child element

  3. import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
    
    @Component({
        selector: 'app-focus-input',
        template: `<input #userInput type="text" placeholder="Type something..." />`,
    })
    export class FocusInputComponent implements AfterViewInit {
        @ViewChild('userInput') inputElement!: ElementRef<HTMLInputElement>;
    
        ngAfterViewInit() {
            this.inputElement.nativeElement.focus();  // focus when ready
        }
    }
    



  4. @ViewChild() — getting a child component instance

  5. @Component({
        selector: 'app-child',
        template: `<p>Child works!</p>`,
    })
    export class ChildComponent {
        sayHello() {
            console.log('Hello from Child!');
        }
    }
    
    @Component({
        selector: 'app-parent',
        template: `
            <app-child></app-child>
            <button (click)="callChild()">Call Child</button>
        `,
    })
    export class ParentComponent {
        @ViewChild(ChildComponent) child!: ChildComponent;
    
        callChild() {
            this.child.sayHello();
        }
    }
    



  6. @ViewChildren() — getting multiple elements or components

  7. @Component({
        selector: 'app-item',
        template: `<li>Item</li>`,
    })
    export class ItemComponent {}
    
    @Component({
        selector: 'app-list',
        template: `
            <ul>
                <app-item *ngFor="let i of [1,2,3]"></app-item>
            </ul>
            <button (click)="logItems()">Log items</button>
        `,
    })
    export class ListComponent implements AfterViewInit {
        @ViewChildren(ItemComponent) items!: QueryList<ItemComponent>;
    
        ngAfterViewInit() {
            console.log('Found items:', this.items.length);
        }
    
        logItems() {
            this.items.forEach((item, index) => console.log('Item', index + 1, item));
        }
    }
    



  8. @ContentChild() and @ContentChildren() — projected content

  9. @Component({
        selector: 'app-panel',
        template: `
            <div class="panel">
                <ng-content></ng-content>
            </div>
        `,
    })
    export class PanelComponent implements AfterContentInit {
        @ContentChild('projected') content!: ElementRef;
    
        ngAfterContentInit() {
            console.log('Projected element:', this.content.nativeElement);
        }
    }
    
    <app-panel>
        <p #projected>Hello projected world!</p>
    </app-panel>
    



  10. When to use each hook

  11. 
    ViewChild       → single element in template
    ViewChildren    → multiple elements in template
    ContentChild    → single projected element (via <ng-content>)
    ContentChildren → multiple projected elements
    


  12. Practical use cases




Using DOM APIs in Angular

  1. Why use DOM APIs?



  2. Accessing elements with ElementRef

  3. import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
    
    @Component({
        selector: 'app-box',
        template: `<div #box class="blue-box">Hello Box</div>`,
        styles: [`.blue-box { width: 100px; height: 100px; background: lightblue; }`]
    })
    export class BoxComponent implements AfterViewInit {
        @ViewChild('box') box!: ElementRef<HTMLDivElement>;
    
        ngAfterViewInit() {
            console.log(this.box.nativeElement); // <div> element itself
            this.box.nativeElement.style.border = '2px solid navy';
        }
    }
    



  4. Modifying styles and classes

  5. import { Component, ElementRef, Renderer2, ViewChild, AfterViewInit } from '@angular/core';
    
    @Component({
        selector: 'app-colored-box',
        template: `<div #box>Click me!</div>`
    })
    export class ColoredBoxComponent implements AfterViewInit {
        @ViewChild('box') box!: ElementRef;
        constructor(private renderer: Renderer2) {}
    
        ngAfterViewInit() {
            this.renderer.setStyle(this.box.nativeElement, 'background', 'pink');
            this.renderer.setStyle(this.box.nativeElement, 'padding', '10px');
            this.renderer.addClass(this.box.nativeElement, 'rounded');
        }
    }
    



  6. Listening to DOM events

  7. ngAfterViewInit() {
        this.renderer.listen(this.box.nativeElement, 'click', () => {
            alert('Box clicked!');
        });
    }
    



  8. Reading sizes and positions

  9. ngAfterViewInit() {
        const rect = this.box.nativeElement.getBoundingClientRect();
        console.log('Width:', rect.width, 'Height:', rect.height);
    }
    



  10. Scrolling and focusing

  11. scrollToBox() {
        this.box.nativeElement.scrollIntoView({ behavior: 'smooth' });
    }
    



  12. Using @HostListener for simple DOM events

  13. import { Component, HostListener } from '@angular/core';
    
    @Component({
        selector: 'app-click-alert',
        template: `<div>Click anywhere on me!</div>`,
    })
    export class ClickAlertComponent {
        @HostListener('click')
        handleClick() {
            console.log('Host element clicked!');
        }
    }
    



  14. Safety notes




Inheritance in Angular

  1. Using inheritance between Angular components

  2. import { Component } from '@angular/core';
    
    export class BaseLogger {
        log(message: string) {
            console.log(`[LOG]: ${message}`);
        }
    }
    
    @Component({
        selector: 'app-user',
        template: `<p>User Component</p>`,
    })
    export class UserComponent extends BaseLogger {
        constructor() {
            super();
            this.log('User component initialized');
        }
    }
    
    @Component({
        selector: 'app-admin',
        template: `<p>Admin Component</p>`,
    })
    export class AdminComponent extends BaseLogger {
        constructor() {
            super();
            this.log('Admin component initialized');
        }
    }
    



  3. Extending a base component with lifecycle hooks

  4. import { OnInit, OnDestroy } from '@angular/core';
    
    export abstract class BaseComponent implements OnInit, OnDestroy {
        ngOnInit() {
            console.log('Component initialized.');
        }
    
        ngOnDestroy() {
            console.log('Component destroyed.');
        }
    }
    
    @Component({
        selector: 'app-profile',
        template: `<p>Profile</p>`,
    })
    export class ProfileComponent extends BaseComponent {
        ngOnInit() {
            super.ngOnInit(); // Call base logic
            console.log('Profile-specific init.');
        }
    }
    



  5. Sharing injected services across subclasses

  6. import { Component } from '@angular/core';
    import { HttpClient } from '@angular/common/http';
    
    export class BaseDataComponent {
        constructor(protected http: HttpClient) {}
    
        fetchData(url: string) {
            return this.http.get(url);
        }
    }
    
    @Component({
        selector: 'app-users',
        template: `<button (click)="load()">Load Users</button>`,
    })
    export class UsersComponent extends BaseDataComponent {
        load() {
            this.fetchData('/api/users').subscribe(data => console.log(data));
        }
    }
    



  7. Inheritance for directives

  8. import { Directive, HostListener, ElementRef } from '@angular/core';
    
    @Directive()
    export class HoverBase {
        constructor(protected el: ElementRef) {}
    
        @HostListener('mouseenter')
        onEnter() {
            this.el.nativeElement.style.opacity = '0.8';
        }
    
        @HostListener('mouseleave')
        onLeave() {
            this.el.nativeElement.style.opacity = '1';
        }
    }
    
    @Directive({
        selector: '[highlightOnHover]'
    })
    export class HighlightDirective extends HoverBase {
        @HostListener('mouseenter')
        override onEnter() {
            super.onEnter();
            this.el.nativeElement.style.background = 'lightyellow';
        }
    }
    



  9. When not to use inheritance




Programmatically Rendering Components

  1. What does "programmatically rendering" mean?



  2. Basic idea



  3. Setup: create a target placeholder

  4. <div>Dynamic Area:</div>
    <ng-container #container></ng-container>
    <button (click)="addCard()">Add Card</button>
    



  5. Getting the container in TypeScript

  6. import { Component, ViewChild, ViewContainerRef } from '@angular/core';
    import { CardComponent } from './card.component';
    
    @Component({
        selector: 'app-dynamic-host',
        templateUrl: './dynamic-host.component.html',
    })
    export class DynamicHostComponent {
        @ViewChild('container', { read: ViewContainerRef }) container!: ViewContainerRef;
    
        addCard() {
            this.container.createComponent(CardComponent);
        }
    }
    



  7. Passing data to the created component

  8. addCard() {
        const cardRef = this.container.createComponent(CardComponent);
        cardRef.instance.title = 'Dynamic card created at ' + new Date().toLocaleTimeString();
    }
    



  9. Clearing or removing components

  10. clearAll() {
        this.container.clear();
    }
    

    const ref = this.container.createComponent(CardComponent);
    // later...
    ref.destroy();
    



  11. Example: a dynamic alert system

  12. @Component({
        selector: 'app-alert',
        template: `<div class="alert">{{ message }}</div>`,
        styles: [`.alert { background: #ffe4b5; padding: .5rem; margin: .25rem 0; }`],
        standalone: true,
    })
    export class AlertComponent {
        message = '';
    }
    
    @Component({
        selector: 'app-alert-demo',
        template: `
            <button (click)="showAlert('Something happened!')">Show Alert</button>
            <ng-container #alertHost></ng-container>
        `,
    })
    export class AlertDemoComponent {
        @ViewChild('alertHost', { read: ViewContainerRef }) host!: ViewContainerRef;
    
        showAlert(text: string) {
            const ref = this.host.createComponent(AlertComponent);
            ref.instance.message = text;
            setTimeout(() => ref.destroy(), 3000); // auto remove after 3s
        }
    }
    



  13. Advanced: creating from a component type variable

  14. import { Type } from '@angular/core';
    
    const components: Type<any>[] = [AlertComponent, CardComponent, WidgetComponent];
    
    addRandom() {
        const randomType = components[Math.floor(Math.random() * components.length)];
        this.container.createComponent(randomType);
    }
    



  15. Lifecycle and cleanup




Advanced component configuration

  1. Introduction



  2. Standalone components

  3. @Component({
        selector: 'app-hello',
        standalone: true,
        template: `<p>Hello from a standalone component!</p>`,
    })
    export class HelloComponent {}
    

    @Component({
        selector: 'app-parent',
        standalone: true,
        imports: [HelloComponent],
        template: `<app-hello></app-hello>`,
    })
    export class ParentComponent {}
    


  4. Change detection strategies

  5. import { Component, ChangeDetectionStrategy } from '@angular/core';
    
    @Component({
        selector: 'app-optimized',
        template: `<p>Optimized Component</p>`,
        changeDetection: ChangeDetectionStrategy.OnPush,
    })
    export class OptimizedComponent {}
    



  6. View encapsulation modes

  7. import { Component, ViewEncapsulation } from '@angular/core';
    
    @Component({
        selector: 'app-box',
        template: `<div>I'm a box</div>`,
        styles: [`div { color: red; }`],
        encapsulation: ViewEncapsulation.None,
    })
    export class BoxComponent {}
    



  8. Using providers inside a component

  9. import { Component } from '@angular/core';
    import { CounterService } from './counter.service';
    
    @Component({
        selector: 'app-counter',
        template: `
            <p>Count: {{ service.count }}</p>
            <button (click)="service.increment()">+</button>
        `,
        providers: [CounterService]
    })
    export class CounterComponent {
        constructor(public service: CounterService) {}
    }
    



  10. Controlling change detection manually

  11. import { Component, ChangeDetectorRef } from '@angular/core';
    
    @Component({
        selector: 'app-manual-refresh',
        template: `
            <p>Last updated: {{ time }}</p>
            <button (click)="refresh()">Refresh</button>
        `
    })
    export class ManualRefreshComponent {
        time = new Date().toLocaleTimeString();
    
        constructor(private cdr: ChangeDetectorRef) {}
    
        refresh() {
            this.time = new Date().toLocaleTimeString();
            this.cdr.detectChanges();  // manually trigger view update
        }
    }
    



  12. Host bindings and host listeners

  13. import { Component, HostBinding, HostListener } from '@angular/core';
    
    @Component({
        selector: 'app-box',
        template: `<ng-content></ng-content>`,
    })
    export class BoxComponent {
        @HostBinding('class.active') isActive = false;
    
        @HostListener('click')
        toggle() {
            this.isActive = !this.isActive;
        }
    }
    



  14. Importing dependencies directly

  15. import { CommonModule } from '@angular/common';
    
    @Component({
        selector: 'app-list',
        standalone: true,
        imports: [CommonModule],
        template: `
            <ul>
                <li *ngFor="let item of items">{{ item }}</li>
            </ul>
        `,
    })
    export class ListComponent {
        items = ['A', 'B', 'C'];
    }
    




Binding dynamic text, properties, and attributes

  1. Introduction



  2. Interpolating text ({{ }})

  3. @Component({
        selector: 'app-greeter',
        template: `
            <p>Hello, {{ name }}!</p>
            <p>Today is {{ date | date:'fullDate' }}.</p>
        `,
    })
    export class GreeterComponent {
        name = 'Hwangfucius';
        date = new Date();
    }
    



  4. Binding to DOM properties

  5. <button [disabled]="isLoading">Save</button>
    <input [value]="username" />
    <img [src]="imageUrl" [alt]="description" />
    
    export class ExampleComponent {
        isLoading = true;
        username = 'Alice';
        imageUrl = '/assets/avatar.png';
        description = 'Profile picture';
    }
    



  6. Binding to attributes

  7. <button [attr.aria-label]="label">Click</button>
    <div [attr.data-id]="uniqueId"></div>
    
    export class AttrExampleComponent {
        label = 'Action button';
        uniqueId = 12345;
    }
    



  8. Class and style bindings

  9. <div [class.active]="isActive">Active box</div>
    <div [style.background-color]="color">Colored box</div>
    
    export class StyleExampleComponent {
        isActive = true;
        color = 'lightgreen';
    }
    



  10. Event binding + property binding = interactivity

  11. <input [value]="username" (input)="username = $event.target.value" />
    <p>Current username: {{ username }}</p>
    
    export class TwoWayComponent {
        username = 'Guest';
    }
    



  12. Binding HTML safely

  13. <div [innerHTML]="htmlSnippet"></div>
    
    export class HtmlExampleComponent {
        htmlSnippet = '<b>Bold text</b> and <i>italic</i>';
    }
    



  14. Binding SVG or custom attributes

  15. <svg width="100" height="100">
        <circle [attr.cx]="50" [attr.cy]="50" [attr.r]="radius" [attr.fill]="color"></circle>
    </svg>
    
    export class SvgExampleComponent {
        radius = 30;
        color = 'lightblue';
    }
    




Adding Event Listeners in Angular

  1. Introduction



  2. Basic syntax

  3. <element (eventName)="expression"></element>
    

    <button (click)="onClick()">Click me</button>
    
    export class ClickExampleComponent {
        onClick() {
            console.log('Button clicked!');
        }
    }
    



  4. Accessing the event object

  5. <input (input)="onInput($event)" placeholder="Type something..." />
    
    export class InputExampleComponent {
        onInput(event: Event) {
            const input = event.target as HTMLInputElement;
            console.log('You typed:', input.value);
        }
    }
    



  6. Passing custom arguments

  7. <button (click)="sayHello('Hwangfucius', $event)">Greet</button>
    
    export class HelloComponent {
        sayHello(name: string, event: MouseEvent) {
            console.log(`Hello, ${name}!`, event);
        }
    }
    



  8. Keyboard events

  9. <input (keyup.enter)="onEnter($event)" placeholder="Press Enter" />
    
    export class KeyExampleComponent {
        onEnter(event: KeyboardEvent) {
            const input = event.target as HTMLInputElement;
            console.log('Entered:', input.value);
        }
    }
    



  10. Mouse events

  11. <div
        (mouseenter)="onHover(true)"
        (mouseleave)="onHover(false)"
        [class.active]="hovered"
    >
        Hover over me!
    </div>
    
    export class MouseExampleComponent {
        hovered = false;
    
        onHover(isHovering: boolean) {
            this.hovered = isHovering;
        }
    }
    



  12. Event filtering with templates

  13. <input (keydown)="($event.key === 'Enter') && submit()" />
    



  14. Using @HostListener for host element events

  15. import { Component, HostListener } from '@angular/core';
    
    @Component({
        selector: 'app-box',
        template: `<div>Click anywhere on me</div>`,
    })
    export class BoxComponent {
        @HostListener('click', ['$event'])
        handleClick(event: MouseEvent) {
            console.log('Box clicked!', event);
        }
    }
    

    @HostListener('window:scroll', ['$event'])
    onScroll(event: Event) {
        console.log('Scrolling detected!');
    }
    



  16. Event binding vs DOM addEventListener



  17. Combining events with data binding

  18. <input [value]="text" (input)="text = $event.target.value" />
    <p>You typed: {{ text }}</p>
    
    export class CombineExampleComponent {
        text = '';
    }
    




Two-way binding in Angular

  1. Introduction



  2. Basic concept

  3. <input [(ngModel)]="username" />
    <p>Hello, {{ username }}!</p>
    
    export class TwoWayExampleComponent {
        username = 'Alice';
    }
    



  4. How ngModel works internally

  5. <input
        [ngModel]="username"
        (ngModelChange)="username = $event"
    />
    



  6. Using ngModel (FormsModule required)

  7. import { Component } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    
    @Component({
        selector: 'app-form-demo',
        standalone: true,
        imports: [FormsModule],
        template: `
            <input [(ngModel)]="name" placeholder="Enter your name" />
            <p>Hello, {{ name || 'stranger' }}!</p>
        `
    })
    export class FormDemoComponent {
        name = '';
    }
    



  8. Two-way binding with custom components

  9. @Component({
        selector: 'app-toggle',
        template: `
            <button (click)="toggle()">
                {{ on ? 'ON' : 'OFF' }}
            </button>
        `,
        standalone: true
    })
    export class ToggleComponent {
        @Input() on = false;
        @Output() onChange = new EventEmitter<boolean>();
    
        toggle() {
            this.on = !this.on;
            this.onChange.emit(this.on);
        }
    }
    
    <app-toggle [(on)]="enabled"></app-toggle>
    <p>Feature is: {{ enabled ? 'Enabled' : 'Disabled' }}</p>
    
    export class ParentComponent {
        enabled = true;
    }
    



  10. Two-way binding with checkboxes and select boxes

  11. <label>
        <input type="checkbox" [(ngModel)]="subscribed" /> Subscribe to newsletter
    </label>
    
    <select [(ngModel)]="language">
        <option value="en">English</option>
        <option value="de">German</option>
        <option value="zh">Chinese</option>
    </select>
    
    <p>Subscribed: {{ subscribed }}</p>
    <p>Language: {{ language }}</p>
    
    export class PreferencesComponent {
        subscribed = false;
        language = 'en';
    }
    



  12. Using standalone signals instead of ngModel (modern alternative)

  13. import { Component, signal } from '@angular/core';
    
    @Component({
        selector: 'app-signal-demo',
        template: `
            <input [value]="name()" (input)="name.set($event.target.value)" />
            <p>Name: {{ name() }}</p>
        `,
        standalone: true
    })
    export class SignalDemoComponent {
        name = signal('Hwangfucius');
    }
    




Control Flow in Angular

  1. Introduction



  2. Conditional rendering with @if

  3. @if (loggedIn) {
        <p>Welcome back, {{ username }}!</p>
    } @else {
        <p>Please log in to continue.</p>
    }
    
    export class LoginExampleComponent {
        loggedIn = false;
        username = 'Hwangfucius';
    }
    



  4. Using @else if

  5. @if (role === 'admin') {
        <p>Welcome, admin!</p>
    } @else if (role === 'editor') {
        <p>Hello, editor!</p>
    } @else {
        <p>Guest access only.</p>
    }
    
    export class RoleExampleComponent {
        role = 'editor';
    }
    



  6. Repeating elements with @for

  7. @for (item of items; track item.id) {
        <li>{{ item.name }}</li>
    }
    
    export class ListExampleComponent {
        items = [
            { id: 1, name: 'Angular' },
            { id: 2, name: 'React' },
            { id: 3, name: 'Vue' },
        ];
    }
    



  8. Using index, count, and even/odd values in @for

  9. <ul>
        @for (user of users; let i = $index; let count = $count; let even = $even) {
            <li [class.even]="even">{{ i + 1 }}/{{ count }}: {{ user }}</li>
        }
    </ul>
    
    export class UsersExampleComponent {
        users = ['Alice', 'Bob', 'Charlie'];
    }
    



  10. Switching between multiple options with @switch

  11. @switch (status) {
        @case ('loading') {
            <p>Loading...</p>
        }
        @case ('success') {
            <p>Data loaded successfully!</p>
        }
        @case ('error') {
            <p>Something went wrong.</p>
        }
        @default {
            <p>Idle state.</p>
        }
    }
    
    export class SwitchExampleComponent {
        status = 'loading';
    }
    



  12. Grouping elements with @let

  13. @let fullName = firstName + ' ' + lastName;
    <p>Hello, {{ fullName }}!</p>
    
    export class LetExampleComponent {
        firstName = 'Hwangfucius';
        lastName = 'Wang';
    }
    



  14. Older syntax: *ngIf, *ngFor, *ngSwitch

  15. <div *ngIf="loggedIn; else loggedOut">
        Welcome!
    </div>
    <ng-template #loggedOut>Please log in.</ng-template>
    
    <ul>
        <li *ngFor="let item of items; index as i">{{ i + 1 }} - {{ item }}</li>
    </ul>
    



  16. Combining control flow blocks

  17. @if (users.length > 0) {
        <ul>
            @for (user of users) {
                <li>{{ user }}</li>
            }
        </ul>
    } @else {
        <p>No users found.</p>
    }
    
    export class NestedExampleComponent {
        users: string[] = [];
    }
    




Pipes in Angular

  1. Introduction



  2. Basic syntax

  3. {{ value | pipeName }}

    {{ username | uppercase }}



  4. Chaining multiple pipes

  5. {{ birthday | date:'longDate' | uppercase }}



  6. Using pipe arguments

  7. {{ amount | currency:'EUR':'symbol':'1.2-2' }}
    export class PipeExampleComponent {
        amount = 1234.5;
    }
    



  8. Common built-in pipes

  9. Pipe Example Description
    uppercase {{ 'hello' | uppercase }} Converts text to uppercase.
    lowercase {{ 'HELLO' | lowercase }} Converts text to lowercase.
    titlecase {{ 'hello world' | titlecase }} Capitalizes the first letter of each word.
    date {{ today | date:'fullDate' }} Formats a Date object into a readable string.
    currency {{ price | currency:'USD' }} Formats numbers as currencies.
    percent {{ 0.25 | percent }} Converts a decimal to a percentage (e.g. 0.25 → 25%).
    number {{ 1234.567 | number:'1.0-1' }} Formats numbers with control over decimal places.
    json {{ obj | json }} Pretty-prints objects as JSON text (for debugging).
    slice {{ list | slice:1:3 }} Extracts part of an array or string (start:end).
    async {{ user$ | async }} Subscribes to an Observable or Promise and shows its value automatically.


  10. The async pipe (special case)

  11. <p>User: {{ user$ | async }}</p>
    
    import { Component } from '@angular/core';
    import { of } from 'rxjs';
    
    @Component({
        selector: 'app-async-demo',
        templateUrl: './async-demo.component.html',
    })
    export class AsyncDemoComponent {
        user$ = of('Hwangfucius');
    }
    



  12. Pure vs Impure pipes

  13. @Pipe({
        name: 'myPipe',
        pure: true, // default
    })
    export class MyPipe {
        transform(value: string) {
            return value.toUpperCase();
        }
    }
    



  14. Creating a custom pipe

  15. import { Pipe, PipeTransform } from '@angular/core';
    
    @Pipe({
        name: 'greet'
    })
    export class GreetPipe implements PipeTransform {
        transform(name: string): string {
            return `Hello, ${name}!`;
        }
    }
    
    {{ 'Hwangfucius' | greet }}



  16. Custom pipe with parameters

  17. @Pipe({ name: 'repeat' })
    export class RepeatPipe implements PipeTransform {
        transform(value: string, times: number = 2): string {
            return value.repeat(times);
        }
    }
    
    {{ 'Hi' | repeat:3 }}



  18. When to use pipes




Creating Custom Pipes in Angular

  1. Introduction



  2. Basic structure of a custom pipe

  3. import { Pipe, PipeTransform } from '@angular/core';
    
    @Pipe({
        name: 'example'  // The pipe name used in templates: {{ value | example }}
    })
    export class ExamplePipe implements PipeTransform {
        transform(value: any): any {
            // modify the value and return it
            return value;
        }
    }
    

    {{ 'Hello' | example }}



  4. Example: A "greet" pipe

  5. import { Pipe, PipeTransform } from '@angular/core';
    
    @Pipe({ name: 'greet' })
    export class GreetPipe implements PipeTransform {
        transform(name: string): string {
            return `Hello, ${name}!`;
        }
    }
    
    {{ 'Hwangfucius' | greet }}



  6. Passing parameters to your pipe

  7. @Pipe({ name: 'repeat' })
    export class RepeatPipe implements PipeTransform {
        transform(value: string, times: number = 2): string {
            return value.repeat(times);
        }
    }
    
    {{ 'Hi' | repeat:3 }}



  8. Example: Capitalize only the first letter

  9. @Pipe({ name: 'capitalize' })
    export class CapitalizePipe implements PipeTransform {
        transform(value: string): string {
            if (!value) return '';
            return value[0].toUpperCase() + value.slice(1).toLowerCase();
        }
    }
    
    {{ 'aNGuLAr' | capitalize }}



  10. Example: Filtering a list

  11. @Pipe({ name: 'filter' })
    export class FilterPipe implements PipeTransform {
        transform(items: string[], search: string): string[] {
            if (!items || !search) return items;
            const lower = search.toLowerCase();
            return items.filter(item => item.toLowerCase().includes(lower));
        }
    }
    
    <input [(ngModel)]="keyword" placeholder="Search..." />
    <ul>
        @for (name of (names | filter:keyword)) {
            <li>{{ name }}</li>
        }
    </ul>
    
    export class FilterExampleComponent {
        keyword = '';
        names = ['Alice', 'Bob', 'Charlie', 'David'];
    }
    



  12. Pure vs Impure custom pipes

  13. @Pipe({
        name: 'impureFilter',
        pure: false
    })
    export class ImpureFilterPipe implements PipeTransform {
        transform(items: string[], search: string): string[] {
            // runs on every change detection
            return items.filter(item => item.includes(search));
        }
    }
    



  14. Using custom pipes in standalone components

  15. @Component({
        selector: 'app-greet-demo',
        standalone: true,
        imports: [GreetPipe],
        template: `<p>{{ 'Hwangfucius' | greet }}</p>`
    })
    export class GreetDemoComponent {}
    



  16. Using custom pipes in NgModules (non-standalone apps)

  17. @NgModule({
        declarations: [CapitalizePipe],
        exports: [CapitalizePipe]
    })
    export class SharedPipesModule {}
    




Render Templates from a Parent Component with <ng-content> (sorted of repeated)

  1. Introduction



  2. Basic example

  3. @Component({
        selector: 'app-alert',
        template: `
            <div class="alert">
                <ng-content></ng-content>
            </div>
        `,
        styles: [`.alert { background: #ffe5e5; padding: 1rem; border-radius: 4px; }`],
        standalone: true,
    })
    export class AlertComponent {}
    
    <!-- Parent component template -->
    <app-alert>
        Something went wrong! Please try again.
    </app-alert>
    



  4. Projecting multiple content areas

  5. @Component({
        selector: 'app-card',
        template: `
            <div class="card">
                <div class="card-header">
                    <ng-content select="[card-title]"></ng-content>
                </div>
    
                <div class="card-body">
                    <ng-content></ng-content>
                </div>
            </div>
        `,
        styles: [`
            .card { border: 1px solid #ccc; border-radius: 8px; padding: 1rem; margin: .5rem 0; }
            .card-header { font-weight: bold; margin-bottom: .5rem; }
        `],
        standalone: true,
    })
    export class CardComponent {}
    
    <!-- Parent component -->
    <app-card>
        <span card-title>User Info</span>
        <p>Name: Alice</p>
        <p>Email: alice@example.com</p>
    </app-card>
    



  6. Default content fallback

  7. @Component({
        selector: 'app-message-box',
        template: `
            <div class="message-box">
                <ng-content>No message provided.</ng-content>
            </div>
        `,
        styles: [`.message-box { border: 1px dashed #888; padding: .5rem; }`],
        standalone: true,
    })
    export class MessageBoxComponent {}
    
    <app-message-box></app-message-box>
    <!-- Renders "No message provided." -->
    



  8. Projecting content dynamically

  9. @Component({
        selector: 'app-section',
        template: `
            <div class="section">
                @if (visible) {
                    <ng-content></ng-content>
                } @else {
                    <p>(Hidden)</p>
                }
            </div>
        `,
        standalone: true,
    })
    export class SectionComponent {
        visible = true;
    }
    



  10. Lifecycle of projected content

  11. export class CardComponent implements AfterContentInit, AfterContentChecked {
        ngAfterContentInit() {
            console.log('Projected content initialized');
        }
    
        ngAfterContentChecked() {
            console.log('Projected content checked');
        }
    }
    




Create Template Fragments with <ng-template>

  1. Introduction



  2. Basic example

  3. <ng-template>
        <p>This is hidden by default.</p>
    </ng-template>
    



  4. Using with *ngIf

  5. <div *ngIf="loggedIn; else loginPrompt">
        Welcome back, user!
    </div>
    
    <ng-template #loginPrompt>
        <p>Please log in to continue.</p>
    </ng-template>
    
    export class LoginExampleComponent {
        loggedIn = false;
    }
    



  6. Using with @for (or *ngFor)

  7. <ng-template #userTemplate let-name>
        <li>{{ name }}</li>
    </ng-template>
    
    <ul>
        @for (name of users) {
            <ng-container [ngTemplateOutlet]="userTemplate" [ngTemplateOutletContext]="{ $implicit: name }"></ng-container>
        }
    </ul>
    
    export class UserListComponent {
        users = ['Alice', 'Bob', 'Charlie'];
    }
    



  8. Rendering manually with ngTemplateOutlet

  9. <ng-template #alertTemplate>
        <div class="alert">This is an alert message!</div>
    </ng-template>
    
    <ng-container [ngTemplateOutlet]="alertTemplate"></ng-container>
    



  10. Passing data into a template

  11. <ng-template #greetTemplate let-person>
        <p>Hello, {{ person }}!</p>
    </ng-template>
    
    <ng-container
        [ngTemplateOutlet]="greetTemplate"
        [ngTemplateOutletContext]="{ person: 'Hwangfucius' }">
    </ng-container>
    



  12. Using multiple templates dynamically

  13. <ng-template #loading><p>Loading...</p></ng-template>
    <ng-template #success><p>Data loaded successfully!</p></ng-template>
    <ng-template #error><p>Something went wrong.</p></ng-template>
    
    <ng-container [ngTemplateOutlet]="currentTemplate"></ng-container>
    
    export class TemplateSwitcherComponent {
        state: 'loading' | 'success' | 'error' = 'loading';
    
        get currentTemplate() {
            if (this.state === 'success') return this.success;
            if (this.state === 'error') return this.error;
            return this.loading;
        }
    
        @ViewChild('loading', { static: true }) loading!: TemplateRef<any>;
        @ViewChild('success', { static: true }) success!: TemplateRef<any>;
        @ViewChild('error', { static: true }) error!: TemplateRef<any>;
    }