Enhance the visibility of an HTML element using a directive

A specific directive I've created has the ability to display or hide an HTML element. You can view the code on StackBlitz:

<div *authorize="'A'">
  This will be visible only for letter A
</div>

Here is the Authorize directive implementation:

@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 necessary authentication service includes the method:

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)
    );

  }

}

In real-life situations, the data$ would come from an API using HTTPClient.

As for the NoteService:

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, it triggers an update in the data$...

This should dynamically change the visibility of elements utilizing the directive.

In the StackBlitz example, you'll notice that by clicking a button, the div containing C should appear.

However, this functionality seems to not be working as expected. How can we trigger it successfully?

Answer №1

After making some adjustments for you, the main issue was how the mock auth service was set up... it wasn't effectively accomplishing the task due to the nature of observables. By using of, which creates a static observable, you cannot call next on it to update subscribers. What you needed was a static subject to handle this, 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)); // now this will trigger whenever next is called

  this.notifier = this.noteService.get().subscribe((code: number) => { 
    if (code == 0) {
      this.dataSource.next(['C', 'D']);
    }
  });
}     

This adjustment allows you to update subscribers when calling next. Simply implementing this fix should resolve the issue.

In addition, I refined your directive to enable dynamic letter changes and enhance efficiency:

private hasView = false; // prevents unnecessary template clearing / creating
private letterSource = new Subject<string>()
private sub: Subscription
@Input() set authorize(letter: string) {
  this.letterSource.next(letter); // trigger reauthorization if the input letter 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) { // recreate view only if not already present
      this.viewContainer.createEmbeddedView(this.templateRef)
      this.hasView = true;
    } else if (!x && this.hasView) { // clear view only if necessary
      this.viewContainer.clear();
      this.hasView = false;
    }
  })
}
  1. Directives typically expect that their inputs can be changed and updated accordingly, even if not initially anticipated. It's best to account for potential changes.

  2. Avoiding unnecessary clearing or recreating of views can greatly impact performance, especially if this directive is used in an element with many sub-components. It's crucial to prioritize efficiency with a structural directive.

Updated blitz: https://stackblitz.com/edit/angular-wlvlkr?file=src%2Fapp%2Fauthorize.directive.ts

Answer №2

Replace data$ with a behavior subject instead of using of(). This way, it will not be a hot observable and will emit only once when subscribed to. Also, consider simplifying the file structure as there might be unnecessary 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)); 
  }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

correct usage of getServerSideProps with Typescript in a next.js project using i18n

I'm encountering challenges with integrating next-i18next into a Typescript NextJS project. There are very few recent examples available for reference. I have successfully set up internationalized routing, but I am facing difficulties in configuring i ...

Storing information from a signup form using Angular

Can you help with my registration form? <div class="form-group"> <label for="email" class="col-sm-3 control-label">Email Address</label> <div class="col-sm-9"> <input type="email" id="email" placeholder="Enter your ...

Understanding the integration of jQuery html attributes with Angular2

I am encountering an issue with a library that requires me to include the tag below in my image: data-zoom-image When I add this tag to my image: <img class="larger-picture" [src]="'images/'+item.picture" align="middle" data-zoom-image="&ap ...

Angular 2+ encountering an internal server error (500) while executing an http.post request

Here is my service function: public postDetails(Details): Observable<any> { let cpHeaders = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: cpHeaders }); return this.htt ...

Having Trouble with Angular Route (6) Configuration

I've been experiencing some unusual issues with the Angular 6 router, but this particular one is really frustrating. Here are the routes that I have defined in routing-app.module.ts: export const routes: Routes = [ { path: 'login&a ...

Display the autocomplete dropdown in Angular 2+ only once the user has typed at least one letter

Looking to create a customized autocomplete feature using Angular 2+ and angular material design, where the options are only displayed after at least one letter has been typed. The default behavior of the autocomplete is to toggle on focus even when the i ...

Develop a fresh Typescript-driven sql.js database

I'm in the process of converting my JavaScript code to TypeScript. One of the libraries I rely on is sql.js. I have successfully installed the corresponding typing for it, but I am facing a roadblock when it comes to creating the database. Here is ho ...

Setting up the Angular 2 router to function from the /src subfolder

My goal is to create two separate subfolders within my project: src and dist. Here are the key elements of my application: root folder: C:\Server\htdocs\ app folder: C:\Server\htdocs\src index.html contains <base href="/ ...

Export both the enum and default function in the Typescript declaration for uuidv5

My goal is to create a Typescript Declaration for uuidv5, my first declaration for a 3rd party module. The structure of the module is unfamiliar to me, as it looks like this: function uuidToString(uuid) { } function uuidFromString(uuid) { } function cre ...

Exploring the functionality of multiple checkboxes in Next.js 14, the zod library, shadcn/ui components, and react-hook

I'm currently working on a form for a client where one of the questions requires the user to select checkboxes (or multiple checkboxes). I'm still learning about zod's schema so I'm facing some challenges in implementing this feature. I ...

Presenting a hierarchical JSON structure from a RESTful API in tabular form

I'm struggling to showcase nested JSON data retrieved from my backend server in a table format. Below is the JSON structure: { "id": 1, "personalData": { "firstName": "Bob", "lastName": "Bobby", "personalIdNumber": 852963 }, "ema ...

Tips for broadcasting a router event

Currently, I am working with 2 modules - one being the sidenav module where I can select menus and the other is the content module which contains a router-outlet. I am looking for the best way to display components in the content module based on menu selec ...

The Raycaster fails to identify objects that have been rotated

I have recently started working with Three.js and I'm facing an issue with raycasting not detecting certain parts of a rotated mesh object. For instance, in the image below, when the mouse is at the position of the green circle, it's detected as ...

Direct to another route in Angular 2 if the user is already authenticated

Looking at my route setup, it's structured like this: const routes: Routes = [ { path: '', redirectTo: '/login', pathMatch: 'full' }, { path: 'login', component: LoginComponent }, { path: 'dashboard& ...

Only numerical values are permitted, and numbers may not commence with 0 in Angular 4

Hey there, I'm new to Angular 4 and in need of help validating a form field. I want to allow only numbers, but the first digit should not be 0. I've tried different solutions that I found, but none of them worked for me. Could someone please as ...

Priority of Search Results in Ionic Search Bar

I have an array of items that I'll refer to as fruitArray. fruitArray = ['Orange', 'Banana', 'Pear', 'Tomato', 'Grape', 'Apple', 'Cherries', 'Cranberries', 'Raspberr ...

What is the process for importing a submodule from a private package?

I'm currently working on a private npm package in TypeScript that needs to be reused in multiple TS projects. To streamline this process, I created and published the @myorg/core package, containing enums and types that are shared across different repo ...

TypeScript does not verify keys within array objects

I am dealing with an issue where my TypeScript does not flag errors when I break an object in an array. The column object is being used for a Knex query. type Test = { id: string; startDate: string; percentDebitCard: number, } const column = { ...

`The term 'promise' is typically used to describe a type, yet in this context, it is being utilized as a value.`

I attempted to employ a promise for an Async call in my custom form validator, so I created a separate TypeScript file called usernameValidators.ts. import { Control } from 'angular2/common'; export class UsernameValidators { static should ...

Configuring dependencies in Ionic2 and Angular2 for seamless integration

I need to configure a global configuration file or from app.ts. We want to pass configuration settings that can be automatically used in our dependency injection system. Service for Api.ts import {Injectable} from 'angular2/core'; import {Http ...