天道酬勤,学无止境

How to observe input element changes in ng-content

Question

How to call parent component's function when child component observed input changes?

The below is HTML structure.

# app.comopnent.html
<form>
  <textbox>
    <input type="text">
  </textbox>
</form>

# textbox.component.html
<div class="textbox-wrapper">
  <ng-content>
</div>

Restrictions are like following.

  • TextboxComponent has ng-content and need to project input element to it.
  • Emit an event in TextboxComponent when input element is inputted something.
  • Don't wanna make input element to have more attributes, e.g. <input type="text" (input)="event()">.

I was writing code, but cannot find a solution...

# input.directive.ts
@Directive({ selector: 'input', ... })
export class InputDirective {
  ngOnChanges(): void {
    // ngOnChanges() can observe only properties defined from @Input Decorator...
  }
}

# textbox.component.ts
@Component({ selector: 'textbox', ... })
export class TextboxComponent {
  @ContentChildren(InputDirective) inputs: QueryList<InputDirective>;
  ngAfterContentInit(): void {
    this.inputs.changes.subscribe((): void => {
      // QueryList has a changes property, it can observe changes when the component add/remove.
      // But cannot observe input changes...
    });
  }
}
Answer1

The input event is bubbling and can be listened on the parent component

<div class="textbox-wrapper" (input)="inputChanged($event)">
  <ng-content></ng-content>
</div> 

Plunker example

Answer2

In ngAfterViewInit(), find the element(s) of interest, then imperatively add event listener(s). The code below assumes only one input:

@Component({
    selector: 'textbox',
    template: `<h3>textbox value: {{inputValue}}</h3>
      <div class="textbox-wrapper">
        <ng-content></ng-content>
      </div>`,
})
export class TextboxComp {
  inputValue:string;
  removeListenerFunc: Function;
  constructor(private _elRef:ElementRef, private _renderer:Renderer) {}
  ngAfterContentInit() {
    let inputElement = this._elRef.nativeElement.querySelector('input');
    this.removeListenerFunc = this._renderer.listen(
      inputElement, 'input', 
      event => this.inputValue = event.target.value)
  }
  ngOnDestroy() {
    this.removeListenerFunc();
  }
}

Plunker

This answer is essentially an imperative approach, in contrast to Günter's declarative approach. This approach may be easier to extend if you have multiple inputs.


There doesn't seem to be a way to use @ContentChild() (or @ContentChildren()) to find DOM elements in the user-supplied template (i.e, the ng-content content)... something like @ContentChild(input) doesn't seem to exist. Hence the reason I use querySelector().

In this blog post, http://angularjs.blogspot.co.at/2016/04/5-rookie-mistakes-to-avoid-with-angular.html, Kara suggests defining a Directive (say InputItem) with an input selector and then using @ContentChildren(InputItem) inputs: QueryList<InputItem>;. Then we don't need to use querySelector(). However, I don't particularly like this approach because the user of the TextboxComponent has to somehow know to also include InputItem in the directives array (I guess some component documentation could solve the problem, but I'm still not a fan). Here's a plunker for this approach.

Answer3

You could use the following scenario, wich has hostbinding property on input directive

input.directive.ts

import {Directive, HostBinding} from 'angular2/core';
import {Observer} from 'rxjs/Observer';
import {Observable} from 'rxjs/Observable';

@Directive({
  selector: 'input',
  host: {'(input)': 'onInput($event)'}
})
export class InputDirective {
  inputChange$: Observable<string>;
  private _observer: Observer<any>;
  constructor() {
    this.inputChange$ = new Observable(observer => this._observer = observer);
  }
  onInput(event) {
    this._observer.next(event.target.value);
  }
}

And then your TextBoxComponent will subscribe on Observable object that defined above in InputDirective class.

textbox.component.ts

import {Component, ContentChildren,QueryList} from 'angular2/core';
import {InputDirective} from './input.directive';
@Component({
  selector: 'textbox',
  template: `
    <div class="textbox-wrapper">
      <ng-content></ng-content>
      <div *ngFor="#change of changes">
        {{change}}
      </div>
    </div>
  `
})
export class TextboxComponent {
  private changes: Array<string> = [];
  @ContentChildren(InputDirective) inputs: QueryList<InputDirective>;

