Pnpjs: How to set managed Metadata list item field?

Created on 8 Nov 2018  路  10Comments  路  Source: pnp/pnpjs

I want to set a field that is a managed Metadata type for a list item. I have a term set "foo" and a term "moo" , i want to set "moo" for the value of the field for the list item. How is this accomplished using the new libraries?
I only found information for working with the terms but not in combination with list items.
Doing this in CSOM is relatively easy. But i cant find any tutorials on how to do it with the new libraries.

code complete enhancement

Most helpful comment

We could add a helper method for this. Generally we don't add things until someone asks just to keep the workload manageable. The thing here that makes it difficult is the taxonomy stuff is in a separate library that works in an entirely different way than the core sp lib, and I don't want to take a dependency across them. So the helper method would require that you already had all the information such as term set id etc. We could then help format the request correctly. Would that be helpful?

All 10 comments

Hi @stwel

If we assume you know some things about the terms you want to add you can do like this:

Single Value Metadata field

// Setup data object.
const data = {};
data['FieldName] = { "__metadata": { "type": "SP.Taxonomy.TaxonomyFieldValue" },
                "Label": "TermName",
                'TermGuid': '354c6f93-8c41-4da7-8655-5c9471997ada', 
                'WssId': '4' // -1 works also
    };
const updated = await sp.web.lists.getByTitle('ListName').items.getById(1).update(data);
console.log(updated);

Multivalue metadata field

import { sp } from "@pnp/sp";

const list = await sp.web.lists.getByTitle('ListName');
// We get FieldName_0 to get the hidden related textfield
const field = await list.fields.getByTitle('FieldName_0').get();
// Setup data object with multiple values
const data = {};
data[field.InternalName] = "4;#Term2|354c6f93-8c41-4da7-8655-5c9471997ada;#3;#Term1|248e5d6a-6a3e-4292-a1d6-2632dd59a892;";

const updated = await list.items.getById(1).update(data);
console.log(updated);

Let me know if you need any more help!

@simonagren thanks, i already saw this way of doing it but was interested if there is a more modern way of doing it. Like passing the term from the set to a method in pnp or something like that.

@stwel Yeah there isn't as far as I know, since the PnPJs is for building functionality that reflects the API, not helper methods as far as I know. Maybe @patrick-rodgers could answer better.

But if you would want to just send in a term from the termset this could be done like this:

import {sp } from "@pnp/sp";
import { taxonomy, ITerm, ITermData } from "@pnp/sp-taxonomy";

public async doStuff(): Promise<void> {

    // Using taxonomy to get term set
    const store = await taxonomy.getDefaultSiteCollectionTermStore();
    const termset = await store.getTermSetById("10e3b8d1-edef-48ce-82f4-d184c5cd49b2");

    // get a single term
    const term = await termset.terms.getByName("Term2").get();

    // get all terms in termset
    const terms = await termset.terms.get();

    // Update single valued taxonomy field and log updated item
    console.log(await this.updateMeta(term, 'ListName', 'Meta', 1));

    // Update multi valued taxonomy field and log updated item
    console.log(await this.updateMultiMeta(terms, 'ListName', 'MultiMeta', 1));

  }

public cleanGuid(guid: string): string {
    if (guid !== undefined) {
        return guid.replace('/Guid(', '').replace('/', '').replace(')', '');
    } else {
        return '';
    }
}

public async updateMeta(term: (ITerm & ITermData), list: string, field: string, itemId: number): Promise<any> {
    const data = {};
    data[field] = {
      "__metadata": { "type": "SP.Taxonomy.TaxonomyFieldValue" },
      "Label": term.Name,
      'TermGuid': this.cleanGuid(term.Id),
      'WssId': '-1'
    };

    return await sp.web.lists.getByTitle(list).items.getById(itemId).update(data);

  }

  public async updateMultiMeta(terms: (ITerm & ITermData)[], listName: string, fieldName: string, itemId: number): Promise<any> {
    const data = {};

    const list = await sp.web.lists.getByTitle(listName);
    const field = await list.fields.getByTitle(`${fieldName}_0`).get();

    let termsString: string = '';
    terms.forEach(term => {
      termsString += `-1;#${term.Name}|${this.cleanGuid(term.Id)};#`;
    })

    data[field.InternalName] = termsString;

    return await list.items.getById(itemId).update(data);

  }

We could add a helper method for this. Generally we don't add things until someone asks just to keep the workload manageable. The thing here that makes it difficult is the taxonomy stuff is in a separate library that works in an entirely different way than the core sp lib, and I don't want to take a dependency across them. So the helper method would require that you already had all the information such as term set id etc. We could then help format the request correctly. Would that be helpful?

Just as a parenthesis, I updated the code example. I had forgotten to include some of the code in the example. So there's both an example of single term, and multi term.

We could add a helper method for this. Generally we don't add things until someone asks just to keep the workload manageable. The thing here that makes it difficult is the taxonomy stuff is in a separate library that works in an entirely different way than the core sp lib, and I don't want to take a dependency across them. So the helper method would require that you already had all the information such as term set id etc. We could then help format the request correctly. Would that be helpful?

@patrick-rodgers
This sound very good. Would be nice to have this feature.

Just for a quick example here is the equivalent in CSOM of what what we are talking about or at least close to. How to update a list item that has a terms field with multiple terms.

Field myField = myList.Fields.GetByInternalNameOrTitle("DemoField"); TaxonomyField myFieldTax = this.ctx.CastTo<TaxonomyField>(myField); myFieldTax.SetFieldValueByCollection(myListItem, myTermCollectionofTerms, 1033); myListItem.SystemUpdate(); this.ctx.executeQuery();

Added two helper methods, docs here

I would not recommend using the update API for setting managed metadata. As of today there seems to be an issue with using it. It will break all other metadata fields in the item from showing up in search. This is the case if you only update the value of a the changed managed metadata field and not providing all the other managed metadata fields in the update call even if they do not change.

Might be a search related issue. validateUpdateListItem API seems to work ok when providing only the updated fields.

Tested this with two different tenants and I'm creating a reproducable test case for microsoft. But would not recommend using the regular list item update REST api if you are not updating all managed metadata fields from the item at the same time even though not changing anything. Will break item searchability via managed metadata properties.

@patrick-rodgers Facing issue in saving multi-value managed metadata even with the logic described above "Updating the hidden field conatining ManagedMetadata field strimg value" using REST API/Pnp JS failed if field have space in display name while creation. This is a long time issue please help as i stucked with it for quite a long time.

Was this page helpful?
0 / 5 - 0 ratings