Josh Bavari's Thoughts

Thoughts on technology and philosophy

Angular 2 and Ng-model

about a 2 minute read

Angular 2 introduces ng-model from Angular 1 in a completely different manner. Due to this, I wanted to make a quick post on how to use Angular 2’s ng-model to build components that alert its parents app/component of changes.

I’m going to use the Ionic 2 conference app as an example.

In this post, we’ll look at the schedule page in the app and see how it uses the ion-search-bar to update its searchQuery to filter out sessions from the schedule when the user changes the search input.

The set up

On the schedule component, we set up the search query as a simple string, as such: this.searchQuery = '';.

Then in our schedule page template, we tell the ion-search-bar to use the ng-model directive and tell it to two-way bind using the schedule component’s searchQuery variable.

The template is like this:

1
<ion-search-bar [(ng-model)]="searchQuery" placeholder="Search"></ion-search-bar>

Now, in the search bar, we need to take that searchQuery as an ngModel, and ensure the search-bar has a value accessor implemented, so that we may tell the schedule component of when things change to update its shadow DOM if need be.

The ion-search-bar will take an ngControl as part of it’s injection, and sets up the value accessor to itself, like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
constructor(
  elementRef: ElementRef,
  config: Config,
  ngControl: NgControl,
  renderer: Renderer
) {
  super(elementRef, config);
  this.renderer = renderer;
  this.elementRef = elementRef;
  if(!ngControl) {
    // They don't want to do anything that works, so we won't do anything that breaks
    return;
  }

  this.ngControl = ngControl;
  this.ngControl.valueAccessor = this;
}

NOTE: ngModel extends the ngControl class in angular (source code). The valueAccessor is a ControlValueAccessor is an interface that provides certain methods, like so:

1
2
3
4
5
export interface ControlValueAccessor {
  writeValue(obj: any): void;
  registerOnChange(fn: any): void;
  registerOnTouched(fn: any): void;
}

The ControlValueAccessor gives us a method to write the new value, a method to register to listen to the changes, and the register on touched function to allow components to use.

Those are implemented in the search-bar, as seen here.

You can see that the writeValue method on search-bar updates it’s local value, so that it’s internal <input> element can update its value it shows. When that internal input is changed, it calls the inputChanged event on the search-bar, which alerts other components that it has changed, as well as updating its current value.

1
2
3
4
  inputChanged(event) {
    this.writeValue(event.target.value);
    this.onChange(event.target.value);
  }

Filtering out sessions

Since the onChange event is called, the schedule component will see this and cause re-evaluation on its searchQuery variable, and filters the code.

That makes our filtering method super easy, as seen here, copied below for convenience:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
getSessionsForTheDay() {
  if (!this.searchQuery || this.searchQuery.trim() == '') {
    return this.sessionsForTheDay;
  }
  var talks = [];
  this.sessionsForTheDay.forEach((session) => {
    var matched = session.talks.filter((v) => {
      if(v.name.toLowerCase().indexOf(this.searchQuery.toLowerCase()) >= 0) {
        return true;
      }
      return false;
    });
    if (matched.length > 0) {
      session.talks = matched;
      talks.push(session);
    }
  });
  return talks;
}

When the schedule component’s variable for searchQuery is updated, this method will be auto-magically re-evaluated, which causes the list to update.

Hope this helps you understand Angular 2 and ng-models better! Enjoy!

Comments