7

Data fields appear to be populating ok by using the suggested patch below, however, media fields are not populating.

Tried the following with no luck -

* http://localhost:1337/api/pages?populate=*
* {{protocol}}://{{host}}:{{port}}/api/pages?populate[Content][populate]=images

Reference - https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest/populating-fields.html#component-dynamic-zones

Example below is from - https://forum.strapi.io/t/strapi-v4-populate-media-and-dynamiczones-from-components/12670/9

Also tried this plugin (no luck) - https://www.npmjs.com/package/strapi-plugin-populate-deep

Page example below with dynamic zone -

enter image description here

/helpers/populate.js (this works for all data except images) -

const { createCoreController } = require("@strapi/strapi/lib/factories");

function populateAttribute({ components }) {
  if (components) {
    const populate = components.reduce((currentValue, current) => {
      return { ...currentValue, [current.split(".").pop()]: { populate: "*" } };
    }, {});
    return { populate };
  }
  return { populate: "*" };
}

const getPopulateFromSchema = function (schema) {
  return Object.keys(schema.attributes).reduce((currentValue, current) => {
    const attribute = schema.attributes[current];
    if (!["dynamiczone", "component"].includes(attribute.type)) {
      return currentValue;
    }
    return {
      ...currentValue,
      [current]: populateAttribute(attribute),
    };
  }, {});
};

function createPopulatedController(uid, schema) {
  return createCoreController(uid, () => {
    console.log(schema.collectionName, JSON.stringify(getPopulateFromSchema(schema)));
    return {
      async find(ctx) {
        ctx.query = {
          ...ctx.query,
          populate: getPopulateFromSchema(schema),
        //   populate: '*',
        };
        return await super.find(ctx);
      },
      async findOne(ctx) {
        ctx.query = {
          ...ctx.query,
          populate: getPopulateFromSchema(schema),
          // populate: '*',
        };
        return await super.findOne(ctx);
      },
    };
  });
}

module.exports = createPopulatedController;

/src/api/page/controllers/pages.js -

"use strict";

const collectionType = 'page'

const schema = require(`../content-types/${collectionType}/schema.json`);
const createPopulatedController = require("../../../helpers/populate");

module.exports = createPopulatedController(`api::${collectionType}.${collectionType}`, schema);

Response below images don't come through -

{
  "data": [
    {
      "id": 1,
      "attributes": {
        "title": "testing home page",
        "slug": "/",
        "publish_at": null,
        "createdAt": "2022-04-12T12:08:32.002Z",
        "updatedAt": "2022-04-12T15:07:11.990Z",
        "publishedAt": "2022-04-12T12:42:55.682Z",
        "locale": "en",
        "seoComponent": null,
        "block": [
          {
            "id": 1,
            "__component": "image.image-copy-full",
            "heading": "Delivering something amazing",
            "subHeading": "test sadasdf",
            "ctaButton": "test",
            "miscValues": {
              "testing": "object field"
            },
            "actionUrl": null,
            "isInternal": true,
            "isVisible": true
          },
          {
            "id": 1,
            "__component": "image.image-copy-chip",
            "heading": "A platform",
            "subHeading": "Allowing full integration",
            "ctaButton": null,
            "miscValues": null,
            "actionUrl": null,
            "isInternal": true,
            "isVisible": false
          },
          {
            "id": 1,
            "__component": "image.image-copy",
            "heading": "To transform our world",
            "subHeading": "In order to reach that scale",
            "ctaButton": null,
            "miscValues": null,
            "actionUrl": null,
            "isInternal": true
          }
        ]
      }
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 1
    }
  }
}

Schema sample for "ImageCopyFull" on screenshot -

{
  "collectionName": "components_image_image_copy_fulls",
  "info": {
    "displayName": "ImageCopyFull",
    "icon": "file-image",
    "description": ""
  },
  "options": {},
  "attributes": {
    "heading": {
      "type": "string"
    },
    "subHeading": {
      "type": "string"
    },
    "ctaButton": {
      "type": "string"
    },
    "miscValues": {
      "type": "json"
    },
    "actionUrl": {
      "type": "string"
    },
    "isInternal": {
      "type": "boolean",
      "default": true
    },
    "image": {
      "type": "media",
      "multiple": false,
      "required": true,
      "allowedTypes": [
        "images",
        "videos",
        "audios",
        "files"
      ]
    },
    "isVisible": {
      "type": "boolean",
      "default": false
    }
  }
}
Rodrigo Rubio
  • 1,686
  • 2
  • 16
  • 26

7 Answers7

11

You can make use of the Populate Deep plugin from the marketplace.

You simply have to install the package with:

yarn add strapi-plugin-populate-deep

Then use your populate parameter with the value of deep like so:

/api/articles/1?populate=deep
Rubek Joshi
  • 562
  • 7
  • 22
  • Thank you @Rubek, I did try using "strapi-plugin-populate-deep" but no dice at the time. – Rodrigo Rubio Jun 30 '22 at 00:06
  • 1
    Just an update. I have tried strapi-plugin-populate-deep on the latest v4 release and it works as expected. In my case it was "/populate=deep,3". It can go up to 10 deep. – Rodrigo Rubio Jul 22 '22 at 07:26
2

Found the answer under strapi github, shoutout to "Tomnovotny7", thank you man.

Copy the following code under your "page.js" -

const { isEmpty, merge } = require("lodash/fp");

