Get innerHTML from component AFTER render - javascript

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;
}
}

Related

custom elements & connectedCallback() : wait for parent node to be available before firing a function

I'm using custom elements, which are very nice.
But I'm facing a problem :
When the connectedCallback() function is called, it seems that the node is not yet at its place in the DOM, thus I cannot access its parents - and I need them.
class myElement extends HTMLElement{
constructor() {
super();
this.tracklist = undefined;
}
connectedCallback(){
this.render();
}
render(){
this.tracklist = this.closest('section');
// following code requires this.tracklist!
// ...
}
window.customElements.define('my-element', myElement);
How could I be sure the parent nodes are accessible before calling render() ?
Thanks !
It is a known issue:
connectedCallback does not mean your element is or is not fully parsed.
Custom Elements is lacking a parsedCallback method
See all the answers at:
textContent empty in connectedCallback() of a custom HTMLElement
connectedcallback-of-a-custom-htmlelement
How to have a 'connectedCallback' for when all child custom elements have been connected
TL;DR;
The accepted method is to delay your render method:
connectedCallback(){
setTimeout(this.render);
}
It seems that the connectedCallback cannot access other elements in relation to itself when it hasn't been parsed yet. This kind of makes sense if you consider that a custom-element has to be able to live anywhere in the DOM without being dependent on another element. So if there were no parent to be selected, the element would probably not work properly.
A way to do this is to modify the render method to take an argument which will set the tracklist property dynamically to the custom element. Then select the my-element element from the DOM and look for the section.
Then use the customElements.whenDefined method to connect the section and my-element together whenever the custom element is ready. This method returns a Promise that resolves whenever the custom element is defined and gives you the ability to execute a callback.
See example below:
// Do something whenever the element is ready.
window.addEventListener('load', function() {
// Wait for the document to load so the DOM has been parsed.
window.customElements.whenDefined('my-element').then(() => {
const myElement = document.querySelector('my-element');
// Only do something if the element exists on the page.
if (myElement !== null) {
const tracklist = myElement.closest('section');
myElement.render(tracklist);
console.log(myElement.tracklist);
}
});
});
// Create element.
class myElement extends HTMLElement{
constructor() {
super();
this.tracklist = null;
}
render(tracklist){
this.tracklist = tracklist;
// following code requires this.tracklist!
// ...
}
}
// Define element.
window.customElements.define('my-element', myElement);
<section>
<my-element></my-element>
</section>
If I have been unclear or you have questions, please let me know.
Have a nice day!
I haven't tested this out but seems like a Promise might work:
// DomReady.js
class DomReady extends HTMLElement {
constructor() {
super();
this.domReadyPromise = new Promise(resolve => (this.domReadyResolve = resolve));
}
connectedCallback() {
this.domReadyResolve();
}
domReady() { return this.domReadyPromise; }
}
// ParentCustom.js
class ParentCustom extends DomReady {
connectedCallback() {
super.connectedCallback();
...
}
}
// ChildCustom.js
class ChildCustom extends HTMLElement {
async connectedCallback() {
await document.querySelector('parent-custom').domReady();
}
}

Angular Two-Way Data Binding and Watching for Changes in Parent Component

It seems there is no way to watch changes in the parent component when using two-way data binding.
I have a custom input component for collecting a tag list. Two-way data binding is setup and working between this component and its parent.
// the parent component is just a form
// here is how I'm adding the child component
<input-tags formControlName="skillField" [(tags)]='skillTags' (ngModelChange)="skillTagUpdate($event)">
</input-tags>
In the parent component how do you watch the bound variable for changes? While it's always up to date (I've confirmed this) I cannot find any guidance on reacting to changes.
I've tried:
ngOnChanges(changes: SimpleChanges) {
if (changes['skillTags']) {
console.log(this.skillTags); // nothing
}
}
And
skillTagUpdate(event){
console.log(event); // nothing
}
UPDATE:
TWDB IMHO is not what it is advertised to be. Whenever I arrive at this place where TWDB seems to be a solution I rearchitect for a service and or observable communication instead.
When you implement a two way binding of your own, you have to implement an event Emitter. The syntax for that is mandatory.
this means that you have a hook to listen to if the value changes.
Here is a demo :
<hello [(name)]="name" (nameChange)="doSomething()"></hello>
_name: string;
#Output() nameChange = new EventEmitter();
set name(val) {
this._name = val;
this.nameChange.emit(this._name);
}
#Input()
get name() {
return this._name;
}
counter = 0;
ngOnInit() {
setInterval(() => {
this.name = this.name + ', ' + this.counter++;
}, 1000);
}
Stackblitz
From what I know, this seems the less annoying way to use it, and any two way binding will follow the same rule no matter what, i.e. it ends with the Change word !
Your implementation is actually not two-way databinding, the parent and child component are just sharing a reference on the same skillTags variable.
The syntax [(tags)]='skillTags' is syntaxic sugar for [tags]='skillTags' (tagsChange)='skillTags = $event'
You need to implement tagsChange in the child component like this: #Output('tagsChange') tagsChange = new EventEmitter<any>();, then any time you want to modify tags into the children component, dont do it directly, but use this.tagsChange.emit(newValue) instead.
At this point, you'll have real two-way databinding and the parent component is the unique owner of the variable (responsible for applying changes on it and broadcasting changes to the children).
Now in your parent component, if you want to do more than skillTags = $event (implicitly done with [(tags)]='skillTags'), then just add another listener with (tagsChange)='someFunction($event)'.
StackBlitz Demo
Don't know if this is what you're looking for, but have you tried using #Input()?
In child component
#Input() set variableName(value: valueType) {
console.log(value);
}
In parent component
<input-tags formControlName="skillField" [(tags)]='skillTags'
[variableName]="skillTagUpdate($event)"></input-tags>
The input function is called every time the object binded to the function is changed.
you could listen to the change:
<input-tags formControlName="skillField" [tags]='skillTags' (tagsChange)='skillTags=$event; skillTagUpdate();'></input-tags>
or use getter and setter:
get skillTags(): string {
return ...
}
set skillTags(value) {
variable = value;
}
another approach:
export class Test implements DoCheck {
differ: KeyValueDiffer<string, any>;
public skillTags: string[] = [];
ngDoCheck() {
const change = this.differ.diff(this.skillTags);
if (change) {
change.forEachChangedItem(item => {
doSomething();
});
}
}
constructor(private differs: KeyValueDiffers) {
this.differ = this.differs.find({}).create();
}
}}
1.you can use output(eventemitter)
2.easiest solution is rxjs/subject. it can be observer and observable in same time
Usage:
1.Create Subject Property in service:
import { Subject } from 'rxjs';
export class AuthService {
loginAccures: Subject<boolean> = new Subject<boolean>();
}
2.When event happend in child page/component use :
logout(){
this.authService.loginAccures.next(false);
}
3.And subscribe to subject in parent page/component:
constructor(private authService: AuthService) {
this.authService.loginAccures.subscribe((isLoggedIn: boolean) => {this.isLoggedIn = isLoggedIn;})
}
Update
for two-way binding you can use viewchild to access to your child component items and properties
<input-tags #test></<input-tags>
and in ts file
#ViewChild('test') inputTagsComponent : InputTagsComponent;
save()
{
var childModel = this.inputTagsComponent.Model;
}

