Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How can I make parts of a reusable component's template optional in a Vue 3 app?

I have been working on an SPA with Vue 3, TypeScript and The Movie Database (TMDB) API.

I have a movie card component (MovieCard.vue) that I use in (at least) 2 diffrent contexts and I want it to behave slightly difrentlly from one context to another.

<template>
  <div class="movie card" @click="showDetails(movie.id)">
    <div class="thumbnail">
      <img
        :src="movieCardImage"
        :alt="movie.title"
        class="img-fluid"
      />
    </div>

    <div class="card-content">
      <h2 class="card-title">{{ movie.title }}</h2>
      <p class="card-desc">{{ movie.overview }}</p>
      <span :title="`Score: ${movie.vote_average}`" class="score">{{ movie.vote_average}}</span>
    </div>

    <div class="card-footer">
      <p class="m-0 release">
        Release date: {{ dateTime(movie.release_date) }}
      </p>
      <p v-if="movieGenres" class="m-0 pt-1">
        <span class="genre" v-for="genre in movieGenres" :key="genre.id">
          {{ genre.name }}
        </span>
      </p>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import moment from "moment";

export default defineComponent({
  name: "MovieCard",

  props: {
    movie: {
      type: Object,
      required: true,
    },

    genres: {
      type: Object,
      required: false,
    },
  },

  data: () => ({
    genericCardImage: require("../assets/generic-card-image.png"),
  }),

  methods: {
    dateTime(value: any) {
      return moment(value).format("MMMM DD, YYYY");
    },

    showDetails(movie_id: any) {
      let movie_url = `movie/${movie_id}`;
      window.open(movie_url, "_self");
    },
  },

  computed: {
    movieCardImage() {
      return !this.movie?.backdrop_path
        ? this.genericCardImage
        : `https://image.tmdb.org/t/p/w500/${this.movie?.backdrop_path}`;
    },

    movieGenres() {
      if (this.genres) {
        let genres = this.genres;
        return this.movie?.genre_ids
          .filter((genre_id: number) => genre_id in genres)
          .map((genre_id: number) => genres[genre_id])
          .filter((genre: { name: string }) => genre.name.length > 0);
      } else {
        return [];
      }
    },
  },
});
</script>

The first context is the MoviesList.vue component

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

<template>
  <div class="row list">
    <div
      v-for="movie in movies"
      :key="movie.id"
      class="col-xs-12 col-sm-6 col-lg-4 col-xl-3"
    >
      <MovieCard :movie="movie" :genres="genres" />
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import axios from "axios";
import env from "../env";
import MovieCard from "./MovieCard.vue";

export default defineComponent({
  name: "MoviesList",
  components: { MovieCard },

  props: {
    listType: {
      type: String,
      required: true,
    },
  },

  data: () => ({
    searchTerm: "",
    movies: [],
    genres: [],
  }),

  mounted() {
    this.listMovies();
    this.getGenres();
  },

  methods: {
    listMovies() {
      axios
        .get(
          `${env.api_url}/movie/${this.$props.listType}?api_key=${env.api_key}`
        )
        .then((response) => {
          this.movies = response.data.results;
        })
        .catch((err) => console.log(err));
    },

    getGenres() {
      axios
        .get(`${env.api_url}/genre/movie/list?api_key=${env.api_key}`)
        .then((response) => {
          this.genres = response.data.genres;
        })
        .catch((err) => console.log(err));
    },
  },
});
</script>

The result:

enter image description here

The second context is the ActorDetails.vue component, where I want it displayed without the votes average "bubble":

<div v-if="knownFor" class="movies">
    <h2 class="section-title">Known For</h2>
    <div class="row list">
        <div
            v-for="item in knownFor"
            :key="item.id"
            class="col-xs-12 col-sm-6 col-lg-4 col-xl-3"
        >
            <MovieCard :movie="item" />
        </div>
    </div>
</div>

The desired result:

enter image description here

Stackblitz

You can see a Stackblitz HERE.

The goal

I want to make this part of the movie card component optional:

<span :title="`Score: ${movie.vote_average}`" class="score">{{ movie.vote_average}}</span>

I have tried the "obvious" way, in the ActorDetails component, but it does not work (due to scoped styles, which I prefer to keep):

.score {
    display: none !important;
}

What is an easy and reliable way to achieve this goal?

>Solution :

Someone more experienced can maybe post a better answer but i’ll give it a go.
If i understood your issue correctly is you want to show the rating of each movie only when user is on "general" movies list and hide it when user is on the actor’s one.
You can add another prop to the movieCard component as a Boolean and pass it as true/false depending if you want the card to have a rating or not.

<span v-if="showRating" :title="`Score: ${movie.vote_average}`" class="score d-flex">{{movie.vote_average}}</span>

and on props

showRating: {
   type: Boolean,
   default: false,
},

and then on MoviesList component:

<MovieCard :showRating="true" :movie="movie" :genres="genres" />
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading