I have a problem. After searching for hours I cannot find an explanation for this. I want to display a modal (from primeNG) and show it when the user clicks a button. This button calls (with an id) to my API REST and brings information, very simple. I receive the information, but when the modal should show, this doesn't happen.
map.component.ts
export class MapComponent implements OnInit {
public alteraciones: any[];
public alteracion: any = {};
display: boolean = false;
/*...*/
generateData(map: L.map) {
const data: any[] = [];
let marker: any;
L.geoJson(this.alteraciones, {
pointToLayer: (feature, latlng) => {
marker = L.marker(latlng, {
icon: this.getIconMarker(feature.properties.tipo_alteracion)
});
marker.on('click', (e) => {
this.getInfoAlteracion(feature.properties.id_alteracion); // <==
});
data.push(marker);
}
});
/*...*/
}
/**...**/
getInfoAlteracion(id_alteracion: string) {
this.mapService.getInfoAlteracion(id_alteracion).subscribe(
result => {
this.alteracion = result;
console.log(this.alteracion); // < == Information OK
this.display = true; // <== this variable should change but doesn't
},
err => console.log(err)
);
}
}
map.component.html
<p-dialog header="Info" [(visible)]="display" modal="modal" width="500" [responsive]="true">
<!--some code-->
<p-footer>
<button type="button" pButton icon="fa-close" (click)="display=false" label="Cerrar"></button>
</p-footer>
</p-dialog>
However, when I recompile or when I turn off the server, the value of the display variable changes, and it shows me the modal. I cannot find an explanation, any idea?
EDIT
Posible conflicts:
#asymmetrik/ngx-leaflet: 3.0.2
#asymmetrik/ngx-leaflet-markercluster: 1.0.0
EDIT 2
I also added a new marker with a new variable to change but doesn't work too. At this point, I think (and I'm 90% sure) that it's a problem of communication between component.ts and component.html.
Try to make that boolean display property public !
Finally, I solved the problem.Thanks to this link, I realized that it was a problem of compatibility between libraries. Leaflet event handlers run outside of Angular's zone, where changes to input bound fields will not be detected automatically. To ensure my changes are detected and applied, I need to make those changed inside of Angular's zone. Adding this to the code, finally, all works:
constructor(private mapService: MapService, private zone: NgZone) { }
marker.on('click', (e) => {
this.zone.run(() => {
this.getInfoAlteracion(feature.properties.id_alteracion);
});
});
data.push(marker);
}
Thanks to all for the help!
Related
I'm working on an open-source project and have encountered a bug. I'm not able to navigate the dropdown list items using the keyboard (arrow key/tab). I've written the keyboard-navigation logic, but not quite sure of how to implement it. Below is the code snippet.
.
.
.
const TopNavPopoverItem: FC<ComponentProps> = ({closePopover, description, iconSize, iconType, title, to}) => {
const history = useHistory();
const handleButtonClick = (): void => {
history.push(to);
closePopover();
};
const useKeyPress = function (targetKey: any) { // where/how am I supposed to use this function?
const [keyPressed, setKeyPressed] = useState(false);
function downHandler(key: any) {
if (key === targetKey) {
setKeyPressed(true);
}
}
const upHandler = (key: any) => {
if (key === targetKey) {
setKeyPressed(false);
}
};
React.useEffect(() => {
window.addEventListener('keydown', downHandler);
window.addEventListener('keyup', upHandler);
return () => {
window.removeEventListener('keydown', downHandler);
window.removeEventListener('keyup', upHandler);
};
});
return keyPressed;
};
return (
<button className="TopNavPopoverItem" onClick={handleButtonClick}>
<Icon className="TopNavPopoverItem__icon" icon={iconType} size={iconSize} />
<div className="TopNavPopoverItem__right">
<span className="TopNavPopoverItem__title">{title}</span>
<span className="TopNavPopoverItem__description">{description}</span>
</div>
</button>
);
};
Any workaround or fixes?
Thanks in advance.
A custom hook should always be defined at the top level of your file. It cannot be inside of a component. The component uses the hook, but doesn't own the hook.
You have a hook which takes a key name as an argument and returns a boolean indicating whether or not that key is currently being pressed. It's the right idea, but it has some mistakes.
When you start adding better TypeScript types you'll see that the argument of your event listeners needs to be the event -- not the key. You can access the key as a property of the event.
(Note: Since we are attaching directly to the window, the event is a DOM KeyboardEvent rather than a React.KeyboardEvent synthetic event.)
Your useEffect hook should have some dependencies so that it doesn't run on every render. It depends on the targetKey. I'm writing my code in CodeSandbox where I get warnings about "exhaustive dependencies", so I'm also adding setKeyPressed as a dependency and moving the two handlers inside the useEffect.
I see that you have one handler as function and one as a const. FYI it really doesn't matter which you use in this case.
Our revised hook looks like this:
import { useState, useEffect } from "react";
export const useKeyPress = (targetKey: string) => {
const [keyPressed, setKeyPressed] = useState(false);
useEffect(
() => {
const downHandler = (event: KeyboardEvent) => {
if (event.key === targetKey) {
setKeyPressed(true);
}
};
const upHandler = (event: KeyboardEvent) => {
if (event.key === targetKey) {
setKeyPressed(false);
}
};
// attach the listeners to the window.
window.addEventListener("keydown", downHandler);
window.addEventListener("keyup", upHandler);
// remove the listeners when the component is unmounted.
return () => {
window.removeEventListener("keydown", downHandler);
window.removeEventListener("keyup", upHandler);
};
},
// re-run the effect if the targetKey changes.
[targetKey, setKeyPressed]
);
return keyPressed;
};
I don't know you intend to use this hook, but here's a dummy example. We show a red box on the screen while the spacebar is pressed, and show a message otherwise.
Make sure that the key name that you use when you call the hook is the correct key name. For the spacebar it is " ".
import { useKeyPress } from "./useKeyPress";
export default function App() {
const isPressedSpace = useKeyPress(" ");
return (
<div>
{isPressedSpace ? (
<div style={{ background: "red", width: 200, height: 200 }} />
) : (
<div>Press the Spacebar to show the box.</div>
)}
</div>
);
}
CodeSandbox Link
I am trying to display a routerlink name based on a condition. I want to display the div section routerLink name if condition is true.If i check {{isNameAvailable}}, first it displays false and after this.names got the values it shows true.Since in the component getDetails() method is asynchronous this.names getting the values after html template render.Therefore this routerLink does n't display.Therefore I want to display div section after some time. (That 's the solution i have) Don't know whether is there any other solution.
This is my html file code.
<main class="l-page-layout ps-l-page-layput custom-scroll bg-white">
{{isNameAvailable}}
<div class="ps-page-title-head" >
<a *ngIf ="isNameAvailable === true" [routerLink]="['/overview']">{{Name}}
</a>
{{Name}}
</div>
</main>
This is my component.ts file
names= [];
isNameAvailable = false;
ngOnInit() {
this.getDetails()
}
getDetails() {
this.route.params.subscribe(params => {
this.names.push(params.Names);
console.log(this.names);
this.getValues().then(() => {
this.isNameAvailable = this.checkNamesAvailability(this.names);
console.log(this.isNameAvailable);
});
});
}
resolveAfterSeconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 900);
});
}
checkNamesAvailability(names) {
console.log(names);
return names.includes('Sandy');
}
async getValues() {
await this.resolveAfterSeconds(900);
}
And console.log(this.isLevelAvailable); also true. What I can do for this?
1.You do not have anything to show in the HTML only the isNameAvailable, because you do not have any assignment in the Name variable.
2.It is better to use the angular build-in async pipe,
when you want to show the returned value from observables.
3.When you are using the *ngIf directive you can skip *ngIf ="isNameAvailable === true" check because the variable is boolean type, you gust write *ngIf ="isNameAvailable", it will check also for null but NOT for undefined
It is working because the *ngIf directive is responsible for checking and rendering the UI, you can see how many times the directive is checking by calling an function and print and answer in the console.
By any chance do you have changeDetection: ChangeDetectionStrategy.OnPush docs set in component annotation? That might explain this behaviour. With it Angular run change detection only on component #Input()'s changes and since in your case there were non it did not run change detection which is why template was not updated. You could comment that line to check if that was cause of the issue. You are always able to run change detection manually via ChangeDetectorRef.detectChange() docs which should solve you problem
constructor(private cd: ChangeDetectorRef) {}
...
getDetails() {
this.route.params.subscribe(params => {
...
this.getValues().then(() => {
this.isNameAvailable = this.checkNamesAvailability(this.names);
this.cd.detectChanges(); // solution
console.log(this.isNameAvailable);
});
});
}
This stackblitz show this bug and solution. You can read more about change detection here
You could use RxJS timer function with switchMap operator instead of a Promise to trigger something after a specific time.
Try the following
import { Subject, timer } from 'rxjs';
import { takeUntil, switchMap } from 'rxjs/operators';
names= [];
isNameAvailable = false;
closed$ = new Subject();
ngOnInit() {
this.getDetails()
}
getDetails() {
this.route.params.pipe(
switchMap((params: any) => {
this.names.push(params.Names);
return timer(900); // <-- emit once after 900ms and complete
}),
takeUntil(this.closed$) // <-- close subscription when `closed$` emits
).subscribe({
next: _ => {
this.isNameAvailable = this.checkNamesAvailability(this.names);
console.log(this.isNameAvailable);
}
});
}
checkNamesAvailability(names) {
console.log(names);
return names.includes('Sandy');
}
ngOnDestroy() {
this.closed$.next(); // <-- close open subscriptions when component is closed
}
I´m new on Angular and try to parse Adobe IDML (XML) files for showing them up in a browser. Let me explain that:
There is the following XML-Structure ("->" means gives info about next line):
IDML (designmap.xml) ->
Spread (spread_xy.xml) ->
Page ->
TextFrames ->
Story
Rectangle ->
Image
I have written a backend that translates these files in JSON-Arrays and I load these JSON-Arrays via HTTP. So far so good.
The following is my corresponding IDML-Service in the Frontend:
export class IdmlService {
apiUrl = environment.backendApi + 'idml/';
constructor(private http: HttpClient) { }
renderSpread(currentFolder, currentSpreadId): Observable<HttpResponse<any[]>> {
return this.http.get<MenuItem[]>(
this.apiUrl + currentFolder + '?spreadId=' + currentSpreadId, { observe: 'response' }).pipe(
retry(3)
);
}
}
I call "renderSpread"-method in my publicationComponent like this:
renderPublication(currentFolder: String, currentSpreadId: String): void {
this.idmlService.renderSpread(currentFolder, currentSpreadId)
.subscribe(resp => {
this.currentSpreadJson = resp.body;
// console.log(this.currentSpreadJson);
});
}
and bind the "currentSpreadJson" to the child component called spreadComponent in the template:
<div class="publication">
<app-spread [currentSpreadJson]="currentSpreadJson"></app-spread>
</div>
In the spreadComponent I construct my currentSpread and bind the necessary rest of JSON via "currentElementsJson" to the next child elementsComponent like this:
#Input() currentSpreadJson: any;
currentSpread: Spread;
currentElementsJson: any;
ngOnInit() {
setTimeout(() => { // THE MAIN PROBLEM
this.render();
}, 3000);
}
render(): void {
this.currentSpread = {
id: this.currentSpreadJson.idPkg_Spread.Spread['Self'],
[...some other vars...]
};
[...doing some stuff...]
this.currentElementsJson = this.currentSpreadJson.idPkg_Spread.Spread;
}
Here´s the template:
<div id="{{currentSpread.id}}" class="spread box_shadow" [ngStyle]="{'width': currentSpread.width + 'px', 'height': currentSpread.height + 'px'}">
<app-page [currentSpreadJson]="currentSpreadJson"></app-page>
<app-element [currentElementsJson]="currentElementsJson"></app-element> // <-- here
</div>
So here my question: Such an IDML could become very huge. This strategy goes deeper in the XML-Tree and the problem is that I always need to do a timeout onInit. I tried some other lifecycles like ngAfterViewInit and so on, but I think the upper strategy is not the thing I need for my project. I heared about "async await"-functions, but I don´t really know how to implement that in this context and I´m not sure if this is the solution. It would be nice if somebody could give me a hint. Thank you.
Regards, Dominik
This worked for me now. I changed this:
ngOnInit() {
setTimeout(() => { // THE MAIN PROBLEM
this.render();
}, 3000);
}
to that:
ngAfterContentChecked() {
this.render();
}
Now the nested components wait until the Content is available.
Regards
I am trying to run getResponse once when a web components finishes loading. However, when I try to run this, the debounce function just acts as an async delay and runs 4 times after 5000 ms.
static get properties() {
return {
procedure: {
type: String,
observer: 'debounce'
}
}
}
debounce() {
this._debouncer = Polymer.Debouncer.debounce(this._debouncer, Polymer.Async.timeOut.after(5000), () => {
this.getResponse();
});
}
getResponse() {
console.log('get resp');
}
What is necessary to get getResponse to run once upon the loading of the element?
Are you sure you want to use a debouncer for that? you could just use the connectedCallBack to get a one Time Event
class DemoElement extends HTMLElement {
constructor() {
super();
this.callStack = 'constructor->';
}
connectedCallback() {
this.callStack += 'connectedCallback';
console.log('rendered');
fetch(this.fakeAjax()).then((response) => {
// can't do real ajax request here so we fake it... normally you would do
// something like this.innerHTML = response.text();
// not that "rendered" get console logged before "fetch done"
this.innerHTML = `
<p>${this.callStack}</p>
<p>${response.statusText}</p>
`;
console.log('fetch done');
}).catch(function(err) {
console.log(err); // Error :(
});
}
fakeAjax() {
return window.URL.createObjectURL(new Blob(['empty']));
};
}
customElements.define('demo-element', DemoElement);
<demo-element></demo-element>
If you really need to use an observer you could also set a flag this.isLoaded in your connectedCallback() and check for that in your observer code.
This question is specific to vuejs router, however may simply be a misunderstanding of importing js objects and assigning to the window object.
I am watching for url changes on a page which works fine with the watcher code in the component file. I need to use the same watcher code for multiple components so I extracted it to its own file, assigned it to the global scope, and cannot get it to work. Here are the details:
Working code in with the watcher in the component:
watch:{
$route () {
console.log('route changed')
//was it a reset?
console.log( this.$route.query.sort)
if(this.$route.query.sort === undefined){
if(this.$route.meta.reset){
//reset was pressed... actually do nothing here
this.$route.meta['reset'] = false;
}
else{
this.loading = true;
this.searchableTable.removeResultsTable();
this.searchableTable.options.search_query = this.$route.fullPath;
this.searchableTable.updateSearchPage();
}
}
else
{
//sort change just update the table view
}
}
}
So then I extracted the watch to a file routeWatcher.js:
export default {
$route () {
console.log('route changed')
//was it a reset?
console.log(this.$route.query.sort)
if (this.$route.query.sort === undefined) {
if (this.$route.meta.reset) {
//reset was pressed... actually do nothing here
this.$route.meta['reset'] = false;
}
else {
this.loading = true;
this.searchableTable.removeResultsTable();
this.searchableTable.options.search_query = this.$route.fullPath;
this.searchableTable.updateSearchPage();
}
}
else {
//sort change just update the table view
}
}
}
then I import and use, which works fine....
import searchableTableRouteWatcher from '../../controllers/routeWatcher'
...
watch:searchableTableRouteWatcher
again works fine.
Now the problem - I want to avoid the import in multiple files, so I thought I could put it on the window as a global
in my main.js file:
import searchableTableRouteWatcher from './controllers/routeWatcher'
window.searchableTableRouteWatcher = searchableTableRouteWatcher;
Then in my component:
watch:searchableTableRouteWatcher
results in searchableTableRouteWatcher is not defined
watch:window.searchableTableRouteWatcher
results in no errors, but the code is not being called
I have a feeling it has to do with this and there is confusion on $route()
For your purpose there are 'Mixins' in Vue.js: documentation
What you can do:
create a file, say mixins/index.js:
export const routeWatcher = {
watch: {... your watcher code pasted here ... }
};
import into your component:
import { routeWatcher } from 'path/to/mixins/index';
add mixin to your component properties and methods:
<script>
export default {
mixins: [routeWatcher];
data () ...... all your original component's script content
}
Mixin's content will be merged with component's original properties and act if it was hardcoded there.
Addition after your comment:
You can also declare Mixin globally, like this:
above 'new Vue' declaration put this code:
Vue.mixin({
watch: {....}
});
This mixin will appear in every component.