Reputation: 7620
In Angular 8, I am using a directive to read a value in the store, and not render div depending on the value :
Directive :
@Directive({
selector: '[appOperator]'
})
export class OperatorDirective implements OnInit, OnDestroy {
private isOperator = false;
private subscription: Subscription;
constructor(private elementRef: ElementRef,
private viewContainer: ViewContainerRef,
private templateRef: TemplateRef<any>,
private store$: Store<RootStoreState.IAppState>) { }
ngOnInit() {
this.subscription = this.store$.pipe(select(AuthStoreSelectors.isOperator)).subscribe((isOperator) => {
this.isOperator = isOperator;
this.setElementOperation();
});
}
setElementOperation(): void {
if (this.isOperator) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Use case :
<app-name-input
class="header_name"
[name]="waypoint.name"
(nameChange)="applyRename(waypoint, $event)"
*appOperator
></app-name-input>
I would like to have some sort of fallback template in case the original content is hidden. So I was trying to add a template fallback to the directive that would be displayed in this situation :
I added :
@Input() fallBackTemplateRef: TemplateRef<any>;
setElementOperation(): void {
if (this.isOperator) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
if(this.fallBackTemplateRef) {
this.viewContainer.createEmbeddedView(this.fallBackTemplateRef);
}
}
}
And I am trying to use it as follow
<ng-template #myTemplate let-waypoint="waypoint">
<div class="header_name">{{waypoint.name}}</div>
</ng-template>
<app-name-input
class="header_name"
[name]="waypoint.name"
(nameChange)="applyRename(waypoint, $event)"
appOperator
*appOperator
fallBackTemplateRef="myTemplate"
></app-name-input>
The problem I face is this compile but do not work in the app, fallBackTemplateRef
is always undefined and I can't nothing is drawn
Is it possible to achieve what I am trying to do ? what am I missing ?
Upvotes: 0
Views: 534
Reputation: 13515
In your version, you're not binding to fallbackTemplateRef
. And trying to use the [fallbackTemplateRef]
syntax would be attempting to bind to an input property on the component.
To bind to additional properties on a structural directive, you can take *ngFor
as an example.
*ngFor="let item of collection;trackBy: trackByFn"
In the source code, this is achieved by creating an input property prefixed with the directive selector:
@Input() set ngForTrackBy(fn: TrackByFn) {
this._trackByFn = fn;
}
DEMO: https://stackblitz.com/edit/router-template-vm1hsv
I have modified your directive to take an additional input property in the style of *ngForTrackBy
:
import { Directive, OnInit, OnDestroy, ElementRef, ViewContainerRef, TemplateRef, Input } from '@angular/core';
import { Subject, interval } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Directive({
selector: '[appOperator]'
})
export class OperatorDirective implements OnInit, OnDestroy {
constructor(private elementRef: ElementRef,
private viewContainer: ViewContainerRef,
private templateRef: TemplateRef<any>
) {
}
// dummy default input
@Input('appOperator') dummy: string;
@Input('appOperatorFallback') fallback: TemplateRef<any>;
private isOperator = false;
private destroyed: Subject<void> = new Subject<void>();
ngOnInit() {
interval(1000).pipe(
takeUntil(this.destroyed)
).subscribe(() => {
this.isOperator = !this.isOperator;
this.setElementOperation();
});
this.setElementOperation();
}
setElementOperation(): void {
this.viewContainer.clear();
if (this.isOperator) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else if(this.fallback) {
this.viewContainer.createEmbeddedView(this.fallback);
}
}
ngOnDestroy() {
this.destroyed.next();
this.destroyed.complete();
}
}
I used an rxjs interval
to allow me to create a working demo. You would replace this with your observable.
This is then used in the html like this:
<ng-template #myfallback>
<div>
FALLBACK
</div>
</ng-template>
<other *appOperator="'';fallback:myfallback">
</other>
Where <other>
is some other component.
The breakdown of '';fallback:myfallback
is this:
''
- this is the input for the @Input('appOperator')
dummy input property. It can be literally anything except blank. I chose an empty string, but it could also be an undeclared variable: _;fallback:myfallback
;
- input property separatorfallback:myfallback
is passing the template #myfallback
in to the @Input('appOperatorFallback') fallback: TemplateRef<any>;
propertyMy problem with this is that *appLoading="'';fallback:fallback"
is ugly. I chose to leave the default input as a dummy since it doesn't make sense given that the fallback isn't a primary input value for the operator directive.
In my research I couldn't find a way to specifiy additional inputs without specifying something in that initial spot.
If you were to only ever have one input and don't like the syntax I have reluctantly chosen, you could always pass the fallback in as the primary input:
@Input('appOperator') fallback: TemplateRef<any>;
And then it's simply a case of using it like this:
*appOperator="myfallback"
To me there is some cognitive dissonance with this, but at least it's pretty :)
Upvotes: 1