Search code examples
angularresources

angular multiple resources with single namesource suggestion


I am wondering this is the right way to do this way or not (I am new to resource/rxresource): this is my templateUrl:

<div class="container">
  <table class="table table-bordered">
    <thead>
      <tr>
        <th>Id</th>
        <th>title</th>
        <th>param</th>
      </tr>
    </thead>
    <tbody>
      @for(p of data(); track p.id){
      <tr>
        <td class="text-start">{{p.id}}</td>
        <td class="text-start">{{p.title}}</td>
        <td class="text-start">{{p.param}}</td>
      </tr>
      }
    </tbody>
  </table>
  <button (click)="buttonVClick()" class="btn btn-primary">Video</button>
  <button (click)="buttonMClick()"class="btn btn-info">Movie</button>
</div>

and this is my component.ts

export class ResourceComponent extends ResourceOptionComponent implements OnInit {
  public service = inject(DataService);
  videoBool: boolean = false;
  ngOnInit() {
    this.videoBool = true;
    this.service.apiURL.set("json/menus/videos/menu.json")
  }
  constructor() {
    super();
  }
  //data = computed(() => {
  //  return this.service.dataResource.value()
  //});
  buttonVClick(): void {
    this.service.apiURL.set("json/menus/videos/menu.json")
  }
  buttonMClick(): void {
    this.service.apiURL.set("json/menus/movies/menu.json")
  }
  //dataExt = computed(() => { return super.data() }
  //dataExt2 = computed(() => { return this.basedata() }

}

my data service:

export class DataService {
  public apiURL = signal<any>('');
  dataResource = resource(
    {
      request: () => ({
        apiUrl: this.apiURL()
      }),
      loader: async ({ request }) =>
        fetch(request.apiUrl)
          .then(
            (res) => {
              return res.json() as Promise<any[]>
            }
          )
    }
  )
}

my extend class and extend class...

export class ResourceOptionComponent extends ResourceMovieComponent {

  override dataResource = resource<Video[], any>(
    {
      loader: async () => fetch("json/menus/videos/menu.json")
        .then(
          (res) => {
            return res.json() as Promise<Video[]>
          }
        )
    }
  );
  constructor() {
    super();
  }
  public data(): any {
    this.dataResource;
  }
}

another way to do it is :

data = computed(() => {
  if (this.videoBool) {
    return this.service.dataResource.value()
  }
  else {
      return this.dataResource.value()
    }
  }
)

My problem with extend class is that I can a child by using 'super'. However, it seems that it doesn't work with grandchild (super.super) level - it may be I don' know how.

The logic with the 2 buttons I DON'T LIKE is that I have to pass url to the loader which is not good clean way to do it.

My goal from this logic is that I would like to have one signal data name (data()) used in my templateUrl and datasource can be video or movie or arhieved video or movie etc (just change the datasource and the html still remain the same).

The non-signal way is very simple. Just pass subscription data to datasource and it is all done.

Any other way to do it? Thank you


Solution

  • You can first setup an enum which contains the different modes.

    export enum RESOURCE_MODES {
      VIDEO = 'video',
      MOVIE = 'movie',
      ARCHIVED = 'archived',
    }
    

    Then we setup a signal that toggles between the modes, you can have this at the service level or at the component level, does not matter, the URL is a derived state from the mode, so we use computed to get it, then finally resource fetches the data.

    export class DataService {
      public mode = signal<any>(RESOURCE_MODES.VIDEO);
    
      apiurl = computed(() => {
        switch (this.mode()) {
          case RESOURCE_MODES.ARCHIVED:
            return 'json/menus/videos/menu.json';
          case RESOURCE_MODES.MOVIE:
            return 'json/menus/movies/menu.json';
          default:
            return 'json/menus/archived/menu.json';
        }
      });
    
      dataResource = resource({
        request: () => ({
          apiUrl: this.apiurl(),
        }),
        loader: async ({ request }) =>
          fetch(request.apiUrl).then((res) => {
            return res.json() as Promise<any[]>;
          }),
      });
    }
    

    This can be imported on the component and we define data which is a computed, which simply fetches the value. The button clicks set the service mode to work on, again you can do this at the component level also.

    @Component({
      selector: 'app-root',
      template: `
        <div class="container">
          <table class="table table-bordered">
            <thead>
              <tr>
                <th>Id</th>
                <th>title</th>
                <th>param</th>
              </tr>
            </thead>
            <tbody>
              @for(p of data(); track p.id){
              <tr>
                <td class="text-start">{{p.id}}</td>
                <td class="text-start">{{p.title}}</td>
                <td class="text-start">{{p.param}}</td>
              </tr>
              }
            </tbody>
          </table>
          <button (click)="buttonVClick()" class="btn btn-primary">Video</button>
          <button (click)="buttonMClick()"class="btn btn-info">Movie</button>
        </div>
      `,
    })
    export class App {
      public service = inject(DataService);
      data = computed(() => this.service.dataResource.value());
    
      buttonVClick(): void {
        this.service.mode.set(RESOURCE_MODES.VIDEO);
      }
    
      buttonMClick(): void {
        this.service.mode.set(RESOURCE_MODES.MOVIE);
      }
    }