通过指令更新 HTML 元素的可见性 [英] Update visibility of HTML element through directive

查看:26
本文介绍了通过指令更新 HTML 元素的可见性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个显示或隐藏 HTML 元素的指令 StackBlitz :

字母为 A 时可见

授权指令是:

@Directive({选择器:'[授权]'})导出类 AuthorizeDirective 实现 OnInit {字母:字符串;@Input() 设置授权(字母:字符串){this.letter = 字母;}构造函数(私有元素:ElementRef,私有模板引用:TemplateRef,私有视图容器:ViewContainerRef,私有授权服务:AuthorizationService){}ngOnInit() {this.authorizationService.authorize(this.letter).subscribe(x => {X ?this.viewContainer.createEmbeddedView(this.templateRef) : this.viewContainer.clear();});}}

授权服务及其授权方法为:

导出类 AuthorizationService {私人通知:订阅;私有数据$:Observable;构造函数(私人笔记服务:NoteService){this.data$ = of(['A', 'B']);this.data$.subscribe(x => console.log(x));this.notifier = this.noteService.get().subscribe((code: number) => {如果(代码== 0){this.data$ = of(['C', 'D']);this.data$.subscribe(x => console.log(x));}});}授权(字母:字符串):Observable{返回 this.data$.pipe(地图(数据 => data.indexOf(字母)> -1));}}

在实际场景中,data$ 是使用 HTTPClient 从 API 获取的.

NoteService 是:

导出类 NoteService {私人科目=新科目<编号>();发送(代码:数字){this.subject.next(代码);}清除() {this.subject.next();}get(): Observable{返回 this.subject.asObservable();}}

当发出带有代码 0 的 notedata$ 也会更新 ...

这应该更新使用指令的元素的可见性.

在 StackBlitz 示例中,通过单击按钮,应该会出现带有 C 的 Div.

但它并没有这样做......如何触发?

解决方案

为您解决了一些问题.最大的问题是你如何设置你的模拟身份验证服务......由于可观察的工作方式,它并没有真正完成工作,如果你使用 of,那是一个静态的 observable,所以你无法调用 next 并更新订阅者.你需要一个静态主题来利用,就像这样:

private dataSource = new ReplaySubject(1);私有数据$:Observable= this.dataSource.asObservable();构造函数(私人笔记服务:NoteService){this.dataSource.next(['A','B']);this.data$.subscribe(x => console.log(x));//每次 next 被调用时都会触发this.notifier = this.noteService.get().subscribe((code: number) => {如果(代码== 0){this.dataSource.next(['C', 'D']);}});}

通过这种方式,您可以拨打 next 电话,订阅者会收到更新.仅此修复即可解决问题.

但我也修改了你的指令,允许动态更改信件并提高效率:

private hasView = false;//此变量将防止不需要的模板清除/创建private letterSource = new Subject()私人订阅:订阅@Input() 设置授权(字母:字符串){this.letterSource.next(letter);//如果字母输入发生变化,则在此主题上调用 next 以重新验证}构造函数(私有元素:ElementRef,私有模板引用:TemplateRef,私有视图容器:ViewContainerRef,私有授权服务:AuthorizationService){this.sub = this.letterSource.pipe(switchMap(letter => this.authorizationService.authorize(letter))). 订阅(x => {if (x && !this.hasView) {//只有在视图不存在时才重新创建视图this.viewContainer.createEmbeddedView(this.templateRef)this.hasView = true;} else if (!x && this.hasView) {//只有在它是时才清除它this.viewContainer.clear();this.hasView = false;}})}

  1. 指令通常期望您可以更改他们的输入并且它们会相应地更新,即使在这种情况下您没有预料到,最好考虑到这一点.

  2. 如果该指令用于包含许多子组件的元素,那么在不需要时阻止视图清除/重新创建实际上可能会产生巨大的性能影响,这很可能有一天会发生.最好使用结构指令尽可能高效.

修复闪电战:https://stackblitz.com/edit/angular-wlvlkr?file=src%2Fapp%2Fauthorize.directive.ts

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?

解决方案

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

这篇关于通过指令更新 HTML 元素的可见性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