1

I have an Angular's 7 component template as follows:

<div class="measure">
  <ng-content select="[label]"></ng-content>
  <div class="chart">
    <div class="state" [style.width.%]="progress">&nbsp;</div>
  </div>
  <ng-content select="[value]"></ng-content>
</div>

As you can see progress is a variable in that component:

export class ProgressComponent implements OnInit {

  @Input() current: any;
  @Input() maximum: any;
  @Input() minimum: any;

  progress: number;

  ngOnInit() {
    // Some code
  }
}

Is there a way to use the variable progress when using the component:

<mk-progress [minimum]="0" [maximum]="100" [current]="50" class="test">

  <span label>Label</span>

  <span value>{{progress}}%</span>

</mk-progress>

I am trying to use it in the code line:

 <span value>{{progress}}%</span>
1
  • You already have the progress variable available within the ProgressComponent. Why do you want to project it back into component? If you do want it, you can simply emit the value out and project it back in. Commented Jan 29, 2019 at 11:21

1 Answer 1

1

This is not possible using <ng-content>. However, you can achieve it by projecting templates instead of regular content.

Your component will then render these templates instead of ng-content via <ng-container>'s marked with an ngTemplateOutlet and set a context for these templates.

By using structural directives (adding a * in front of your directive), you are effectively wrapping your injected content with templates.

By using @ContentChild in your component, you can then reference these templates to instantiate them.

In order to access the template context when using your component, you can use the let keyword on the template to alias the implicit context and then reference it.

<div class="measure">
    <ng-container *ngTemplateOutlet="labelTemplate; context: templateContext"></ng-container>
    <div class="chart">
        <div class="state" [style.width.%]="progress">&nbsp;</div>
    </div>
    <ng-container *ngTemplateOutlet="valueTemplate; context: templateContext"></ng-container>
</div>
export class ProgressComponent implements OnInit {
    @Input() current: any;
    @Input() maximum: any;
    @Input() minimum: any;

    @ContentChild(LabelDirective, { read: TemplateRef }) labelTemplate;
    @ContentChild(ValueDirective, { read: TemplateRef }) valueTemplate;

    progress: number;

    templateContext = { $implicit: this };

    ngOnInit() {
        // Some code
    }
}
<mk-progress [minimum]="0" [maximum]="100" [current]="50" class="test">
  <span *label>Label</span>
  <span *value="let item">{{ item.progress }}%</span>
</mk-progress>

This technique also allows you to provide default content if the templates are not provided:

<ng-container *ngTemplateOutlet="valueTemplate ? valueTemplate : defaultTemplate; context: templateContext">
</ng-container>
<ng-template #defaultTemplate>
  default thing to show
</ng-template>

See this article for more details.

Sign up to request clarification or add additional context in comments.

3 Comments

I tried your code and checked the link you posted but it is not working. I created an example in stackblitz.com/edit/mk-angular-progress. Am I missing something?
You didn't declare the directives in the AppModule. It does work with the declarations, I wish angular would give us warnings for these errors, it took me a while to find it!
Thank you. I was trying to find the error and didn't notice that the directive were missing in module. I also wish angular could warn this situations.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.