Wait for element to be defined in Angular2 - angularjs

I'm trying to embed a Google Chart in a PrimeNG tab. I'm using a fork of the Angular 2 Google Chart package. The first time I route to the page with the chart, everything works as expected. However, when I navigate away, and return I get the following error:
Error in ./MyChartComponent class MyChartComponent - inline
template:1:5 caused by: The container #my_chart is not defined.
This is the same error one would get if they were trying to draw a chart into a div that didn't exist. As far as I can tell,
1) it works the first time because there is a delay while the charts library is retrieved, allowing for the <div id="my_chart"...> element to be rendered.
2) when the route is reopened, a sequence of events fires that results in the charting library to try and set the content of of the div before the div has been included in the DOM.
The charting components work fine when they are not embeded in the PrimeNG tabs. I suspect the order in which events occurs has been broken by this. I found a reference that said PrimeNG tabs put content into <ng-content>. If this breaks some sort of Parent/Child relationship in the components lifecycle, then I further suppose that I need some way to wait on setting the #Input to the chart component until after the "my_chart" div exists.
I've dug through the documentation for the various Angular life cycle hooks, but haven't been able to solve this issue. Any adivice would be appreciated.

Try using the resolve attribute in setting up your route:
{
path: '',
resolve: { chartData: ChartResolver },
component: MyChartComponent
}
Then in your component pull that data in:
ngOnInit() {
//The 'data' gets loaded into the snapshot by the route resolver
this.chartData = this.route.snapshot.data['chartData'];
}
Then you have to create the Resolver class:
#Injectable()
export class ChartResolver implements Resolve<ChartData> {
constructor(private http:Http) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<ChartData> {
//Doesn't have to be a server call, as long as it returns the data
return this.http.get("/url-to-chart-info)
.map((res:Response) => res.json());
}
}
You'll have to import everything correctly and I don't know if you have a Typescript class for the data, but something in this direction should work. This should make the data or whatever else you need to have ready available before the template is rendered.
Hopefully that helps, it doesn't fully address your question, more part 2, but the resolver is the best way to get everything ready before you need to render.

Related

Reactivesearch & Reactivemaps search routing

Recently I started using Reactivesearch and Reactivemaps by Appbase.io on my website to search my database of items and render them and so on. Most of the components work as they should and some have minor bugs. So recently I moved my component into its own component and then placed that on my nav bar for global access. The issue I then encountered was "how can I route back to my /explore page with my search value?". Evidently its not as simple as I thought.
I setup my search page with a function toPath that takes the path and the value that comes from onValueSelected
toPath(path, obj) {
this.props.history.push(`${path}?q="${obj.term}"`);
console.log('SearchVal: ',obj)
}
Note that term is added because its the object.term and term is the string from your search.
So I try this and I have URLParams={true} on my <ReactiveGoogleMap/> and <CategorySearch/> set and I select my value, and it takes me to the /explore page but my map is centered on item 0 of the elastic database.
I tried a few other methods, passing just the object, removing URLParams and so on. Nothing seems to work.
If anyone has any idea how this could be achieved I would greatly appreciate it, its just this minor issue but still a problem down the road and the documentation lacks how to do this / any examples for the public.
Here is the current config:
toPath(path, obj) {
this.props.history.push(`${path}?q="${obj.term}"`);
console.log('SearchVal: ',obj)
}
<CategorySearch
className="searchbar"
dataField={["restaurantName"]}
categoryField="city.keyword"
placeholder='Let's Search"'
componentId="q"
URLParams={true}
...
<ReactiveGoogleMap
componentId="map"
dataField="location"
stream={true}
defaultZoom={13}
pagination
autoCenter={false}
onPageChange={() => {
window.scrollTo(0, 0);
}}
style={{
}}
onPopoverClick={this.onPopoverClick}
className="rightCol"
showMarkerClusters={false}
showSearchAsMove={false}
searchAsMove={true}
//this.props.location.state.reactive_s
//The old way I passed the object I used this ^
react={{
and: [
"q"
],
}}
Also as a side note, i'm not sure if anyone else is getting this but I get these constant issues from #emotion and its a problem with Reactivesearch
serialize.browser.cjs.js:149 Functions that are interpolated in css calls will be stringified.
If you want to have a css call based on props, create a function that returns a css call like this
let dynamicStyle = (props) => css`color: ${props.color}`
It can be called directly with props or interpolated in a styled call like this
let SomeComponent = styled('div')`${dynamicStyle}`
This is also an issue with reactivesearch and makes it very difficult to work because you can't exactly hide it without hiding everything else. It happens with every click and scroll of the page that has a reactive component. Only on dev branches, never on compiled builds.

React Ionic Navigation with directly passed functional props

How do I push a page using React and Ionic with functional props?
I did try:
history.push({
pathname: '/edit_some_property',
state: {
onSetSomeProperty(val) { setSomeState(val) }
}
})
But that throws:
DOMException: Failed to execute 'pushState' on 'History': onSetSomeProperty(val) { setSomeState(val); } could not be cloned.
Apparently, state is serialized to a string, so passing functions isn't going to work.
Or is there another approach that would work better for pushing screens with callbacks to the previous screen?
Background and Detail:
I have a very hierarchical style React + Ionic app.
Choose an item from a list, push a screen with item details.
Choose a sub item from a list, push a screen with sub item details.
Tap to edit an attribute, push a screen with form fields, and callback to the previous screen with the result.
Now it appears that in order to navigate from screen to screen, Ionic requires you to use React Router, where each page has a unique URL. But this doesn't work so well here since each screen depends on the state of previous screens.
This means that there is no apparent way to communicate rich props between pages. If I have a callback to the previous page, I can't pass that along because I don't have direct access to component being pushed with the router in the way.
There's also ion-nav https://ionicframework.com/docs/api/nav which is closer to what I think I need, but that doesn't seem to have a react interface at all.
I tried:
<IonNav root={Home} />
Which throws this, deep in obfuscated internals:
Error: framework delegate is missing
My original answer was way off base sorry. I did some testing, and found that anything passed to state needs to be serializable. In order to do this for a function, others have reported that https://www.npmjs.com/package/safe-json-stringify helped them out.

How to load an Angular2 component from AngularJS more than one time?

I am working on an application which has both AngularJS and Angular. Right now, I am invoking Angular component from a html page using the below line:
System.import('app')
Inside app\app.module.ts file, I have specified a specific parent component to be bootstrapped. This parent component in turn invokes a child component. I put some console.log statements in the constructor of the parent and child component and I see everything works fine for the first time.
But, when I navigate away and comeback to the html page again, I notice that the parent and child components are not getting initialized. The only workaround I have found is to refresh the entire page which is not ideal. I tried to unload the Angular components as soon as the user navigates away but I couldn't find any suitable SystemJS methods.
I know Angular components gets initialized only once which is probably why this is happening but is there a way to get past this issue?
Thanks
I have managed to find an answer to my question. The trick is to use the fully qualified URL when importing and deleting the module like below:
System.import('protocol//domainname:portnumber/modulename/filename')
System.delete('protocol//domainname:portnumber/modulename/filename')
Hope this helps someone.
Maybe AngularJS bootstrap interfers with angular2.
https://angular.io/guide/upgrade#bootstrapping-hybrid-applications explains how bootstrap hybrid applications.
You can also try to migrate Angular 2 to Angular 5.
Well, this might be a long shot. But have you tried making your component to implement OnChanges and OnInit. You can call ngOnInit inside ngOnChanges to reinitialize the component again.
//this was my fix for a similar problem
In component, say:
#Component({
selector: 'app-myapp',
templateUrl: './myapp.component.html',
styleUrls: ['./myapp.component.scss']
})
export class MyappComponent implements OnInit, OnChanges {
ngOnChanges() {
ngOnInit(){}; //reinitialize when onchanges is called again
}
}
You should also consider https://angular.io/api/core/OnChanges#usage-notes.
hope it helps