Angular 4+ Function reloading each time the component loads

So here is the function
greenToAmber() {
let x = 0;
setInterval(function () {
x++;
..... Rest of code
}, 500);
}
}
I've set this component up using the routes as you would expect, I've tried calling the function in OnInit as well, but every time I go this component then off it and back again the counter will launch a second instance of the counter & then a third ect for each time I leave & come back to the page.
From my understanding I thought ngOnDestroy was meant to prevent this, I'm assuming that I'll need to subscribe and then unsubscribe to the function maybe on destroy?
But I'm relatively new to angular 4 so pretty inexperienced.
setInterval is not destroyed on component destroy, you have to save the interval id in your class and use clearInterval javascript native function to clean it on your component destroy hook ngOnDestroy:
import {Component, OnDestroy} from '#angular/core';
#Component({ ... })
export class YourClass implements OnDestroy {
public intervalId: any;
public greenToAmber() {
let x = 0;
// registering interval
this.intervalId = setInterval(function () {
// ..... Rest of code
}, 500);
}
}
public ngOnDestroy () {
if (this.intervalId !== undefined) {
clearInterval(this.intervalId); // cleaning interval
}
}
}
Hopes it helps.
You're setting a demon process with setInterval. The behavior you mention is expected. That is how JavaScript works. Its not Angular specific.
SetInterval always returns a ID which you might want to track in your controller. When you want to destroy it, make sure you do it specifically.
eg:
greenToAmber() {
let x = 0;
$scope.myDemon = setInterval(function () {
x++;
..... Rest of code
}, 500);
}
}
//Somewhere else; where you want to destroy the interval/stop the interval:
If($scope.myDemon) {
clearInterval($scope.myDemon);
}

Mock Es6 classes using Jest

