ng-brainstorm: content projection

What does "content" mean?

  1. DOM I control that gets "threaded" into the component's DOM at some location
  2. A template for content that I want the element to be able to render many times, at will
  3. DSL for configuration/inputs (<router><route ...><route ...></router>)
  4. Plain text to transform (<markdown>...</markdown>)

Custom Elements API for slots

<template> <slot name="user"> <p>Default content for the user</p> </slot> <slot name="address"> <!-- no default content provided --> </slot> </template> <!-- and in the consumer --> <some-element> <user-card slot="user"></user-card> <address slot="address"> <p>Line 1</p> <p>Line 2</p> </address> </some-element>

Thoughts, in no particular order

  • Explicit API - slots are always keyed by their name
    • This could get boilerplate-y
  • No obvious way to pass more than one element
  • Default content is interesting
  • Should have some way to mark a slot as "required"
  • Only solves case 1 above.

Angular Material: Data Table

A basic data table.

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> <!--- Note that these columns can be defined in any order. The actual rendered columns are set as a property on the row definition" --> <!-- Position Column --> <ng-container matColumnDef="position"> <th mat-header-cell *matHeaderCellDef> No. </th> <td mat-cell *matCellDef="let element"> {{element.position}} </td> </ng-container> <!-- Name Column --> <ng-container matColumnDef="name"> <th mat-header-cell *matHeaderCellDef> Name </th> <td mat-cell *matCellDef="let element"> {{element.name}} </td> </ng-container> <!-- Weight Column --> <ng-container matColumnDef="weight"> <th mat-header-cell *matHeaderCellDef> Weight </th> <td mat-cell *matCellDef="let element"> {{element.weight}} </td> </ng-container> <!-- Symbol Column --> <ng-container matColumnDef="symbol"> <th mat-header-cell *matHeaderCellDef> Symbol </th> <td mat-cell *matCellDef="let element"> {{element.symbol}} </td> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> </table>

Very structured, and yet super hard to understand still.

A silly straw man:

<mat-table [dataSource]="dataSource"> <mat-table-column> <th>No.</th> <td></td> </mat-table-column> </mat-table>

Notes from Pawel

  • static content projection - <ng-content>
  • "slot" vs css-selector
    • based on custom elements v0 spec
    • slots used by VueJS, Svelte, etc.
  • need to differentiate between what can be projected, and wrapping the projected node if present
<mat-card> <mat-card-actions slot="actions"> <button>Click me</button> </mat-card-actions> </mat-card>
<div *ngFor="let user of users" slot="user"> </div>
<div *ngForSlot="user"> <div class="wrapper"> <ng-content></ng-content> </div> </div>
<menu> <item></item> <item></item> <item></item> ----- <item></item> <item></item> </menu>
<div *ngFor="let item of data"> <ng-content [content]="item"></ng-content> <*item></*item> </div>
@Component({...})
class FooCmp {
  @Content('slotname', {multi: true})
  data: Content<?>[];
}
  • mental model
  • API to grab/insert
  • API to name things
<mat-card> <:header>text</:header> <:header> <span>Title</span> <span>Sub-title</span> </:header> </mat-card>
<div *ngIf="expr"> ... </div>

template + context

@Template(
template: '{{name}}'
)
export interface Foo {
name: string;
}

template + context = content
``
var Foo = makeTemplate<IFoo>('{{name}}');

@Component({

})
export class Foo {
@Input('name') name: IFoo['name'],
}

render(Foo, {name: 'Alex'});

Observable<Foo>.map(data => render(Foo, data)) => Observable<Content>

<ng-template-outlet [template]="Foo" [context]="{name: }">

Select a repo