My Angular2 app consumes a RESTful API and then creates a bunch of <select> elements based on the result. I'm trying to call a jQuery function on these <select> elements, but it looks like the jQuery function executes too late. I tried putting the function in ngAfterContentInit but that didn't work. Putting it in ngAfterViewChecked froze my browser.
After the page has rendered, if I paste the jQuery function into the console, everything works, so I know that my function and everything are functional. Their order is just probably messed up or something.
In my component:
ngOnInit() {
this.myService.getAll().subscribe(
data => this._data = data,
error => this._error = "invalid.");
}
ngAfterViewInit() {
$("select").select2(); // <--- jQuery function I need to execute after rendering
}
In my template:
<select *ngFor="let d of _data">...blah blah</select>
This should work for you:
#ViewChild('select') selectRef: ElementRef;
constructor(private myService: MyService, private ngZone: NgZone) {}
ngOnInit() {
this.myService.getAll().subscribe(data => {
this.options = data;
// waiting until select options are rendered
this.ngZone.onMicrotaskEmpty.first().subscribe(() => {
$(this.selectRef.nativeElement).select2();
});
});
}
Plunker Example
Related
I'm trying to have a singleton work across multiple threads in node/ts. But for some reason only main event loop has access to it. Is there any way I can have a singleton updated in the main loop, and have the changes reflected in a worker thread?
Here's the code -
data_source.ts
class DataSource {
private _data: any
constructor() {
this._data = []
}
set(data: any) {
this._data.push(data)
}
get(id: any) {
return this._data.find((d: any) => d.id === id)
}
}
const instance = new DataSource();
Object.freeze(instance)
export default instance
index.ts
let workerpool = require('workerpool')
import DataSource from './data_source'
import { test } from './worker'
console.log(DataSource)
function main () {
let pool = workerpool.pool()
pool.exec(test, [])
setInterval(() => {
DataSource.set({id: 'asd'})
}, 1000)
}
main()
worker.ts
import DataSource from './data_source'
export function test() {
setInterval(() => {
console.log(DataSource)
console.log("test")
}, 500)
}
As you can see, the main.ts executes a worker which "should" console log DataSource singleton every 0.5s, while main.ts adds new item to the DataSource every second.
This works if I emit the following line from worker - console.log(DataSource), a string "test" gets logged properly every 0.5s. But, when I try to console log DataSource in my worker thread, it gets logged properly for the first time only, and after that it seems that interval from main loop completely takes over the singleton and completely breaks the worker, and it stops logging anything.
I'm trying to use singleton as the schedule, main loop as scheduler, and worker as the executor (which checks the schedule every second).
Thank you!
I am trying to "steal" from the DOM the SVG code generated by an own component. I do it like this:
<my-own-component id="my-component-id" #myComponentId
[data]="data"></my-own-component>
onButtonClick() {
this.data = someData;
const svgCode = document.getElementById('my-component-id').innerHTML;
}
Also tried (also not working):
#ViewChild('myComponentId') myComponentId;
...
onButtonClick() {
this.data = someData;
const svgCode = this.myComponentId.nativeElement.children[0].innerHTML;
}
The problem is that I get the content before Angular has applied the changes caused by this.data = someData, so the elements of the SVG are not included.
I have "solved" it introducing a 50ms timeout. This works, but is not a proper solution, is a bad patch:
this.data = someData;
await new Promise(resolve => setTimeout(resolve.bind(null, null), 50));
const svgCode = document.getElementById('my-component-id').innerHTML;
I would like to be able to wait for Angular to finish rendering the component. Is there any way to do it?
Thanks in advance.
Elezan, the problem is that you need "give a breath to Angular". If you has, e.g.
<div>{{data}}</div>
click(){
this.data="......."
//here you don't can check the value of innerHtml
}
This "breath" is use a setTimeout
<div>{{data}}</div>
click(){
this.data="......."
setTimeout(()=>{
//now here you can check the value of innerHtml
})
}
Think that Angular, when you call to click function, execute all the instructions and "repaint" the app. So in the first case you're trying to get the innerHTML before Angular "repaint". Using a setTimeout you're saying to Angular: "Hey! you repaint and, after, don't forget the instructions into setTimeout" -see that setTimeout has no milliseconds-
Another way is inject in constructor ChangeDetectorRef and use markForCheck() before try to get the innerHTML
Update Another example using observables
$observable.subscribe(res=>{
this.data=res
setTimeout(()=>{
..get the innerHTML
})
})
Or promise
$promise.then(
res=>{
this.data=res
setTimeout(()=>{
..get the innerHTML
}),
err=>{...}
)
Or await:
const svgCode = await new Promise<string>(resolve => {
setTimeout(() => {
resolve(document.getElementById('my-component-id').innerHTML));
});
});
Try AfterViewInit lifecycle hook. It's implementing like this
export class MyComponent implements AfterViewInit {
ngAfterViewInit() {
// code that should be executed after view initialization
}
}
You just need to add AfterViewInit lifecycle hook with the class. I would also suggest that you assign the property within the OnInit lifescycle as well. Your parent component should look like this
export class appComponent implements OnInit, AfterViewInit{
ngOnInit(): void {
this.data = someData;
}
ngAfterViewInit(): void{
const svgCode = document.getElementById('my-component-id').innerHTML;
}
}
I am working on a task where I have an event listener window.addEventListener which is in javascript, based on event data I want to trigger a function of typescript and pass the data to that function. the point I am not getting is how to call typescript function in javascript. I have tried different things like a global variable, returning a value from js function but didn't work for me.
ngOnInit() {
(function ($) {
$(document).ready(function () {
window.addEventListener('message', function (event) {
console.log("Rece new Call event ibrahim " + JSON.stringify(event.data));
let obj: any = JSON.stringify(event.data)
obj = JSON.parse(obj);
console.log(obj)
if (obj.EventType === "handleContactIncoming") {
var num = obj.Number
// here i want to trigger Typescript function and pass this num to that function.
}
else if (event.data === "endCall") {
// return "No"
// var dbBtn = document.getElementById("db");
// dbBtn.click();
}
// $('.sidebar-menu').tree();
});
});
});
There is no difference when calling function from TS or JS. Finally there's always only JS in the browser, TS exists only in source code.
Also your code is a bit messy. There's no need to use jQuery inside angular (with an exception when you want to use some plugins or custom controls).
$(document).ready(function () {
is also redundant, if angular works the document is for sure ready.
Your code is quite messy and breaks separation of concerns. You should not use jQuery inside Angular. The injectable EventManager provides functionality for setting events on the window or document objects.
constructor(private readonly eventManager: EventManager) {}
ngOnInit(): void {
this.eventManager.addGlobalEventListener(target, event, () => {
this.hello();
});
}
hello(): void {
console.log('Hello World!');
}
I've got a service that loads a list of people from a local database, and that all works great right now. I get a list displayed properly... I even got a filter working with the list and it works great... but when I attempt to add a loading wheel to the filter, it will not display...
Right at the beginning of my AppComponent, I initialize a variable called "loadingData" to false.
export class AppComponent implements OnInit, AfterViewInit {
loadingData: boolean = false;
Then in my filter method, I attempt to set the variable to true, and then back to false.
public doFilter = (value: string) => {
this.loadingData = true;
setTimeout(() =>
{
this.dataSource.filter = value.trim().toLocaleLowerCase();
},
5000);
this.loadingData = false;
}
The filter method works, but the HTML side of things never changes...
I've got a simple message I want to display when "loadingData" is true:
<ng-container *ngIf="loadingData">
<p>Loading data, please wait...</p>
</ng-container>
But the ngIf function doesn't appear to want to work in real-time... if I initiallize "loadingData" to true instead of false then the message shows up, but it never disappears...
How can I get this message to show up and disappear as I change the variable?
you should set loadingData to false only after async operation finishes:
public doFilter = (value: string) => {
this.loadingData = true;
setTimeout(() => {
this.dataSource.filter = value.trim().toLocaleLowerCase();
this.loadingData = false;
}, 5000);
}
I have a public method that I exposed to window. This method talks to a Component and modifies a variable I am watching in my template. But when I change the value, the *ngIf() does not get triggered.
app.component
constructor(private _public: PublicService,) {
window.angular = {methods: this._public};
}
PublicService
export class PublicService {
constructor(
private _viewManager: ViewManagerComponent,
) {}
CallMe(){
this._viewManager.renderView('page1')
}
}
LayoutManagerComponent
#Component({
selector: 'view-manager',
template: `<page *ngIf="view == 'page1'"></page>`
})
export class ViewManagerComponent {
//This is the variable being watched
view = "page";
renderView = function(type){
console.log(type)
this.view = type;
console.log(this.view)
};
}
So the idea is that when the view initially loads, the view is blank. Then when I type angular.methods.CallMe() it modifies the view variable to page1 which should then show the html for the Component. If I console renderView function it is successfully getting called, just the view does not change.
----Update - Still not working -------
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(this.view)
this.zone.run(() => {
// type is 'page'
console.log(this.view)
this.view = type;
// type is 'page1'
console.log(this.view)
});
// type is 'page1'
console.log(this.view)
//cdRef errors:
//view-manager.component.ts:36 Uncaught TypeError: this.cdRef.detectChanges is not a function(…)
this.cdRef.detectChanges();
};
}
In this case Angular2 doesn't know that it needs to run change detection because the change is caused by code that runs outside Angulars zone.
Run change detection explicitely
contructor(private cdRef:ChangeDetectorRef) {}
someMethodCalledFromOutside() {
// code that changes properties in this component
this.cdRef.detectChanges();
}
Run the code that modifies the components properties inside Angulars zone explicitely
contructor(private zone:NgZone) {}
someMethodCalledFromOutside() {
this.zone.run(() => {
// code that changes properties in this component
});
}
The zone method is a better fit when // code that changes properties in this component not only changes properties of the current component, but also causes changes to other components (like this.router.navigate(), call method references of methods of other components) because zone.run() executes the code inside Angulars zone, and you don't need to explicitely take care of change detection in every component where a change might happen because of this call.
If you use function(...) instead of () => it's likely you'll get unexpected behavior with this in code inside the Angular component.
See also my answer to this similar question for more details Angular 2 - communication of typescript functions with external js libraries
update
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
self = this;
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(self.view)
self.zone.run(() => {
// type is 'page'
console.log(self.view)
self.view = type;
// type is 'page1'
console.log(self.view)
});
// type is 'page1'
console.log(self.view)
self.cdRef.detectChanges();
};
}