I'm struggling to fully grasp the component lifecycle. I've created a component, but I need my custom javascript function to run after the DOM is complete. I've scoured through the EmberJS Docs and Stackoverflow, but any documentation I come across doesn't work as I intended.
My Issue
I've attempted to use didInsertElement and didRender, but they still get called before my repeating elements have loaded in the DOM. How do I circumvent this to have my Javascript run after the DOM has fully rendered.
My Attempts
Below is an extremely stripped down and barebones example using an alert in place of my script. The desired outcome is to have this alert appear after the DOM is rendered, the below examples have it alert before.
import Component from '#ember/component';
export default Component.extend({
didRender() {
alert('didRender');
},
didInsertElement() {
alert('didInsertElement');
}
});
Thanks in advance.
Assuming you have the following template
{{#each this.items as |item|}}
<ItemComponent #item={{item}} />
{{/each}}
You want to know when the the list is done rendering?
extract the above template into a component.
<ItemList #items={{this.items}} />
where ItemList is defined as
import Component from '#ember/component';
export default class ItemList extends {
didInsertElement() {
console.log('list was inserted');
}
}
Here is the code:
https://canary.ember-twiddle.com/6e023f1413fbce6bab8954f3eec73554?openFiles=templates.components.item-list.hbs%2Ctemplates.components.item-component.hbs
If you open the console, you'll see:
item 1 was inserted
item 2 was inserted
item 3 was inserted
item 4 was inserted
item 5 was inserted
list was inserted
Although creating a child component is always safe, ( as said in ember's docs
When a view has children, didInsertElement will be called on the child view(s) first and on itself afterwards.
)
, however, even without child components, the didInsertElement hook will trigger only after the component is inserted to DOM. Here is an example twiddle.
Related
Let's say I want to do some jQuery stuff
// Do jQuery Stuff
$(document).ready(function(){
$("button").click(function(){
$("p").hide();
});
});
And I want to include this code in either the route or the controller, I'm quite sure I cannot implement this because there's no hook function (I.E. model(), renderTemplate(), etc. that I can use that guarantees that all the elements in the DOM have safely been rendered.
Now, if I were doing this in a component, I know I could call the didInsertElement hook and that could would allow me to run the code above.
My Use Case
I want to use a library called ScrollMagic which would require jQuery to configure. Someone other than me has already written a lot of code in the route and controller. We could easily just move this to a component (and we probably will), but I still wanted to ask this for my own curiosity.
Question
Is there any hook in either the route or the model that guarantees all the elements of that template have been rendered in the DOM? If not, why is that? After all, you have that hook available in a component.
The route and controller don't have any after render hooks
https://www.emberjs.com/api/ember/release/classes/Controller
https://www.emberjs.com/api/ember/release/classes/Route
Generally the way you'd want to accomplish an afterRender thing, is to either use a custom element-modifier (new): https://github.com/emberjs/ember-render-modifiers
(or bind an action to did-insert)
Or, you can make a renderless component that just has a didInserElement hook defined.
So, in your template:
<BindButtonsToHideParagraphs />
and then inside that component:
export default class BindButtonsToHideParagraphs extends Component {
didInsertElement() {
document).ready(function(){
$("button").click(function(){
$("p").hide();
});
}
}
though, based on the jQuery you've provided, I highly recommend you use normal actions on the button, and conditional if/elses to show/hide the p tags.
that would look something like this:
<button {{action 'toggleParagraph'}}>click me</button>
{{#if this.showParagraph}}
<p>text</p>
{{/if}}
export default class SomeComponent extends Component {
#action
toggleParagraph() {
this.set('showParagraph', !this.showParagraph);
}
}
or, if you are using the sparkles components: https://github.com/rwjblue/sparkles-component
<button {{action this.toggleParagraph}}>click me</button>
{{#if this.showParagraph}}
<p>text</p>
{{/if}}
export default class SomeComponent extends Component {
#tracked showParagraph = true;
toggleParagraph() {
this.showParagraph = !this.showParagraph;
}
}
Hi I am using an UI Library (forced to, company issue..) which provides an Angular component, which renders a form.
Now I want to disable all of the input fields an buttons inside this form. But the component of the library doesn't provide me the possibility to pass a parameter to change the status to read only.
Now I have no other option to do dirty DOM hacking. However it doesn't seem to work.
Here is my HTML of my own component, where I render the Library Component:
<component-of-the-library #formComponent></component-of-the-library>
Now inside my own components class I reference it:
#ViewChild('formComponent', {read: ElementRef}) formComponent: ElementRef;
However when I use the nativeElement feature and the querySelectorAll() function I don't see the button elements:
ngAfterViewInit() {
console.log(this.formComponent.nativeElement);
console.log(this.formComponent.nativeElement.querySelectorAll('button'))
}
The first line outputs the DOM of the library component. There I also see the buttons.
However the second line just returns an empty NodeList.
Am I missing something?
Instead of doing all these, come up with a div overlay and with the size of your form and make show or hide it based on your needs.
It will be easier than disabling each form inputs and buttons. Also the overlay is not the component but your div.
I able to read the DOM Nodes present in the child component from the parent Component using ViewChild() https://stackblitz.com/edit/angular-edmyur?file=src%2Fapp%2Fapp.component.ts
EDIT: I see another problem. AfterViewChecked gets called multiple times...
I found the answer myself. The problem is the LifeCycleHook. AfterViewInit works for your own component, but it doesn't wait for the child components to finish rendering.
When I use AfterViewChecked it works!
However I am still puzzled, that logging the nativeElement has always given me the correct DOM, even though it's still not rendered.
I ran into an issue which I do not know how to "properly" solve.
I have two components, let's call them parent and child.
child component is "generated" by a call to a function that creates it, let's call this function child creator.
parent component has a button which shows/hides the child but is also suppose to .focus() the HTML dom node which is what the child is. Now the show/hide I implemented via the state, but I do not know how to apply javascript's .focus() onto the child's HTML dom node.
There is also a small catch... the child component is being "generated" and "returned" by a call to a plain old javascript function, let's call it createChild.
This is due to the fact that child component needs to be vastly different based on what the parameters that were passed are, however, it also needs to be reused throughout the application so the createChild functions make sure that all the child components are the same, given the same inputs.
I tried passing ref to the creator, however since ref is not a prop, it is not accessible. What is the proper way to "grab" the children's dom nodes in order to .focus() them when the button is clicked since I cannot pass a ref?
Code sandbox link: https://codesandbox.io/s/lyj6x2948m
Yes, child ref is accessible since it is part of real DOM. I made a simple example with two nested components, check it out:
class Parent extends React.Component {
focusRef(ref) {
ref.focus();
}
render() {
return <Child focusRef={this.focusRef} />
}
};
class Child extends React.Component {
render() {
return (
<button
ref={childRef => this.childRef = childRef}
onMouseEnter={() => this.props.focusRef(this.childRef)}
>
When mouse enters, i get focused
</button>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
*:focus {
outline: 2px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root" />
Also, I strongly recommend further reading on react docs:
Okay for everyone wondering, I found a solution to the problem.
To begin with, the issue was not in the ref passing (at least not explicitly), but rather in a way controls are being created (which doesnt allow the ref to be passed).
The controlCreator function is a good old simple javascript function, and it was being used to provide a type for a React.createElement because the result of calling the controlCreator ends up being a react component.
I have however came to an understanding of why this is wrong and have instead proceeded to generate my child elements by the controlCreator and then using React.cloneElement to inject them with a ref. This way, the element is being cloned within a parent and can thus be referenced by parent's methods.
A link to a working code sandbox
I have an Angular 1.5 component with a controller (which uses controllerAs syntax), where I wrote a function adding additional css class to the component element if a certain html element exists in the page. If this certain html element doesn't exist, this additional css class is not applied on the component. That works in the pages where the html element which I am looking for, exists. However, when switching to another state, the component keeps the additional css class no matter that the html element doesn't exist on this state. You have to refresh the app in order to 'reset' the function which comes from the component's controller. This component is used on every page in the application.
For example, this is my component:
function myComponentController() {
activate();
function activate(){
addAdditionalCssClass();
}
function addAdditionalCssClass(){
// code for adding additional css class to the component
// if html element exists
}
}
It works fine when we are on a state where this html element exists. However when we go to another state, where the concrete html element is null, the function addAdditionalCssClass() continiues to add additional class to the component. Any help/suggestions would be greatly appreciated.
As I understand, you have a page with some dynamic html and a component in this page that should reflect the current state with a change in its classes.
One solution would be to have the component depend of a list of classes, i.e. have in its bindings a classes attribute, e.g.
.component('classSpecial', {
template: "<div ng-class='$ctrl.classes'>Hello</span></div>",
bindings: {
classes: '<'
}
})
See a simple example here, where you can change the classes of a component from the controller of the page.
I have a react component that contains a child list of components created using a map:
var listItems = model.arrayItems.map(function(item) {
return(<ChildComponent item={item}></ChildComponent>)
}, this);
And the component's render function then adds this list of child components:
return (
<div>
<h1>My items:<h1>
<ul>{listItems}</ul>
</div>
);
The problem is that the react component is not showing up in my browser when listItems is empty. However, if I resize the browser window, the component does show up. Does anyone have any advice on what might be causing this behavior?
So it did turn out to be an issue with ChildComponent. In fact, it seems to have been a CSS issue.
ChildComponent itself had a nested set of dynamically generated list items. These grandchild components had the CSS property for width set to 100%. Changing this property to "auto" fixed the issue.
My understanding is that when transitioning to a state in which the top-level list is empty, and given the way React is managing unmounted components, the grandchild component's CSS style of 100% (of parent element) somehow confuses the browser's rendering because its parent component is no longer mounted. If anyone knows the cause of this behavior in more depth, it would be great to hear a deeper explanation of this.