angular2 detect changes in ng-content

Viewed 14933

Is there a way to detect changes in ng-content?

@Component({
    selector: 'example',
    template: `<ng-content></ng-content>`
})
export class Example {}

@Component({
    selector: 'test',
    template: `
        <example>
            <div *ngFor="let el of elements">{{el}}</div>
        </example>`
})
export class Test {
    elements = [1, 2, 3];

    ngOnInit() {
        setInterval(() => this.elements[0] += 10, 3000);
    }
}

I would like to get some information in Example class when my ng-content will change.

Here is plunker

6 Answers

The easiest solution is to use the cdkObserveContent directive. First, you must add to the imports of the module that owns the component ObserversModule

import {ObserversModule} from '@angular/cdk/observers';

@NgModule({
  imports: [
    /* ...other modules you use... */
    ObserversModule
  ],

/* ... */
})
export class MyModule { }

Then in your component's template:

<div (cdkObserveContent)="onContentChange($event)">
  <ng-content></ng-content>
</div>

The event will trigger each time the content inside changes somehow and the value passed to the function is an array with all the details about what changed. The value of the data that has changed can be found in target.data.

In your component.ts:

onContentChange(changes: MutationRecord[]) {
   // logs everything that changed
   changes.forEach(change => console.log(change.target.data));
}

Also, you can use as a service that gives you an Observable<MutationRecord[]>

To detect changes in ng-content assuming you're adding a ItemComponent.

The code would be:

import { QueryList, ContentChildren, AfterViewInit } from '@angular/core';

export class TestComponent implements AfterViewInit  {

    @ContentChildren(ItemComponent)
    public itemList:QueryList<ItemComponent>;


    public ngAfterViewInit() : void {
        this.itemList.changes.subscribe(() => {
            // put your logi here
        });
    }

}

Notes:
According the ContentChildren documentation you have to wait ngAfterViewInit to access the QueryList so you can not use it on ngOnInit.

See the QueryList documentation to see all the other properties that this class provides you.

I've come to a solution but I think this might not fill well for everyone needs. First of all treat nested components childs (after all, ng-content is nothing more than that) is very easy on React world and I think that it would be very nice to have a more robust solution for Angular too.

My solution is based on Material Angular Icon, the code you'll find here: https://github.com/angular/material2/blob/master/src/lib/icon/icon.ts

Following MatIcon approach, I wish to create an icon component where the name of desired icon is passed by ng-content like arrow-top.

Therefore I wish to observe whenever ng-content changes so that I can update the span classname.

The solutions is simply linking the data present into ng-content to an @Input followed by a getter.

Thus, whenever the ng-content changes Angular will update its component input too.

Hope this looks clear.

@Component({
 selector: 'fa-icons',
 template: `
 <span #templateRef class="template">
  <ng-content></ng-content>
 </span>

 <span [className]="iconName" [ngStyle]="ngStyle">
 </span>
 `,
 styles: [`
   :host > span.template {
     display: none;
   }
 `]
})
export class FaIconsComponent {
 @ViewChild('templateRef') templateRef: ElementRef;
 @Input() name: String;
 @Input() ngStyle: StyleSheetList;
 @Input()
 get iconName() {
   const text = this.templateRef.nativeElement.textContent.trim();
   return `fa-${text}`;
  }
}

If you are trying to complete something every time bound data updates you can use the ngOnChanges() lifecycle hook. Check out the docs, for more info.

Related