Advanced Patterns for Angular Components

- Hayden Braxton

 

Why Patterns?

A few notes ...

Inspiration?

Alternative solutions?

Overview

Overview

- Composition vs Inheritance

- Compound Components

- Semantic Inputs and Template Inputs

- Render Prop

- State Reducer

Composition vs Inheritance

Code Smell 👃💩



@Component({
    selector: 'this-is-painful',
    template: `
    <h3>too bad I don't inherit any template or styles from my parent</h3>
    <p>
        something from parent {{parentProperty}}.
        It's really clear where this data came from 🙃
    </p>
    <button (click)="parentHandler()">redundant plumbing</button>
    `
})
export class InheritanceComponent extends ParentComponent {
    constructor(
        private myDependency: CoolService,
        private anotherDep: AnotherService,
        private parentDep: DontKnowWhyService,
        private anotherForTheParent: SomeService
    ) {
        // hope i got the access modifiers right ...
        // hope I got these in the right order ...
        // I'm sure this is scalable ¯\_(ツ)_/¯
        super(parentDep, anotherForTheParent);
    }
}

Compound Compomnents

Code Smell 👃💩



@Component({
    selector: 'list-page',
    template: `
    <h1>List of Cool Things</h1>
    <cool-list-item
        *ngFor="let item of list; let i = $index;"
        [selected]="isSelected(i)"
        (click)="selectItem(i)"
        [item]="item"
    ></cool-list-item>
    `
})
export class MaybeShouldBeCompoundComponent {
    private selectedIndex: number;
    list = [...]; // or maybe I got this from a service
    isSelected = (index: number) => index === this.selectedIndex;
    selectItem = (index: number) => this.selectedIndex = index;
}

Semantic Inputs and Template Inputs

Code Smell 👃💩



@Component({
    selector: 'this-is-fine',
    template: `
    <h3>innerHtml all the things!</h3>
    <!-- too bad my styles dont apply to the innerHtml result -->
    <div
        class="message"
        [innerHtml]="someRichText"
    ></div>
    `,
    styles: [`.message p { color: orange }`]
})
export class RichTextComponent {
    @Input() someRichText = `
        <h1>default title</h1>
        <p>default paragraph that won't be orange</p>
    `;
}

Render Prop

Code Smell 👃💩



@Component({
    selector: 'one-size-fits-all',
    template: `
    <div [ngClass]="getClass()">
        <div
            *ngIf="configSwitch && configOption === 'option1'"
            (click)="doTheThing()"
        >display like this</div>
        <div
            *ngIf="configSwitch && configOption === 'option2'"
            (click)="doTheThing()"
        >display it different</div>
        <div
            *ngIf="!configSwitch"
            (click)="doTheThing()"
        >display another thing</div>
    </div>
    `,
    styles: [`.message p { color: orange }`]
})
export class DisplayManyCasesComponent {
    @Input() configOption: string;
    @Input() configSwitch: boolean;
    getClass = () => ({ [this.configOption]: true });
    doTheThing = () => {...}
}

State Reducer

Code Smell 👃💩



@Component({
    selector: 'doing-too-many-things',
    template: `<button (click)="doTheThing">Do the thing</button>`,
})
export class DoingTooManyThingsComponent {
    @Input() configOption: string;
    @Input() configSwitch: boolean;

    doTheThing = () => {
        if (configSwitch && configOption === 'option1') {
            // do it like this
        } else if (configSwitch && configOption === 'option2') {
            // do it like that
        } else if (!configSwitch) {
            // handle a different case
        } else {
            // this is very scalable ¯\_(ツ)_/¯
        }
    }
}

Thanks!

Resources

Made with Slides.com