  onChange(value, index) {
    this.changes.push(`input${index}: ${value}`);
  }

  ngAfterContentInit(): void {
    this.inputs.toArray().forEach((input, index) => {
      input.inputChange$.subscribe(value => this.onChange(value, index + 1));
    });
  }
}

Here's plunker sample

Answer4

You can use Angular CDK observers https://material.angular.io/cdk/observers/api

import this module in your module

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

then use in ng-content's parent element

<div class="projected-content-wrapper (cdkObserveContent)="contentChanged()">
 <ng-content></ng-content>
</div>
Answer5

You could add an ngControl on your input element.

<form>
  <textbox>
    <input ngControl="test"/>
  </textbox>
</form>

This way you would be able to use NgControl within ContentChild. It tiges access to the valueChanges property you can register on to be notified of updates.

@Component({ selector: 'textbox', ... })
export class TextboxComponent {
  @ContentChild(NgControl) input: NgControl;
  ngAfterContentInit(): void {
    this.input.control.valueChanges.subscribe((): void => {
      (...)
    });
  }
}
标签

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.

相关推荐
  • Angular2+ interview questions collection
    1. Life Cycle Hook The sequence of the life cycle is shown in the figure below: ngOnChanges : Triggered when the input property of the component data binding changes, this method receives a SimpleChanges object, including the current value and the previous property value. The first call must occur before ngOnInit. It is worth noting that this method is only triggered when the object's reference changes. ngOninit : Initialization instruction or component, called after Angular displays the binding properties of the display component for the first time, this method will only be called once
  • Conditional duplicate templateref in ng-content with selector
    Question I have a component which toggles the component's template based on client device size. Component code is: import {Component} from '@angular/core'; import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout'; @Component({ selector: 'ui-switcher', template: ` <ng-content *ngIf="isSmall" select="mobile"></ng-content> <ng-content *ngIf="!isSmall" select="web"></ng-content> ` }) export class UiSwitcherComponent { public isSmall: boolean; constructor(breakpointObserver: BreakpointObserver) { breakpointObserver.observe([Breakpoints.Small, Breakpoints.XSmall]).subscribe(result => {
  • In Angular 2 how to check whether <ng-content> is empty?
    Question Suppose I have a component: @Component({ selector: 'MyContainer', template: ` <div class="container"> <!-- some html skipped --> <ng-content></ng-content> <span *ngIf="????">Display this if ng-content is empty!</span> <!-- some html skipped --> </div>` }) export class MyContainer { } Now, I would like to display some default content if <ng-content> for this component is empty. Is there an easy way to do this without accessing the DOM directly? Answer1 Wrap ng-content in an HTML element like a div to get a local reference to it, then bind the ngIf expression to ref.children.length == 0
  • How to detect async change to ng-content
    Question I made a component which uses the marked package to render markdown content, the thing is it doesn't re-render itself when an async event changes its ng-content element. Here's the code import {Component, ElementRef, AfterContentInit} from 'angular2/core'; declare var marked: any; @Component({ selector: 'markdown', template: '<div style="display:none;">' + ' <ng-content></ng-content>' + '</div>' + '<div class="markdown" [innerHTML]="output"></div>' }) export class MarkdownComponent implements AfterContentInit { output: string; constructor( private element: ElementRef) { }
  • angular 2 access ng-content within component
    Question How can I access the "content" of a component from within the component class itself? I would like to do something like this: <upper>my text to transform to upper case</upper> How can I get the content or the upper tag within my component like I would use @Input for attributes? @Component({ selector: 'upper', template: `<ng-content></ng-content>` }) export class UpperComponent { @Input content: String; } PS: I know I could use pipes for the upper case transformation, this is only an example, I don't want to create an upper component, just know how to access the component's content
  • angular (four) parent-child component communication publish and subscribe, other summary
    参考链接:https://blog.csdn.net/qq_40677590/article/details/103733672?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-2-103733672.nonecase 1. Grammar: { {}} Interpolation syntax? Safe navigation operator, when undefined or null is blank, the page will not report an error! Non-empty assertion, confirm that it will not be undefined and null Built-in template function $any() eliminates special types of errors 2. Template syntax [] Property binding () Event binding [( )] Two-way binding ngIf(let of) ngFor ngSwitch>ngSwitchCase,ngSwitchDefault 3. Two-way data
  • ng-content select bound variable
    Question I'm trying to create a form builder using angular 2. An very basic example is as follows: this.fields = [{name: 'Name', type: 'text'}, {name: 'Age', type: 'number'}]; But I also want to support custom elements like: this.fields = [ {name: 'Name', type: text}, {name: 'Age', type: 'custom', customid: 'Ctl1'}, {name: 'Whatever', type: 'custom', customid: 'Ctl2'} ]; // template: <super-form [fields]="fields"> <Ctl1><input type="number" ...><Ctl1> <Ctl2><whaterver-control ...><Ctl2> </super-form> In my form builder component I then have something like: <div *ngFor="let f of fields"> <div
  • Introduction to Angualr Part One
    Angualr 1. Directory structure "Assets": [store static resource directory "src/favicon.ico", "src/assets"] "Index": "src/index.html", single page, package compilation result export "Main": "src/main.ts", the entry for the entire modular system to start. Its function is to load the root module and start the execution module system, which is the program entry for the entire system. package.json: The project package description file. There is a scripts field in the file. The left side is the simple command, and the right side is the equivalent execution program. That is, entering npm run start
  • Angular notes_7
    Angular notes_7 instruction Components: instructions with templates Structural directives: change the structure of the host document, such as ngIf, ngFor, ngSwitch Attribute directives: change host behavior, such as ngClass, ngStyle, ngModel // 创建一个属性指令 import { Directive, ElementRef, Renderer2, OnInit, Input } from '@angular/core'; @Directive({ selector: 'div[appTest]', // div 宿主类型,需要添加 [] 定义为宿主的属性 }) export class AppTestDirective implements OnInit { @Input() appTest = '0'; @Input() height = '0'; constructor(private elr: ElementRef, private rd2: Renderer2) {} ngOnInit(): void { // 应该在初始化方法内传值
  • Common 'wrapper' component for ngx-datatable
    Question Some introduction: We are currently developing an application based on Angular2 that is quite data-heavy. In order to show this data, we decided to give ngx-datatables a try. Plenty of components will be needed showing data in grids. We added a customized footer template as well as a kind of customized header showing a page size selector using a <select> element. The number of markup lines grew quite a lot, therefore we would like to move the definition <ngx-datatable> with header and footer to a separate grid component. Now we would like to reuse that component by allowing the
  • Component transclude with inline template
    Question I'm using Angular 2-rc3 and have a Component and I want to apply transclusion, just in a bit of a different way. Here's my component: import { Component, Input } from '@angular/core'; @Component({ selector: 'my-list', template: `<ul> <li *ngFor="let item of data"> -- insert template here -- <ng-content></ng-content> </li> </ul>` }) export class MyListComponent { @Input() data: any[]; } And I use it like this: <my-list [data]="cars"> <div>{{item.make | uppercase}}</div> </my-list> As you can see, I'm trying to define an inline template that will be used by my component. Now this goes
  • Angular2 child component as data
    Question Let's say I have two components: parent and child. The HTML would look like this: <parent title="Welcome"> <child name="Chris">Blue Team</child> <child name="Tom">Red Team</child> </parent> The final output would look like: <h1>Welcome</h2> <ul> <li><b>Chris</b> is on the Blue Team</li> <li><b>Tom</b> is on the Red Team</li> </ul> The parent component: @Component({ selector: 'parent', directives: [ChildComponent], // needed? template: ` <h1>{{title}}</h1> <ul> <li *ngFor="#child of children()">{{child.content}}<li> </ul>` }) export class ParentComponent { @Input() title; children() {
  • How to render multiple ng-content inside an ngFor loop using Angular 4?
    Question I'm trying to display two differents ng-content inside two ngFor loops. But as described in this answer, Angular can't project ng-content multiple times. I tried this solution, but this does not allow me to project multiple times my content, only to display it at different places depending on an ngIf condition. Here's my actual code : An example parent component (any component using app-table). That's what I want to do. <app-table [data]="myData"> <div lineHeader let-line> {{line.name}} </div> <div lineContent let-element> {{element.name}} </div> </app-table> The child component (app
  • Angular2: Change detection timing for an auto-scroll directive
    Question I've been working on a simple auto-scroll directive for chat-display: @Directive({ selector: "[autoScroll]" }) export class AutoScroll { @Input() inScrollHeight; @Input() inClientHeight; @HostBinding("scrollTop") outScrollTop; ngOnChanges(changes: {[propName: string]: SimpleChange}) { if (changes["inScrollHeight"] || changes["inClientHeight"]) { this.scroll(); } }; scroll() { this.outScrollTop = this.inScrollHeight - this.inClientHeight; }; } This directive will work when I've set enableProdMode() and when the ChangeDetectionStrategy is set to default, but when in "dev mode" I get an
  • Q: How to use Angular 2 template form with ng-content?
    Question Is it not possible to have form input elements within an ng-content and have that "connect" to the ngForm instance of the parent component? Take this basic template for a parent component: <form (ngSubmit)="onSubmit(editForm)" #editForm="ngForm" novalidate> <ng-content></ng-content> <button type="submit">Submit</button> </form> Then inside the child component, which is put inside "ng-content", something like this: <input type="text" [(ngModel)]="user.firstName" #firstName="ngModel" name="firstName" required minlength="2"> On submit of the parent form, the child controls are not
  • How to make a Modal reusable in Angular 2?
    Question Reusability is very important when programming, and anything we can do to reduce duplication of code is going to help us out. I have to use Modal popups to display information to users in many places in my Angular 2 project. I am using ng-bootstrap and all of these Modals have the same Header and Footer but body changes in many cases. Sometimes the body just wanted to replace a single place holder, at other times it has some complexity to prepare the dynamic content. And these are triggered or managed by different components. ng-bootstrap allow us to pass content into a Modal in two
  • Angular2 Dart - Get Text inside Angular2 Component
    Question I've got an item component which I use inside other components, the item component typically looks like this: <item type="the-type" text="the-text"></item> and is defined like this: @Component( selector: 'item', template: '...') class Item { @Input() String text = "text-goes-here"; @Input() String type = "type-goes-here"; } Now I want to change the item so that it has a more natural feel to it: <item type="the-type">the-text</item> Now I want to get the text the-text from this component. I change the item component to implement AfterContentInit to see if I can somehow capture the text
  • How can I solve the same issue in Angular, that ng-messages solved in AngularJS?
    Question In AngularJS there was a form directive named ng-messages which helped us to make it so that not all form errors showed at the same time. So that for example if an input has 3 errors: required, minlength, maxlength. Then only required shows up, after required is valid, then minlength shows up. Without ng-messages we would need to do some really complex and ugly logic in order to just show required and not the rest while also taking into consideration that errors should only be shown if the form control is also dirty/touched and not valid. In AngularJS this would be something like:
  • Angular how to get the multiple checkbox values?
    Question I m using a checkbox in Angular to select more than one element and I'm trying to get the value of that checkbox for submitting. Instead I'm getting the value those values as an array of true-s. That's what I've tried: export class CreateSessionComponent implements OnInit { form : FormGroup ; constructor(private formBuilder: FormBuilder) {} ngOnInit() { this.form = this.formBuilder.group({ userdata : new FormArray([ new FormControl('', Validators.required) ]) }) } } userdata is a dynamic array populated from a database. <div formArrayName="useremail; let k = index"> <div *ngFor="let
  • Angular learning (two) [components]
    Component The component in the front end is a combination of a bunch of code files in order to realize the same business logic. Putting all the related files in the same directory forms a very independent component. Angular uses the Web Component standard to achieve componentization. A component is the smallest logical unit of an Angular application, and a module is a layer of abstraction above the component. Components and other parts, such as instructions, pipelines, services, routes, etc., can be included in a module, and a series of encapsulated functions can be used externally by