How to provide data to ComponentPortal using Dependency injection

Some time ago I published a video that in detail showed how to use the Angular CDK Portal module and in which use cases it might be useful. Almost immediately in the comments, I got a question: “How to provide data to the ComponentPortal factory?”. Fair enough! This is indeed a good question and in this article, I am going to give you an answer.

By the way, here is the video I mentioned and I would encourage you to watch it first if you are not familiar with the Portal module yet and also because I will be using the project from this video as a basis.

I hope you enjoyed the video and now let’s get back to our problem – providing data to ComponentPortal!

Clone project

Because I will be using an already existing project we need to clone it from GitHub, so in your Terminal run these commands:

git clone https://github.com/DMezhenskyi/angular-cdk-portal-example.git
cd angular-cdk-portal-example/
git checkout provide-data-to-component-portal
git checkout -b my-solution 7ae90c8cf2f2addcd8316d37431f7dee97cea3e
code .

Here we do the next: 1) Clone the project from GitHub. 2) Navigate to the project folder. 3) Switch to the “provide-data-to-component-portal” branch. 4) Create a new branch “my-solution” from a specific commit that contains the initial application state. 5) Open the project with VS Code.

Once you are in VS Code open the Terminal, install node dependencies and start the app locally. Here are commands:

npm install
npm run start

Cool! Now we are ready to go πŸ’ͺ

Let’s get started with a solution

First, I would suggest you have a look at which params ComponentPortal takes, just go to the users-page.component.ts file and hover over ComponentPortal and you should see something like this:

Params that take the ComponentPortal class constructor

As you can see we do not have anything like “inputs”, however, as the 3rd parameter we have an Injector… It actually gives us a hint that we could use Dependency Injection to solve our problem. How exactly are we going to do it? Actually, I am going to create a separate injector. Let’s try it out…

@Component({
  selector: 'app-users-page',
  templateUrl: './users-page.component.html',
  styleUrls: ['./users-page.component.scss'],
})
export class UsersPageComponent implements OnInit, OnDestroy {
  //...
  constructor(private portalBridge: PortalBridgeService) {}

  ngOnInit(): void {
    const portalInjector = Injector.create(); // <-- Created Injector
    this.portalContent = new ComponentPortal<ActionsButtonsComponent>(
      ActionsButtonsComponent
    );
    this.portalBridge.setPortal(compPortal); 
  }
  //...
}

Not a big deal as you can see but our Injector is a bit useless because it doesn’t provide any value, so let’s try to modify it by adding some Dependency Provider. First, I would suggest you create an injection token for it. Just create a separate file anywhere in your project and paste there next code:

import { InjectionToken } from '@angular/core';

export const DATA_TOKEN = new InjectionToken<string>('portal-data');

Awesome! Now we can get back to the users-page.component.ts file and modify our Injector by providing a DATA_TOKEN and provide just some string as a value of this token

const portalInjector = Injector.create({
  providers: [{ provide: DATA_TOKEN, useValue: 'Hello from Portal' }],
});

And then we are going to provide this configured Injector to ComponentPortal as 3rd param. Hint: pay attention that we are not interested in viewContainerRef which goes as the 2nd argument, so we place null instead.

this.portalContent = new ComponentPortal<ActionsButtonsComponent>(
  ActionsButtonsComponent,
  null, // it is viewContainerRef which is optional
  portalInjector
);

Great! Now the only thing we have to do is to go to ActionsButtonsComponent and inject DATA_TOKEN as any other token and read data from it.

export class ActionsButtonsComponent implements OnInit {
  // This is how we inject tokens
  constructor(@Inject(DATA_TOKEN) private data: string) {

  ngOnInit(): void {
    console.log(this.data);
  }
}

Now we can navigate to http://localhost:4200/users and here we go πŸŽ‰πŸŽ‰πŸŽ‰

Note: if you get an error that “async pipe was not found” then just restart “npm run start”.
Link to the source code of the final version please find it here.

Of course, you are not restricted by only string values. You can use any object, class, or service. Also when you create your custom injector you can provide a parent injector as a second argument if it is necessary for your use case. If you are curious why providing a Parent Injector might be important – please check my video about Dependency Injection and how it works.

I hope this article helped you! Do not forget also to subscribe to my YouTube channel and check out my Video Courses made with love for you! πŸ™‚ Thank you for your attention!

Advanced Angular Forms – Deep Dive


Check out the most Advanced Angular Forms course made by Google Developer Expert in Angular

About the author

Dmytro Mezhenskyi

Google Developer Expert in Angular | YouTube Content Creator | Instructor in Web Development

2 Comments

  • Hi Dmytro! Great explains for Angular CDK Portal, it’s very usefull! I’m using ComponentPortal to open some app details in a new window, but I’m facing some issues, like:

    – a button tooltip on mouse over in this new window, for example, is being show in main/parent window;
    – some components that was not yet rendered in main/parent windows, is losing their css styles is this new window;

    Did you face some issue like this?
    I promisse you a beer or coffee for this help! πŸ™‚

    Thank you in advance!

    • Hi Daniel,
      Thanks for your comment! I think I could help you more if you could provide a reproducible demo like Stackblitz, etc πŸ™‚ Or at least a GIF that demonstrates the bug.

      Regards,
      Dmytro Mezhenskyi

Tags

Dmytro Mezhenskyi

Google Developer Expert in Angular | YouTube Content Creator | Instructor in Web Development

Get in touch