Algolia Places
You are reading the documentation for Angular InstantSearch v3, which is in beta. You can find the v2 documentation here.
Introduction
It is currently not possible to combine Anguiar InstantSearch with Places, however it is possible to wrap places.js like it would be for any non-angular dependency. However, there are some potential pitfalls that can be avoided with the tips in this guide.
Instantiation
First of all, we need to know one important thing, and that is that a DOM element made by Anguiar inside a template will be considered “controlled”. Which means that it should not have any side effects. While it is possible to use an input inside the template for the Places container, we will need to make sure it’s fully initialized before you instantiate Places. Similarly we will need to clean up before the template is unmounted.
For that we use the ngAfterViewInit
and ngOnDestroy
lifecycle hooks. You can learn more about lifecycle hooks here.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import {
AfterViewInit,
Component,
EventEmitter,
OnDestroy,
Output,
ViewChild
} from "@angular/core";
import places from "places.js";
@Component({
selector: "app-places",
template: `
<input #input type="search" placeholder="Where are we going?" />
`
})
export class PlacesComponent implements AfterViewInit, OnDestroy {
private instance = null;
@ViewChild("input") input;
ngAfterViewInit() {
this.instance = places({
container: this.input.nativeElement
// places options
});
}
ngOnDestroy() {
this.instance.destroy();
}
}
You can now use this component in your app.
1
<app-places></app-places>
Options
Any Places option can be passed as an angular @Input
. In this example we will make the type
option available as a prop:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import {
AfterViewInit,
Component,
EventEmitter, Input,
OnDestroy,
Output,
ViewChild
} from "@angular/core";
import places from "places.js";
@Component({
selector: "app-places",
template: `
<input #input type="search" placeholder="Where are we going?" />
`
})
export class PlacesComponent implements AfterViewInit, OnDestroy {
private instance = null;
@ViewChild("input") input;
@Input() type : string; // type is available as a prop
ngAfterViewInit() {
this.instance = places({
container: this.input.nativeElement,
type: this.type // we pass the type prop to the instance
// places options
});
}
ngOnDestroy() {
this.instance.destroy();
}
}
Listeners
Anguiar handles events in its own event system. For a nice integration, we would like places events to also follow this. As an example, let’s add a listener for change
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import {
AfterViewInit,
Component,
EventEmitter, Input,
OnDestroy,
Output,
ViewChild
} from "@angular/core";
import places from "places.js";
@Component({
selector: "app-places",
template: `
<input #input type="search" placeholder="Where are we going?" />
`
})
export class PlacesComponent implements AfterViewInit, OnDestroy {
private instance = null;
@ViewChild("input") input;
@Output() onChange? = new EventEmitter();
@Input() type : string; // type is available as a prop
ngAfterViewInit() {
this.instance = places({
container: this.input.nativeElement,
type: this.type // we pass the type prop to the instance
// other places options
});
this.instance.on("change", e => {
this.onChange.emit(e);
});
}
ngOnDestroy() {
this.instance.removeAllListeners("change");
this.instance.destroy();
}
}
We need to make sure to clear all listeners to change
event in ngOnDestroy()
to avoid memory leaks.
Reactivity
Options for places.js are set once on component instantiation, but never updated, since we did not add any code for that.
Recent versions of places.js comes with a method called configure
, this can be used to override some of the options. Here’s an example on how to make type
reactive with ngOnChanges
lifecycle hook.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import {
AfterViewInit,
Component,
EventEmitter,
Input,
OnDestroy,
Output,
SimpleChanges,
ViewChild
} from "@angular/core";
import places from "places.js";
@Component({
selector: "app-places",
template: `
<input #input type="search" placeholder="Where are we going?" />
`
})
export class PlacesComponent implements AfterViewInit, OnDestroy {
private instance = null;
@ViewChild("input") input;
@Output() onChange? = new EventEmitter();
@Input() type : string; // type is available as a prop
ngAfterViewInit() {
this.instance = places({
container: this.input.nativeElement,
type: this.type // we pass the type prop to the instance
// other places options
});
this.instance.on("change", e => {
this.onChange.emit(e);
});
}
ngOnChanges(changes: SimpleChanges) {
if (changes.type) {
const { currentValue } = changes.type;
this.instance.configure({ type: currentValue });
}
}
ngOnDestroy() {
this.instance.removeAllListeners("change");
this.instance.destroy();
}
}
Conclusion
As initially said, wrapping places.js for Anguiar is not that different to any other vanilla JS plugin, but there are some things that are best done to avoid complicated bugs.
You can find the complete source code of the example on GitHub.