Angular (2+) Component providing a binding to projected Content

Viewed 620

Given a root component without properties, this root component template would give an error:

<div>My name is {{foo}}.</div>

I want to create a component (FooProvider) which projects its content, but furthermore provides a binding for 'foo', such that I can write in my root component:

<foo-provider>
    <div>My name is {{foo}}.</div>
</foo-provider>

In other words: how can I put bindings into <ng-content> ?

Update:

Trying this as a structural directive also does not work. What is wrong here? (Angular 5.2.11)

This the root component:

<ng-container *foo-provider>
    <div>foo = {{foo}}</div>
</ng-container>

This is the FooProviderDirective:

import {Directive, TemplateRef, ViewContainerRef} from '@angular/core';

interface FooContext {
  foo: string;
}

@Directive({
  selector: '[foo-provider]'
})
export class FooProviderDirective {
  constructor(
    private readonly viewRef: ViewContainerRef,
    private readonly templateRef: TemplateRef<FooContext>
  ) {
    const context: FooContext = { foo: 'baz' };
    this.viewRef.createEmbeddedView(this.templateRef, context);
  }
}
2 Answers

You can't. Content projection is about the wrapper and managing where content is inserted, not in providing values for the inserted content. This is because content projection allows ANY content to be inserted and it does not make sense to provide default values since the values that may be provided in the inserted content are totally unknown.

If you want to avoid defining foo in the root component then define it in a separate <foo> component like so:

<foo-provider>
  <foo [fooValue]="'Fred'"></foo>
</foo-provider>

Where <foo> looks like this:

<div>my name is {{ fooValue }}</div>

and the <foo> .ts controller could provide a default value where no value is given.

Also, whilst this github post does not address the same issue it does provide a hack to use default content where no content is provided and may prove interesting to you:

https://github.com/angular/angular/issues/12530

I would say it's possible.

Having structural directive like:

@Directive({
  selector: '[foo-provider]'
})
export class FooProviderDirective {
  constructor(
    private readonly viewRef: ViewContainerRef,
    private readonly templateRef: TemplateRef<any>
  ) {
    const context = { $implicit: 'baz' };
    this.viewRef.createEmbeddedView(this.templateRef, context);
  }
}

You should be able to write something like:

<div *foo-provider="let foo">
  foo = {{ foo }}
</div>

Ng-run Example

Related