I have a shared variable I want to declare in both component and I want the components to have shared values. I was wondering if this can be done.
A.html
...
<button (click)="sendInfo(a,b,c)" > </button>
...
B.html
...
<div *ngIf="showData" > {{loadData()}} </div>
...
A.component.ts
...
showData = false;
sendInfo(a: string, b:string, c:string) {
//calling webservice
showData = true;
}
...
B.component.ts
...
showData = false;
//when button from A.html clicked
showData = true;
So in B.component.ts I want to set the showData to true if the button from A.html is clicked.
How does B.component.ts know when A.component.ts has been changed?
How do I set this shared variable between two component?
If you want to share a variable between two components use a service and observable using ReplaySubject:
export class Service {
sharedVariable$ = new ReplaySubject(1);
updateValue(value) {
this.sharedVariable$.next(value);
}
}
In components inject the service:
class Component {
constructor(public service: Service) {}
}
And use in html:
<span>{{service.sharedVariable$ | async}}</span>
<button (click)="service.updateValue(55)">set 55</button>
This is much better than to share variable using component bindings and future proof.
Related
I have the below partial HTML in my Material Table Angular application.
//HTML
<button mat-icon-button class="mat-18" (click)="updateStatus(element.id)"
[disabled]="validateToWork(element.reachedTime)">
<mat-icon>edit</mat-icon>
</button>
//Typescript
validateToWork(reachedTime:any):boolean{
if(reachedTime&& new Date(this.datePipe.transform(reachedTime, DATE_WITH_TIME)) < new Date())
return true;
else
return false;
}
The above code works fine and the edit button is disabled. But what i observed is when i have a console.log(reachedTime) in the typescript like,
console.log(reachedTime)
if(reachedTime&& new Date(this.datePipe.transform(reachedTime, DATE_WITH_TIME)) < new Date())
I could see the event is triggered on every mouseover i do on the table. Its weird for me and not sure. How can i stop that?
Below is the console log i have
Any help ?
Why
One of the rule with angular, is that you should try to avoid using method inside the html.
doing so
<button mat-icon-button class="mat-18" (click)="updateStatus(element.id)"
[disabled]="validateToWork(element.reachedTime)">
<mat-icon>edit</mat-icon>
</button>
Will force [disabled] to be recalculated each time you make any interaction with the page, mouseover being one.
To test this out, just scroll or click somewhere, you should have the same result.
How to fix it
Add the logic inside a pipe
import { DatePipe } from '#angular/common'
import { Pipe, PipeTransform } from '#angular/core'
import { DATE_WITH_TIME } from 'from/some/where'
#Pipe({
name: 'validateToWork',
})
export class ValidateToWorkPipe implements PipeTransform {
constructor(private datePipe: DatePipe) {}
transform(reachedTime: any): boolean {
// <- do add a definition, try to never use any
if (reachedTime && new Date(this.datePipe.transform(reachedTime, DATE_WITH_TIME)) < new Date())
return true
else return false
}
}
And use it in your code like follow
<button mat-icon-button class="mat-18" (click)="updateStatus(element.id)"
[disabled]="element.reachedTime | validateToWork">
<mat-icon>edit</mat-icon>
</button>
Further more
Do not forget to declare it in your module under the declarations: [] or in the imports: [] if you did create a module for that specific pipe
If you declares it directly, you won't be able to use it somewhere else.
If this is an object, you will have to manually trigger the update
The manual solutions for Auto Reloading the HTML page of a specific component:
Either by navigating to the HTML page on click.
Or calling the ngOnInit() of that component on click.
I am doing it manually using a click event from the HTML code as follows:
HTML Code: app.component.html
<button (click) = reloadPage()>
TS Code: app.component.ts
reloadPage() {
// Solution 1:
this.router.navigate('localhost:4200/new');
// Solution 2:
this.ngOnInit();
}
But I need to achieve this automatically. I hope I am clear. The page should auto-reload after some specific interval and call the ngOnInit() on each interval.
Add correct call to setInterval anywhere in your call:
setInterval(() => reloadPage(), 150000); and inside the method reloadPage put the same logic you have for the button.
An example:
Just put the reloadPage function call inside the constructor:
export class SomeComponent {
constructor() {
setInterval(() => this.reloadPage(), 150000);
}
reloadPage() {
// anything your button doeas
}
}
also note, that correct call of setInterval would be:
setInterval(() => this.reloadPage(), 150000);
Note: My answer just fixes the code you presented. But it seems there is some bigger logical misunderstanding of "reloading page" in angular and using ngOnInit
I'm using Material - Angular2 Stepper, and I have additional steps that I want to add/enable depending on what the user selects in the first step.
I tried the following:
- Load the additional forms into an array,
- then loop through it in the template with *ngFor
<mat-vertical-stepper linear>
<mat-step [stepControl]="firstForm" label="First">
<!-- Some form controls -->
</mat-step>
<mat-step *ngFor="let f of additionalForms" [stepControl]="f.form"
[label]="f.label">
<!-- Additional Steps -->
</mat-step>
</mat-vertical-stepper>
This works well for adding new steps, the problem is I can't remove them. If the user happened to come back to first form, and uncheck something, these additional steps wouldn't be required.
So trying something like: this.additionalForms = [] doesn't remove the steps. (until you click on one of the "removed" steps, then it throws an error: Cannot read property 'editable' of undefined, and only then, they're removed visually)
I also tried doing ChangeDetectorRef.detectChanges()
and tried wrapping into the NgZone.run()
but made no difference
Any solutions for this?
So I managed with this work-around:
https://github.com/angular/material2/issues/7700#issuecomment-336138411
1) Make a reference to the stepper:
<mat-vertical-stepper #stepper></mat-vertical-stepper>
2) Then, on the .ts side:
import { ViewChild } from '#angular/core';
import { MatVerticalStepper } from '#angular/material';
#ViewChild('stepper') stepper: MatVerticalStepper;
clearAdditionalForms(): void {
this.inventoryForms = [];
this.stepper._stateChanged(); // <- this : Marks the component to be change detected.
}
This is calling a private method which is probably a really bad idea, so if you have a better/correct solution, let me know, and I'll change the answer
A slightly more angular way, avoiding the private method way is to record what you need to do on the form control used by the step. So for instance let's say we have a step:
<mat-step [stepControl]="secondFormGroup">
<form [formGroup]="secondFormGroup">
<!-- your controls here -->
</form>
</mat-step>
Then define your form group:
this.secondFormGroup = this._formBuilder.group({
check: [false, Validators.requiredTrue]
});
We have now defined a pseudo element "check", that will be validated by the step.
Let's say we set something with a click function:
doClick(item) {
this.secondFormGroup.controls.check.setValue(item === 'thevalue');
}
Angular material will now do the rest, you will not be able to move past the step until item === thevalue.
Add *ngIf in each step
<mat-step *ngIf="*expression*"></mat-step>
Also, If you want to do not return the privously, you can use stepper's editable property as below
<mat-vertical-stepper linear>
<mat-step [stepControl]="firstForm" label="First" [editable]="false">
<!-- Some form controls -->
</mat-step>
<mat-step *ngFor="let f of additionalForms" [stepControl]="f.form"
[label]="f.label">
<!-- Additional Steps -->
</mat-step>
</mat-vertical-stepper>
based on https://material.angular.io/components/stepper/overview#editable-step
Angular Material 8.2.3
Best would be a [disabled] option, but incredibly they didn't add! So I tried all and I ended up a clean way to customize the step-headers:
To show/hide a step of course simply use *ngIf (what else?).
To disable steps dynamically based on user clicks / state of the store:
With great results: no hover background effect, cursor is normal, single step-header unclickable, still looks with full color: not opaque.
steps: Array<HTMLElement> = [];
subscriptions: Array<Subscription> = [];
ngAfterViewInit() {
// Needs continuous monitoring
this.subscriptions.push(
this.<observable>.pipe(
tap((data: Data) => {
// IMPORTANT: If you have an *ngIf on the steps,
// you have to sync the references of the HTML elements
this.syncHTMLSteps();
this.updateStepState(1, false); // Always disabled
if (data.isXXX) {
this.updateStepState(5, false);
} else if (data.isYYY) {
this.updateStepState(2, false);
this.updateStepState(5, true);
}
})
).subscribe());
}
ngOnDestroy() {
this.subscriptions.forEach((subscription) => {
if (subscription) {
subscription.unsubscribe();
}
});
}
/**
* Reads from the Dom the list of HTML elements for the steps.
*/
private syncHTMLSteps() {
this.steps = [];
let increment = 1;
let stepper: HTMLElement = document.querySelector('.mat-stepper-vertical');
if (!stepper) {
increment = 2; // 2, because Angular adds 2 elements for each horizontal step
stepper = document.querySelector('.mat-horizontal-stepper-header-container');
}
for (let i = 0; i < stepper.children.length; i += increment) {
this.steps.push(stepper.children[i] as HTMLElement);
}
}
/**
* Enable/Disable the click on the step header.
*
* #param step The step number (starts from 1)
* #param enabled The new state
*/
private updateStepState(step: number, enabled: boolean) {
// If you prefer to start using step 0, remove -1 here
this.steps[step - 1].style.pointerEvents = enabled ? '' : 'none';
}
I have a problem with asynchronous HTTP calls in Angular 4 using typescript/components... I create an array of objects, and in the HTML I have checkboxes next to the objects. Now I want certain objects to be checked, by executing a function in angular. However when I do
(document.getElementById(id) as HTMLInputElement).checked = true;
In my component.ts.
It can't find the element however when I do the same code in a function that executes when you push a button it works. So the problem is that the HTML is not fully loaded when I execute the function. How can I make sure the HTML is fully loaded?
Yeah You shouldn't be manipulating the DOM.
Tag your HTML element in the html using hash.
<input ... #inputname />
Retrieved in the ts controller component.
#ViewChild('inputname') theinput;
Check after view init. ngAfterViewInit if it is checked
ngAfterViewInit() {
...
(this.form as HTMLInputElement).checked
...
}
Consider this as the last option since I wouldn't recommend direct DOM manipulation in Angular. But if you are still facing the issue, use can use my solution as a work around.
In constructor ,
let interval = setInterval(() => {
let flag = self.checkFunction();
if (flag)
clearInterval(interval);
}, 100)
Now create the function
checkFunction() {
if(document.getElementById(id)){
(document.getElementById(id) as HTMLInputElement).checked = true;
return true;
}
return false;
}
My final objective is don't have to write HTML like this:
<div id='counter'>
{{counter}}
</div>
<div>
<button
id="startButton"
on-click="{{start}}">
Start
</button>
<button
id="stopButton"
on-click="{{stop}}">
Stop
</button>
<button
id="resetButton"
on-click="{{reset}}">
Reset
</button>
</div>
I would like to know if it is possible to create a Polymer-element without using HTML. For example I tried this:
#CustomTag('tute-stopwatch')
class TuteStopWatch extends PolymerElement {
ButtonElement startButton,
stopButton,
resetButton;
#observable String counter = '00:00';
TuteStopWatch.created() : super.created() {
createShadowRoot()..children = [
new DivElement()..text = '{{counter}}',
new DivElement()..children = [
startButton = new ButtonElement()..text = 'Start'
..onClick.listen(start),
stopButton = new ButtonElement()..text = 'Stop'
..onClick.listen(stop),
resetButton = new ButtonElement()..text = 'Reset'
..onClick.listen(reset)
]
];
}
}
Previous code creates HTML and shadow root correctly, but it doesn't create the binding between the #observable counter and the text of the DivElement.
I know that this is caused because I am trying to create the shadow root after the element has been instantiated/created. So that I should create the template of the element in other place before the template has been bound with its observable.
You can write a manual data binding like this:
changes.listen((changes) {
for (var change in changes) {
if (change.name == #counter) {
myDivElement.text = change.newValue;
}
}
});
changes is a property of the Observable class, which PolymerElement mixes in. (This is difficult to see in the API reference, as it currently doesn't show a class' mixins or the mixed in properties and methods.)
Polymer seems to be mostly about enabling declarative html based bindings. It may be worth exploring using custom elements and shadow dom directly, as you're not really using polymer for anything in this example. To do this you need to change the class definition to:
class TuteStopWatch extends HtmlElement with Observable {
...
}
And register your element with document.register(). You also need to include the polymer.js polyfill for custom elements.