Meteor handle.ready() in render() not triggering rerender of component

I have the following code in my render method:
render() {
return (
<div>
{this.props.spatulaReady.ready() ? <p>{this.props.spatula.name}</p> : <p>loading spatula</p>}
</div>
)
}
Which according to my understanding, checks if the subscriptionhandle is ready (data is there) and displays it. If no data is available, it should display a simple loading message. However, when I first load the page this snippet is on, it get's stuck on the loading part. On a page reload the data (usually) displays fine.
If I check the spatulaReady.ready() when the page first loads and while the display is stuck on 'loading spatula', and the data that should be there, the handle reports as ready and the data is there like it is supposed to be. If I refresh the page it all displays fine as well. The problem is, this way of checking for data and rendering if it has arrived has worked fine for me in the past. Is it because the render method is not reactive? Because handle.ready() should be reactive.
What makes it even weirder is that it sometimes DOES correctly display the data on page load, seemingly at random.
CreateContainer code:
export default createContainer(props => {
return {
user: Meteor.user(),
spatulaReady: Meteor.subscribe('spatula.byId', props.deviceId),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
Publication code:
Meteor.publish('spatula.byId', function(deviceId) {
const ERROR_MESSAGE = 'Error while obtaining spatula by id'
if (!this.userId) //Check for login
throw new Meteor.Error('Subscription denied!')
const spatula = SpatulaCollection.findOne({_id: deviceId})
if(!spatula) //No spatula by this id
throw new Meteor.Error(403, ERROR_MESSAGE)
if(spatula.ownedBy != this.userId) //Spatula does not belong to this user
throw new Meteor.Error(403, ERROR_MESSAGE)
return SpatulaCollection.find({_id: deviceId})
})
I know I'm missing a piece of the puzzle here, but I've been unsuccessful at finding it. If you don't know the solution to my specific problem, pointing me in the right direction with another way of waiting for the data to arrive before displaying it is also greatly appreciated.
EDIT: After doing some trial-and-error and reading various other posts somewhat related to my project, I figured out the solution:
export default createContainer(props => {
const sHandle= Meteor.subscribe('spatula.byId', props.deviceId)
return {
user: Meteor.user(),
spatulaReady: sHandle.ready(),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
It still makes no sense to me that moving the ready() call to create container fixed all my problems though.
As you figured out, moving the .ready() call to createContainer fixes the problem. This is because Meteor reactivity only works when you call a reactive data source (a reactive function), such as collection.find() or subscriptionHandle.ready() within a reactive context, such as Tracker.autorun or createContainer. Functions within the React component, including render, are not reactive contexts from Meteor's perspective.
Note that React and Meteor reactivity are two different things. React's reactivity works simply so that whenever a component's props or state change, it's render function is re-run. It does not understand anything about Meteor's reactive data sources. Since createContainer (that is re-run by Meteor reactivity when reactive data sources in it change) simply passes props to the underlying component, the component is re-rendered by React when the props given from createContainer change.

Angular 2 setting a new value does not trigger an input change event

I'm running into a weird case that only seems to happen upon first loading a component on a heavily based component page (loading 30+ components).
#Component{
selector: <parent-component>
template: `<child-component [myObject]=myObject>
}
export class ParentComponent {
private myObject:DTOValue;
constructor(service:MyService){
service.getDTOValue().subscribe((dtoValue:DTOValue) => {
this.myObject = dtoValue;
});
}
}
#Component{
selector: <child-component>
template: `<div></div>
}
export class ChildComponent {
#Input set myObject(value:DTOValue) => {//do something};
constructor(){
}
}
In this code, the Parent is going to get a value to a child as an input. This value comes from a request at a later time, so when the child is first initialized, the input could be undefined. When the value does get returned from the request and is set on the variable myObject, I'd expect that the child component would receive an input event being triggered. However, due to the timing, it seems like this is not always the case, especially when I first load a page that contains a lot of files being loaded.
In the case that the child component doesn't receive the input, if I click else where on my page, it seems to now trigger the change detection and will get the input value.
The 2 possible solutions I can think of that would require some large code changes so I want to make sure I choose the right now before implement them.
Change the input to be an Subject, so that I push the input value which should ensure that a correct event is triggered(this seems like overkill).
Use the dynamic loader to load the component when the request as return with the correct value (also seems like overkill).
UPDATE:
Adding a plnker: http://plnkr.co/edit/1bUelmPFjwPDjUBDC4vb, you can see in here that the title seems to never get its data bindings applied.
Any suggestions would be appreciated.
Thanks!
If you can identify where the problem is and appropriate lifecycle hook where you could solve it, you can let Angular know using ChangeDetectorRef.
constructor(private _ref: ChangeDetectorRef)
method_where_changes_are_overlooked() {
do_something();
// tell angular to force change detection
this._ref.markForCheck();
}
I had a similar issue, only with router - it needed to do redirect when/if API server goes offline. I solved it by marking routerOnActivate() for check...
When you trigger change detection this way a "branch" of a component tree is marked for change detection, from this component to the app root. You can watch this talk by Victor Savkin about this subject...
Apologize, the issue ended up being my interaction with jQuery. When I triggered an event for a component to be loaded, inside of the jQuery code, it wouldn't trigger the life cycle. The fix was after the code was loaded to then call for a change detection.

Resources