const getModelPopulationAttributes = (model) => {
  if (model.uid === "plugin::upload.file") {
    const { related, ...attributes } = model.attributes;
    return attributes;
  }

  return model.attributes;
};

const getFullPopulateObject = (modelUid, maxDepth = 20) => {
  if (maxDepth <= 1) {
    return true;
  }
  if (modelUid === "admin::user") {
    return undefined;
  }

  const populate = {};
  const model = strapi.getModel(modelUid);
  for (const [key, value] of Object.entries(
    getModelPopulationAttributes(model)
  )) {
    if (value) {
      if (value.type === "component") {
        populate[key] = getFullPopulateObject(value.component, maxDepth - 1);
      } else if (value.type === "dynamiczone") {
        const dynamicPopulate = value.components.reduce((prev, cur) => {
          const curPopulate = getFullPopulateObject(cur, maxDepth - 1);
          return curPopulate === true ? prev : merge(prev, curPopulate);
        }, {});
        populate[key] = isEmpty(dynamicPopulate) ? true : dynamicPopulate;
      } else if (value.type === "relation") {
        const relationPopulate = getFullPopulateObject(
          value.target,
          maxDepth - 1
        );
        if (relationPopulate) {
          populate[key] = relationPopulate;
        }
      } else if (value.type === "media") {
        populate[key] = true;
      }
    }
  }
  return isEmpty(populate) ? true : { populate };
};

const modelUid = "api::page.page";

module.exports = createCoreController(modelUid, ({ strapi }) => ({
  async find(ctx) {
    const { query } = ctx;

    const { results, meta } = await strapi.service(modelUid).find({
      ...getFullPopulateObject(modelUid),
      ...query,
    });

    const sanitizedEntities = await this.sanitizeOutput(results, ctx);

    return {
      data: sanitizedEntities,
      meta,
    };
  },
}));
Rodrigo Rubio
  • 1,686
  • 2
  • 16
  • 26
1

This seem to work for me when getting dynamic components and components in Strapi v4, not sure if it is the "recommended way", 3 ways (1 and 2 are almost same)

1)

`${process.env.NEXT_PUBLIC_STRAPI_API}/pages?publicationState=live&populate[seo][populate]=%2A&populate[pageHeading][populate]=%2A&populate[socialMedia][populate]=%2A&populate[components][populate]=%2A&filters[slug]=${params.slug}`

Same as 1) except replace "%2A" with "*":

`${process.env.NEXT_PUBLIC_STRAPI_API}/pages?publicationState=live&populate[seo][populate]=*&populate[pageHeading][populate]=*&populate[socialMedia][populate]=*&populate[components][populate]=*&filters[slug]=${params.slug}`
  1. `${process.env.NEXT_PUBLIC_STRAPI_API}/pages?publicationState=live&filters[slug]=${params.slug}`
    

controller (src/api/page/controllers/page.js)

"use strict";

/**
 *  page controller
 */

const { createCoreController } = require("@strapi/strapi").factories;

module.exports = createCoreController("api::page.page", () => ({
  async find(ctx) {
    const populateList = [
      "seo",
      "pageHeading",
      "socialMedia",
      "components.image",
      "components.types",
    ];
    populateList.push(ctx.query.populate);
    ctx.query.populate = populateList.join(",");

    const content = await super.find(ctx);
    return content;
  },
}));

enter image description here

enter image description here

atazmin
  • 4,757
  • 1
  • 32
  • 23
0

Nuxt Strapi Module with "populate on" syntax

After digging into the Documentation and a lot of testing I found a solution that worked for me. I was able populate resources in dynamic content.

const response = await find('posts', {
  populate: {
    project: true,
    blocks: {
      on: {
        'shared.media': { populate: '*' },
        'shared.quote': { populate: '*' },
      },
    },
  },
});

This results to:

.../api/posts?populate[project]=true&populate[blocks][on][shared.media][populate]=%2A&populate[blocks][on][shared.quote][populate]=%2A

sevspo
  • 1
  • 3
0

This is an example of a response of the dynamic section (blocks) of a Content-Type:

"blocks": [
    {
        "id": 1,
        "__component": "shared.media",
        "file": {}

    },
    {
        "id": 1,
        "__component": "shared.slider",
        "files": {}

    },
    {
        "id": 1,
        "__component": "shared.rich-text",
        "body": "# Test"
    }
]

As you can see, in shared.media and shared.slider the attachments are in file and files respectively, which are not populated by default. This is the final request:

http://localhost:1337/api/articles?populate[0]=cover.attributes.&populate[1]=categories&populate[2]=blocks.file&populate[3]=blocks.files

You have to specify each field you want to populate, if you want to go deeper, just add more fields.

host4ideas
  • 11
  • 2
-1

The same situation, but for GraphQL… Is there any elastic way to request dynamic zones via Graphql without explicitly defining each of them in a query? For example, I DON’T want to do something like this:

`query Homepage {
  components {
... on SliderComponent {
      image
      text
    }
    ... on ParagraphComponent {
      title
      description
    }
    // and so on...
  }
}`

Instead, somehow, I’d like to be able to get all of the dynamic zones without querying them separately. So ideally would be something like this:

query Homepage { components } and that would return all of the possible dynamic zone components with their nested fields.

NOTE: I know that the queries above are not correct, but this is just an idea of the query shape.

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 02 '22 at 15:12
  • By design, GraphQL is there to serve specific fields. For example "select *" isn't recommended or possible. You might be better off creating a stored procedure with a custom endpoint. – Rodrigo Rubio Nov 15 '22 at 07:51