Traditional DOM Manipulation Angular 2/Google Maps API - google-maps

I am trying to add a google maps places autocomplete search input to an angular 2 application, I am trying to figure out how to proceed with it. So far I have a component, and OnInit i am trying to run the necessary google code. Google's code is simple Javascript like:
var input = document.getElementById('pace-input')
var autocomplete = new google.maps.places.Autocomplete(input, options)
I assume I have to use ElementRef to grab the DOM elements and run this code, but I can't wrap my head around how to do that. Can anyone help me with traditional DOM manipulation with Angular 2, or suggest a better method?

Figured it out. Used #ViewChild('pacinput') ref: ElementRef;
To grab the element then afterViewOnInit:
let autocomplete = new google.maps.places.Autocomplete((this.ref.nativeElement),{types: ['geocode']});

This is not the solution to your problem. But it may help you towards right direction.
To manipulate DOM with Angular2, you may use BrowserDomAdapter api.
Right way to use BrowserDomAdapter - Plunker
import {BrowserDomAdapter} from 'angular2/platform/browser';
export class AppComponent {
dom:BrowserDomAdapter;
constructor() {
this.dom = new BrowserDomAdapter();
}
...
}

I solved it a bit different. This is an example with autocompletion of cities. First I implement AfterViewInit and in the ngAfterView method I call the google.api:
ngAfterViewInit() {
var autocompleteElm = <HTMLInputElement>document.getElementById('autocomplete');
var autocomplete = new google.maps.places.Autocomplete(
(autocompleteElm), {
types: ['(cities)']
});
}
If anyone is interested I load the google api in my index.html file and create a dummy reference in my Component class file outside the class:
declare var google: any;

Related

How to change dir property of the Angular CDK overlay at runtime?

I'm using Angular 10, on click the following function is executed to preform direction change:
private changeHtmlDirection(direction: 'rtl' | 'ltr') {
document.getElementsByTagName("html")[0].dir = direction;
}
It works well, only that the Angular CDK does not update.
I tried to find an API to change Angular CDK's direction at runtime, but couldn't find any.
I saw that there's a BidiModule but it uses only to get the current direction rather than set it.
Is there any solution?
According to the material documentation, you can't change 'dir' on the "html" tag so that affects bidi API. You can see the document at the following link:
bi-directionality document
But if you want to use material bi-directionality you can add the 'dir' directive to a container element in the root component like bellow:
<div [dir]="documentDirection"> </div>
and whenever the 'documentDirection' variable changes, the bidi "change emitter" will be emit.
like following code you can subscribe to it:
constructor(
private dir: Directionality ) {
this.isRtl = dir.value === 'rtl';
this.dir.change.subscribe(() => {
this.isRtl = !this.isRtl;
});
}

(click) event not work in innerHtml string Angular 4

My function isn't called when I click the <a... tag.
I have the following code in my component:
public htmlstr: string;
public idUser:number;
this.idUser = 1;
this.htmlstr = `<a (click)="delete(idUser)">${idUser}</a>`;
public delete(idUser){
alert("id " + idUser);
}
My html
<div [innerHTML]="htmlstr"></div>
but the function delete isn't called and does not show the alert.
The <div... is created dynamically
If anyone face same issue and above all answer not working then try my trick :
In HTML :
<button onclick="Window.myComponent.test()"> test </button>
In component :
class
constructor(){
Window["myComponent"] = this;
}
test(){
console.log("testing");
}
Your main issue here, on-top of the things pointed out by #Matt Clyde and #Marciej21592, is that you're trying to dynamically add HTML code that needs to be compiled before it can be used (you're trying to bind to a method and variable).
Some ways of doing this can be seen here.
From the code you have supplied, however, there are much easier ways to accomplish what you are after. For starters, I would have that code in the HTML to begin with and hide/show it as needed with ngIf.
i use this method and its work
public htmlstr: string;
public idUser:number;
this.idUser = 1;
this.htmlstr = `<a id='innerHtmlClick'>${idUser}</a>`
this.htmlstr.querySelector(`innerHtmlClick`).addEventListener('click', () => {
this.delete(idUser);
});
public delete(idUser){
alert("id " + idUser);
}
EventListener listen the event bye using id of innerHtml
I assume that it is not a bug but rather Angular's security measure against XSS attacks - for more information I would suggest taking a look here https://angular.io/guide/security#sanitization-example
I somewhat also fail to understand why you insist on passing the event via string literal instead of just simply using:
<div>
<a (click)="delete(idUser)">${this.idUser}</a>
</div>
Your component has inner Html.
Angular will not allow events inside inner Html portions for security reasons. You can use Child components. to make events from inside of inner Html portions. Create a child component and put your html inside the child component and pass the data by using any angular events between parent and child using Input, Output features in Angular
I don't often use [innerHTML], but it looks like the template string you're using <a (click)="delete(idUser)">${idUser}</a> is referencing ${idUser} when you might have meant ${this.idUser}?
Below code snippet worked for me:-
In component :
ngAfterViewChecked () {
if (this.elementRef.nativeElement.querySelector('ID or Class of the Html element')) {
this.elementRef.nativeElement.querySelector('ID or Class of the Html element').addEventListener('click', this.editToken.bind(this));
}
}
inside constructor parameter:-
constructor( private readonly elementRef: ElementRef) {}
import { ElementRef } from '#angular/core';---> at the top of the file
implement 'AfterViewChecked'

vue.js json object array as data

