Update visibility of HTML element through directive

这一生的挚爱 提交于 2020-01-25 04:19:28

问题


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;
    }
  })
}
  1. 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.

  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!