0

I've created a structural directive to show loader when there is no data, however I cannot make "as" syntax work (i.e. asyncData$ | async as data).

I've created a StackBlitz example.

Here when you click on button it sends data to BehaviorSubject.

If I use ngIf it works but not with my own directive.

<pre *loader="name | async as b">
  !{{ b | json }}!
</pre>

<hr>
ngIf

<pre *ngIf="name | async as b">
  !{{ b | json }}!
</pre>

<br>

<button (click)="handler()">Load data</button>

enter image description here


This question being considered as "off-topic" whilst it's not.

I've created a sample code. It doesn't work, while showing my attempt to solve the issue. The issue is fully connected to the code itself.

Sergey
  • 7,184
  • 13
  • 42
  • 85
  • You have created a loader component, not a custom structural directive. Check how to create custom directive here - https://stackoverflow.com/questions/34613065/what-is-the-difference-between-component-and-directive/34616190 – Sayan Aug 28 '19 at 11:02
  • You may find this article useful as well - http://avenshteinohad.blogspot.com/2018/06/custom-ngif-directive.html, https://blog.angularindepth.com/exploring-angular-dom-abstractions-80b3ebcfc02 – Sayan Aug 28 '19 at 11:09
  • @SayanSamanta OP created a valid structural directive. The only missing thing was assigning the context variables. – Harun Yilmaz Aug 28 '19 at 12:19

1 Answers1

2

You need to pass context to view when using

this.vc.createEmbeddedView(this.templateRef);

In order to do it, you can define a context variable inside the directive:

@Directive({
  selector: '[loader]'
})
export class LoaderDirective {
    constructor(
        private readonly templateRef: TemplateRef<any>,
        private readonly vc: ViewContainerRef,
        private readonly resolver: ComponentFactoryResolver
    ) {
    }

    context: any = {};

    /** True - контент, false - лоадер */
    @Input('loader')
    set show(c: boolean) {
        if (c) {
            this.vc.clear();
            this.context.$implicit = this.context.loader = c;
            this.vc.createEmbeddedView(this.templateRef, this.context);
        } else {
            this.vc.clear();
            const comp = this.vc.createComponent(this.factory);
        }
    }

    private get factory() {
        return this.resolver.resolveComponentFactory(LoaderComponent);
    }
}

Note that $implicit and loader properties of context is assigned separately. This means you can pass any property with context to the view.

Hope this helps.

Harun Yilmaz
  • 8,281
  • 3
  • 24
  • 35
  • Here's the working [stackblitz project](https://stackblitz.com/edit/angular-gjrz2a) – Harun Yilmaz Aug 28 '19 at 11:20
  • My apologies, I missed `this.context.$implicit = this.context.loader = c;`. With this part it works. Why is it required to create an input mirrored context property? – Sergey Aug 28 '19 at 11:23
  • No worries :) As stated [here](https://angular.io/guide/structural-directives#microsyntax), it is the microsyntax of structural directives. `$implicit` means the default value. When you pass anything with `context`, you can access them inside the template with the given name. – Harun Yilmaz Aug 28 '19 at 11:27
  • However, when I simply pass input to the `$implicit` it doesn't work. Only when I pass it to the `loader` property. Somehow it drastically differs and setting values to `$implicit` may be wrong (because it seems like they can overlap). – Sergey Aug 28 '19 at 11:31
  • I checked it again. You are right. It can cause an overlap. I currently use this solution in my project. So I too had better have another look at this issue :) If I find something, I will add it here as a comment. – Harun Yilmaz Aug 28 '19 at 11:50
  • It would be great. It isn't obvious that you should make this assignment in order to make template variables work though. – Sergey Aug 28 '19 at 12:15