问题
I have a directive that shows or hides an HTML element StackBlitz :
<div *authorize="'A'">
Visible when letter is A
</div>
And the Authorize directive is:
@Directive({
selector: '[authorize]'
})
export class AuthorizeDirective implements OnInit {
letter: string;
@Input() set authorize(letter: string) {
this.letter = letter;
}
constructor(private element: ElementRef, private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef, private authorizationService: AuthorizationService) { }
ngOnInit() {
this.authorizationService.authorize(this.letter).subscribe(x => {
x ? this.viewContainer.createEmbeddedView(this.templateRef) : this.viewContainer.clear();
});
}
}
The authorization service and its authorize method is:
export class AuthorizationService {
private notifier: Subscription;
private data$: Observable<string[]>;
constructor(private noteService: NoteService) {
this.data$ = of(['A', 'B']);
this.data$.subscribe(x => console.log(x));
this.notifier = this.noteService.get().subscribe((code: number) => {
if (code == 0) {
this.data$ = of(['C', 'D']);
this.data$.subscribe(x => console.log(x));
}
});
}
authorize(letter: string) : Observable<boolean> {
return this.data$.pipe(
map(data => data.indexOf(letter) > -1)
);
}
}
On a real scenario data$
is obtained from API using HTTPClient.
And the NoteService is:
export class NoteService {
private subject = new Subject<number>();
send(code: number) {
this.subject.next(code);
}
clear() {
this.subject.next();
}
get(): Observable<number> {
return this.subject.asObservable();
}
}
When a note
with code 0 is emitted data$
is also updated ...
That should update the visibility of the elements that uses the directive.
On StackBlitz example by clicking the button the Div with C should appear.
But it is not doing that ... How to trigger that?
回答1:
fixed up a few things for you. The biggest issue was how you were setting up your mock auth service... it wasn't really getting the job done due to how observables work, if you use of
, that's a static observable, so you have no way of calling next on it and updating subscribers. you needed a static subject to leverage, like so:
private dataSource = new ReplaySubject<string[]>(1);
private data$: Observable<string[]> = this.dataSource.asObservable();
constructor(private noteService: NoteService) {
this.dataSource.next(['A','B']);
this.data$.subscribe(x => console.log(x)); // this will fire everytime next is called now
this.notifier = this.noteService.get().subscribe((code: number) => {
if (code == 0) {
this.dataSource.next(['C', 'D']);
}
});
}
this way you can call next and subscribers get the update. This fix alone will fix the problem.
But I also touched up your directive to allow the letter to change dynamically and be more efficient:
private hasView = false; // this variable will prevent unneeded template clearing / creating
private letterSource = new Subject<string>()
private sub: Subscription
@Input() set authorize(letter: string) {
this.letterSource.next(letter); // call next here on this subject to reauth if the letter input changes
}
constructor(private element: ElementRef, private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef, private authorizationService: AuthorizationService) {
this.sub = this.letterSource.pipe(
switchMap(letter => this.authorizationService.authorize(letter))
).subscribe(x => {
if (x && !this.hasView) { // only recreate the view if it's not already there
this.viewContainer.createEmbeddedView(this.templateRef)
this.hasView = true;
} else if (!x && this.hasView) { // only clear it if it is
this.viewContainer.clear();
this.hasView = false;
}
})
}
directives usually have an expectation that you can change their input and they will update accordingly, even if you don't anticipate it in this case, it's best to account for it.
preventing the view from clearing / recreating when not needed can actually have huge performance implications if this directive in used on an element that contains many sub components, which it very well may someday. Best to be as efficient as possible with a structural directive.
fixed blitz: https://stackblitz.com/edit/angular-wlvlkr?file=src%2Fapp%2Fauthorize.directive.ts
回答2:
Change data$ to behavior subject, when data$ is of()
it is not an hot observable and will only emit once when subscribed. Btw you should simplify the structure a bit. it is really no need for so many files
this.data$ = new BehaviorSubject(['A', 'B']);
this.data$.subscribe(x => console.log(x));
this.notifier = this.noteService.get().subscribe((code: number) => {
if (code == 0) {
this.data$.next(['C', 'D']);
this.data$.subscribe(x => console.warn(x));
}
来源:https://stackoverflow.com/questions/59211799/update-visibility-of-html-element-through-directive