Skip to content

Safely using Placeholders in an extension

Pat Miller edited this page Oct 19, 2018 · 3 revisions

There have been a bunch of reported issues with application customizers that use placeholders no longer working. From what we can tell, it looks like the source of the problem is with how the placeholders are created.

First, a bit of background (and apologies up front - we should have made this more explicit in our documentation):

Placeholders are a framework level "service" that the various applications (modern pages, SharePoint home, lists and libraries, etc.) can surface. They aren't necessarily required to (for example SharePoint home doesn't), which is why the method for getting a placeholder content can return undefined. It's also possible for placeholders to come and go depending on the page that a user is on. Lastly, it's also possible for the application to create / initialize the placeholders after the application customizer has been loaded.

The errors that have cropped up the past few days all seem to be caused by the latter case.

The anti-pattern is code that looks like this:

        private _topPlaceholder : PlaceholderContent;
        public onInit(void){
            this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(PlaceholderName.Top);
            this._topPlaceholder.domElement.innerText="Hello World";
        }

There are two main issues with this code. First, getting the placeholders directly in onInit, and second, accessing the placeholder without first checking that it was returned.

Here is what a simple view of the code should look like today:

private _topPlaceholder : PlaceholderContent;
public onInit(void){
    this.context.placeholderProvider.changedEvent.add(this, this._handlePlaceholderChange.bind(this));
}

private _handlePlaceholderChange(){
    if (!this._topPlaceholder)
    {
        // We don't have a placeholder populated yet.  Let's try and get it.
        this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(PlaceholderName.Top);
    } 
    
    if ( this._topPlaceholder )
    {
        this._topPlaceholder.innerText = 'Hello World!';
    }
}

Here is what the full robust version would look like:

private _topPlaceholder : PlaceholderContent;
public onInit(void){
    this.context.placeholderProvider.changedEvent.add(this, this._handlePlaceholderChange.bind(this));
}

private _handlePlaceholderChange(){
  if (!this._topPlaceholder)
  {
    // We don't have a placeholder populated yet.  Let's try and get it.
    this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(PlaceholderName.Top);
  } else {
    // We have a placeholder - let's make sure that it still exists.
    let index:number = this.context.placeholderProvider.placeholderNames.indexOf(PlaceholderName.Top);
    if ( index < 0)
    {
        // The placeholder is no longer here.
        this._topPlaceholder.dispose();
        this._topPlaceholder = undefined;
    }
  }
  if ( this._topPlaceholder )
  {
      this._topPlaceholder.innerText = 'Hello World!';
  }
}

We will continue to see how we can make this code easier for you to write, but for now this pattern is the safest thing (and it will continue to be the safest).

Clone this wiki locally