3

It's a common pattern in Angular apps to display some data coming from Observable with ngIf directive and provide else template to show placeholder while data is loading.

<data-view *ngIf="data$ | async as data; else progress" [items]="data">
</data-view>

<ng-template #progress>
  <mat-icon></mat-icon>
  <mat-progress></mat-progress>
</ng-template>

However It requires it multiple repetition of else template, async pipe, and as clause. Is it possible to avoid this boilerplate all together with custom directive like this:

<data-view *ngWait="data$" items="data">
</data-view>

I understand how one can combine ngIf with async pipe, but I can't figure out how to embed else template into custom directive.

Roman Kolesnikov
  • 11,777
  • 11
  • 44
  • 67
  • I am not sure if this is possible using a directive as you might not be able to project the `else` template in place of the original component. However, have you explored creating a custom wrapper component. That way you can consume Observable via inputs and control the display of `else` template. Also using `ng-content` you can project the child component and pass the right data. This way the user of your component only has to worry about their implementation and not handling the `else` scenario. – ashish.gd May 12 '19 at 09:21
  • Sure, a wrapper is a simple way to go. However wrapper will make it harder to control layout and increase boilerplate in html. So I want to explore possibility to use directive for the purpose. – Roman Kolesnikov May 12 '19 at 09:27

1 Answers1

2

You can use the createComponent of ViewContainerRef inside your structural directives

@Directive({
  selector: "{Your-Selector}"
})
export class StructuralDirective implements OnInit {
  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef,
    private resolver: ComponentFactoryResolver
  ) {}

  @Input() {Your-Selector}: boolean;

  ngOnInit() {
    if (this.{Your-Selector}) {
      this.viewContainer.createEmbeddedView(this.templateRef);
    } else {
      let factory = this.resolver.resolveComponentFactory({Your component that holds default message});
      this.viewContainer.createComponent(factory);
    }
  }
}

You must also add {Your component that holds default message} inside your entry components.

Here is a simple demo at CodeSandbox that you can use as reference.