I'm trying to mock an ES6 class with a constructor that receives parameters, and then mock different class functions on the class to continue with testing, using Jest.
Problem is I can't find any documents on how to approach this problem. I've already seen this post, but it doesn't resolve my problem, because the OP in fact didn't even need to mock the class! The other answer in that post also doesn't elaborate at all, doesn't point to any documentation online and will not lead to reproduceable knowledge, since it's just a block of code.
So say I have the following class:
//socket.js;
module.exports = class Socket extends EventEmitter {
constructor(id, password) {
super();
this.id = id;
this.password = password;
this.state = constants.socket.INITIALIZING;
}
connect() {
// Well this connects and so on...
}
};
//__tests__/socket.js
jest.mock('./../socket');
const Socket = require('./../socket');
const socket = new Socket(1, 'password');
expect(Socket).toHaveBeenCalledTimes(1);
socket.connect()
expect(Socket.mock.calls[0][1]).toBe(1);
expect(Socket.mock.calls[0][2]).toBe('password');
As obvious, the way I'm trying to mock Socket and the class function connect on it is wrong, but I can't find the right way to do so.
Please explain, in your answer, the logical steps you make to mock this and why each of them is necessary + provide external links to Jest official docs if possible!
Thanks for the help!
Update:
All this info and more has now been added to the Jest docs in a new guide, "ES6 Class Mocks."
Full disclosure: I wrote it. :-)
The key to mocking ES6 classes is knowing that an ES6 class is a function. Therefore, the mock must also be a function.
Call jest.mock('./mocked-class.js');, and also import './mocked-class.js'.
For any class methods you want to track calls to, create a variable that points to a mock function, like this: const mockedMethod = jest.fn();. Use those in the next step.
Call MockedClass.mockImplementation(). Pass in an arrow function that returns an object containing any mocked methods, each set to its own mock function (created in step 2).
The same thing can be done using manual mocks (__mocks__ folder) to mock ES6 classes. In this case, the exported mock is created by calling jest.fn().mockImplementation(), with the same argument described in (3) above. This creates a mock function. In this case, you'll also need to export any mocked methods you want to spy on.
The same thing can be done by calling jest.mock('mocked-class.js', factoryFunction), where factoryFunction is again the same argument passed in 3 and 4 above.
An example is worth a thousand words, so here's the code.
Also, there's a repo demonstrating all of this, here:
https://github.com/jonathan-stone/jest-es6-classes-demo/tree/mocks-working
First, for your code
if you were to add the following setup code, your tests should pass:
const connectMock = jest.fn(); // Lets you check if `connect()` was called, if you want
Socket.mockImplementation(() => {
return {
connect: connectMock
};
});
(Note, in your code: Socket.mock.calls[0][1] should be [0][0], and [0][2] should be [0][1]. )
Next, a contrived example
with some explanation inline.
mocked-class.js. Note, this code is never called during the test.
export default class MockedClass {
constructor() {
console.log('Constructed');
}
mockedMethod() {
console.log('Called mockedMethod');
}
}
mocked-class-consumer.js. This class creates an object using the mocked class. We want it to create a mocked version instead of the real thing.
import MockedClass from './mocked-class';
export default class MockedClassConsumer {
constructor() {
this.mockedClassInstance = new MockedClass('yo');
this.mockedClassInstance.mockedMethod('bro');
}
}
mocked-class-consumer.test.js - the test:
import MockedClassConsumer from './mocked-class-consumer';
import MockedClass from './mocked-class';
jest.mock('./mocked-class'); // Mocks the function that creates the class; replaces it with a function that returns undefined.
// console.log(MockedClass()); // logs 'undefined'
let mockedClassConsumer;
const mockedMethodImpl = jest.fn();
beforeAll(() => {
MockedClass.mockImplementation(() => {
// Replace the class-creation method with this mock version.
return {
mockedMethod: mockedMethodImpl // Populate the method with a reference to a mock created with jest.fn().
};
});
});
beforeEach(() => {
MockedClass.mockClear();
mockedMethodImpl.mockClear();
});
it('The MockedClassConsumer instance can be created', () => {
const mockedClassConsumer = new MockedClassConsumer();
// console.log(MockedClass()); // logs a jest-created object with a mockedMethod: property, because the mockImplementation has been set now.
expect(mockedClassConsumer).toBeTruthy();
});
it('We can check if the consumer called the class constructor', () => {
expect(MockedClass).not.toHaveBeenCalled(); // Ensure our mockClear() is clearing out previous calls to the constructor
const mockedClassConsumer = new MockedClassConsumer();
expect(MockedClass).toHaveBeenCalled(); // Constructor has been called
expect(MockedClass.mock.calls[0][0]).toEqual('yo'); // ... with the string 'yo'
});
it('We can check if the consumer called a method on the class instance', () => {
const mockedClassConsumer = new MockedClassConsumer();
expect(mockedMethodImpl).toHaveBeenCalledWith('bro');
// Checking for method call using the stored reference to the mock function
// It would be nice if there were a way to do this directly from MockedClass.mock
});
For me this kind of Replacing Real Class with mocked one worked.
// Content of real.test.ts
jest.mock("../RealClass", () => {
const mockedModule = jest.requireActual(
"../test/__mocks__/RealClass"
);
return {
...mockedModule,
};
});
var codeTest = require("../real");
it("test-real", async () => {
let result = await codeTest.handler();
expect(result).toMatch(/mocked.thing/);
});
// Content of real.ts
import {RealClass} from "../RealClass";
export const handler = {
let rc = new RealClass({doing:'something'});
return rc.realMethod("myWord");
}
// Content of ../RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "The.real.deal "+input;
}
// Content of ../test/__mocks__/RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "mocked.thing "+input;
}
Sorry if I misspelled something, but I'm writing it on the fly.

Angular 2 calling jQuery after rendering elements - after consuming API

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

Categories