aye folks!
I'm currently learning to do stuff with vue.js. unfortunately i'm stuck atm. what i want to do is sending a request to my sample API which responds with a simple json formatted object.
I want to have this object as data in my component – but it doesn't seem to do that for whatever reason.
Ofc i tried to find a solution on stackoverflow but maybe i'm just blind or this is just like the code other people wrote. i even found this example on the official vue website but they're doing the same thing as i do .. i guess?
btw. When i run the fetchData() function in a separate file it does work and i can access the data i got from my API. no changes in the code .. just no vue around it. i'm really confused right now because i don't know what the mistake is.
code:
var $persons = [];
and inside my component:
data: {
persons: $persons,
currentPerson: '',
modal: false
},
created: function() {
this.fetchData()
},
methods: {
fetchData: function () {
var ajax = new XMLHttpRequest()
ajax.open('GET', 'http://api.unseen.ninja/data/index.php')
ajax.onload = function() {
$persons = JSON.parse(ajax.responseText)
console.log($persons[0].fname)
}
ajax.send()
}
},
[...]
link to the complete code
First, make sure that the onload callback is actually firing. If the GET request causes an error, onload won't fire. (In your case, the error is CORS-related, see this post suggested by #Pradeepb).
Second, you need to reference the persons data property directly, not the $persons array that you initialized persons with.
It would look like this (inside your fetchData method):
var self = this;
ajax.onload = function() {
self.persons = JSON.parse(ajax.responseText)
console.log($persons[0].fname)
}

Angular 2 Insert DOM into InfoWindow element that was created by Google Maps V3 api

I am working with the Google Maps V3 inside Angular 2 and I am currently attempting to find a solution for populating InfoWindows with bound links and input values. When an InfoWindow element is created it is a new DOM element and according to Angular 2 docs binding to a DOM element that did not exist on the initialization of the app is not supported. Is there a way to insert or inject bound elements into the newly created DOM, or have a pre-created element containing the bound elements that can be moved into the InfoWindow?
I guess, you should use dynamic components creation in this case. Just try smth like this in your service or component, which knows, when you have to render your content:
import {ComponentResolver, ComponentFactory, ComponentRef, ApplicationRef, Injector} from '#angular/core';
export class SomeServiceOrComponent {
constructor(private resolver:ComponentResolver, private injector:Injector, private appRef:ApplicationRef) {
}
renderComponent() {
return this.resolver
.resolveComponent(YourDynamicComponentClass)
.then((factory:ComponentFactory<YourDynamicComponentClass>) => {
let cmpRef:ComponentRef<YourDynamicComponentClass> =
factory.create(this.injector, null, '.infoWindowsDOMSelector');
(<any>this.appRef)._loadComponent(cmpRef);
return cmpRef;
});
}
}
and just call this method, whether you need it.
(<any>this.appRef)._loadComponent(cmpRef) is a trick from prev versions of RC, but maybe soon it should be resolved

Difference between the bound Knockout value and the value of the input element

I have a Knockout object mapped from a JSON object. A value of that knockout object is databound to an input element.
I have also bound a google places autocomplete to that same input element.
The value referenced by the knockout object tells me the value of what I have typed in (not selected, so it would be 'bar').
The value of the input box is the correct value (the location I have chosen, 'Barnet, United Kingdom').
Here is a JSFiddle that demonstrates the problem...
http://jsfiddle.net/twistedinferno/CPEMk/
Why do the two values differ and can I get the selected value as my bound value? (preferably without the need of another property on my KO object)
Updated JSFiddle here
This is normal behavior. Knockout registers its value bindings to events it is aware of such as afterkeypress or blur. It cannot 'detect' changes made by other frameworks such Google Places API by default. So, in order to work, you need to write some code that pushes events from the other framework to Knockout.
In this case, Google Places Autocomplete API supports a place_changed event. I've incorporated this event in my updated Fiddle to showcase a working example. See the Google documentation for more information about this event. The most important bit is:
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
koObj.StartDescription(place.formatted_address);
koObj.Name(place.name);
koObj.Address(place.formatted_address);
});
However, if you intend to use this functionality more than once, I strongly suggest you wrap this behavior in a custom Knockout binding.
Here's an excellent article about custom Knockout bindings. If you scroll all the way down to the last chapter, you will see an example of a Knockout binding that wraps around a third party control. You will want to do the same for Google Places Autocomplete, so you can handle three aspects of working with this control:
Initializing the control (with optional config options for the widget)
Responding to updates made in the UI by setting up an event handler. This is achieved by wrapping around the place_changed event.
Responding to updates made to the view model. This might not be necessary for your scenario.
Using a custom knockout binding will look something like this:
<input id="startDescription" type="text" data-bind="googleAutoComplete: StartDescription, googleAutoCompleteOptions: { componentRestrictions: { country: 'uk' } }" />
Good luck!
Here is a bare bones custom binding that I wrote for this in case anyone is searching for this:
ko.bindingHandlers.addressAutocomplete = {
init: function (element, valueAccessor, allBindingsAccessor) {
var value = valueAccessor(), allBindings = allBindingsAccessor();
var options = { types: ['geocode'] };
ko.utils.extend(options, allBindings.autocompleteOptions)
var autocomplete = new google.maps.places.Autocomplete(element, options);
google.maps.event.addListener(autocomplete, 'place_changed', function () {
result = autocomplete.getPlace();
value(result.formatted_address);
});
},
update: function (element, valueAccessor, allBindingsAccessor) {
ko.bindingHandlers.value.update(element, valueAccessor);
}
};
You can use it like this:
<input type="text" data-bind="addressAutocomplete: Address" />
Or if you want to specify options of the google places autocomplete ( eg. Limiting your search to one country):
<input type="text" data-bind="addressAutocomplete: Address, autocompleteOptions: { componentRestrictions: { country: 'au' } }" />
Hope it helps.
(Note. I have limited my autocomplete to the geocode type by default.)
also if you want to bind to specific components of the address see this gist https://gist.github.com/chadedrupt/5974524