I'm working on angular app.
In my service file, I created a function configure. And it as been called in AfterViewInit in an component.
But On load time, this.config is undefined, If I use it inside setTimeOut I could able to access the value of the this.config.
The below code works,
configure() {
setTimeout(() => {
if(this.config) {
this.apply();
}
}, 200);
}
Is there any better way to do it ? without using setTimeOut.
Please help
try with markForCheck method
constructor(private cdr: ChangeDetectorRef) {}
ngOnInit(){
{
this.cdr.markForCheck();
this.configure()
}
configure(){
if(this.config) {
// your code
}
}
add the below code to #Component decorator
changeDetection: ChangeDetectionStrategy.OnPush,
Can you please try with below code
configure() {
var self = this;
setTimeout(() => {
if(self.config) {
self.apply();
}
}, 200);
}
Related
I'm Facing an issue with the window.postMessage is firing multiple times whenever I returned to that component so I would like to get a solution for this issue. Thank you.
ngOnInit() {
this.service.userDetails = JSON.parse(localStorage.getItem('userDetails'))
window["ReactNativeWebView"] && window["ReactNativeWebView"].postMessage(JSON.stringify({"change": false}))
console.log("notifyReact")
if(this.service.userDetails==null)
this.router.navigate(['health/login'])
else {
this.getPatientList()
this.getDoctorList()
}
}
Well, ngOnInit is fired every time the component intializes, so whenever you return to it that piece of code is going to be executed.
An easy solution would be having a little service injected in root that holds a variable such as:
#Injectable({ providedIn: 'root' })
export class MyService
initialized: boolean = false;
}
and then in your component:
constructor(private myService:MyService) {}
ngOnInit() {
this.service.userDetails = JSON.parse(localStorage.getItem('userDetails'))
if(!myService.initialized) {
window["ReactNativeWebView"] && window["ReactNativeWebView"].postMessage(JSON.stringify({"change": false}))
console.log("notifyReact")
myService.initialized = true;
}
if(this.service.userDetails==null)
this.router.navigate(['health/login'])
else {
this.getPatientList()
this.getDoctorList()
}
I am trying to share data between components using the rxjs subject and i've used that data in component
Component.html
<div class="spinner-container" *ngIf="loading">
<div class="spinner-item">
<nx-spinner nxSize="large"></nx-spinner>
</div>
</div>
component.ts
ngOnInit(){
setTimeout(()=>{
this.commonService.spinnerTrigger.subscribe((trigger)=>{
this.loading = trigger;
})
},100)
}
Here is the error
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed
after it was checked. Previous value: 'ngIf: false'. Current value:
'ngIf: true'.
I found a workaround using changedetectref but I don't think its good practice is ther any other way to solve this issue
You can manually trigger change detection using the detectChanges() method of the ChangeDetectorRef
Try like this:
import { ChangeDetectorRef} from '#angular/core';
constructor(private cdr: ChangeDetectorRef) { }
ngOnInit(){
setTimeout(()=>{
this.commonService.spinnerTrigger.subscribe((trigger)=>{
this.loading = trigger;
if (this.cdr && !(this.cdr as ViewRef).destroyed) {
this.cdr.detectChanges();
}
})
},100)
}
Making the next callback async worked for me once:
this.commonService.spinnerTrigger.subscribe(async (trigger) => {
this.loading = await trigger;
});
Or adding a zero delay:
this.commonService.spinnerTrigger.pipe(delay(0)).subscribe((trigger) => {
this.loading = trigger;
});
This is an open issue in Github,
Github issue => https://github.com/angular/angular/issues/15634
And they provided a workaround using setTimeout() for now and still there aren't any updates regarding this issue.
And also you can try changeDetector that may solve your issue.
import { ChangeDetectorRef } from '#angular/core';
constructor(private cdRef:ChangeDetectorRef) {}
ngAfterViewChecked()
{
this.cdRef.detectChanges();
}
I don't see any need here to mess around with change detection / setTimeout (which triggers change detection).
Stackblitz
Use a spinner service which parent and child can use.
spinner.service.ts
#Injectable()
export class SpinnerService {
private loading = new BehaviorSubject<boolean>(true)
loading$: Observable<boolean> = this.loading.asObservable()
setSpinner(bool: boolean) {
this.loading.next(bool)
}
}
Example - Component setting spinner
ngOnInit() {
this.service.getChildData().pipe(
// handle any errors
catchError(err => {
console.log('Error caught: ', err)
this.data = err
return throwError(err)
}),
// no matter what set spinner false
finalize(() => {
this.spinnerService.setSpinner(false)
}),
// subscription clean up
takeUntil(this.destroyed$)
).subscribe(data => this.data = data)
}
Example - parent / container displaying spinner
ngOnInit() {
this.loading$ = this.spinnerService.loading$
this.spinnerService.setSpinner(true) // if needed
}
<div *ngIf="loading$ | async">
I am a spinner
</div>
I have a sample service:
export class ProjectActionService extends ActionService {
constructor() {
'ngInject'
super($state)
}
getProjects() {
// Call API...
}
}
I would like to change the value of a variable (this.showLoader) that exists in a controller from the getProjects () method.
Controller:
export class ProjectComponent {
constructor() {
'ngInject'
}
$onInit() {
this.showLoader = false
}
}
what is the best way to do it, with a multiple inheritance (mixin), a directive ...?
This is not an appropriate way to deal with it. Your service method should only call the api and get the data and not change controller variables. One way to show/hide loader is changing the boolean before calling and change again after getting the response. A sample inside controller after injecting you service:
this.showLoader = true;
this.ProjectActionService.getProjects().then(response => {
...
})
.finally(() => this.showLoader = false);
If I have an Angular 2 component and I get data from a service that returns an async promise or observable how can I then call a method in the component to display that data?
#Component({
moduleId: module.id,
selector: 'charts',
templateUrl: 'charts.component.html',
providers: [DataService]
})
export class ChartsComponent implements OnInit {
constructor(private dataService:DataService)
ngOnInit() {
this.getData();
}
getData(){
this.dataService.getData().then(function (data) {
this.drawChart(data);
});
}
drawChart(){
//implement drawing chart
}
}
The problem is that inside a promise "this" in "this.drawChart()" no longer refers to the ChartsComponent class. How can I call a class method post promise?
Also, I cant put drawChart() inside the promise because it needs to use other class properties.
When you use Arrow functions, the this is kept:
getData(){
this.dataService.getData().then((data) => { // <-- changed here
this.drawChart(data);
});
}
There are 2 solutions:
1) using "self":
var self = this;
ngOnInit() {
self.getData();
}
getData(){
self.dataService.getData().then(function (data) {
self.drawChart(data);
});
}
2) using "bind method" (or something like that):
.then(function (data) {
this.drawChart(data);
}).bind(this)
you can find so much information about this method, for example: Use of the JavaScript 'bind' method
I prefer first solution, because it helps make code more transparent.
I have a fairly typical, simple ng2 component that calls a service to get some data (carousel items). It also uses setInterval to auto-switch carousel slides in the UI every n seconds. It works just fine, but when running Jasmine tests I get the error: "Cannot use setInterval from within an async test zone".
I tried wrapping the setInterval call in this.zone.runOutsideAngular(() => {...}), but the error remained. I would've thought changing the test to run in fakeAsync zone would solve the problem, but then I get an error saying XHR calls are not allowed from within fakeAsync test zone (which does make sense).
How can I use both the XHR calls made by the service and the interval, while still being able to test the component? I'm using ng2 rc4, project generated by angular-cli. Many thanks in advance.
My code from the component:
constructor(private carouselService: CarouselService) {
}
ngOnInit() {
this.carouselService.getItems().subscribe(items => {
this.items = items;
});
this.interval = setInterval(() => {
this.forward();
}, this.intervalMs);
}
And from the Jasmine spec:
it('should display carousel items', async(() => {
testComponentBuilder
.overrideProviders(CarouselComponent, [provide(CarouselService, { useClass: CarouselServiceMock })])
.createAsync(CarouselComponent).then((fixture: ComponentFixture<CarouselComponent>) => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
// some expectations here;
});
}));
Clean code is testable code. setInterval is sometimes difficult to test because the timing is never perfect. You should abstract the setTimeout into a service that you can mock out for the test. In the mock you can have controls to handle each tick of the interval. For example
class IntervalService {
interval;
setInterval(time: number, callback: () => void) {
this.interval = setInterval(callback, time);
}
clearInterval() {
clearInterval(this.interval);
}
}
class MockIntervalService {
callback;
clearInterval = jasmine.createSpy('clearInterval');
setInterval(time: number, callback: () => void): any {
this.callback = callback;
return null;
}
tick() {
this.callback();
}
}
With the MockIntervalService you can now control each tick, which is so much more easy to reason about during testing. There's also a spy to check that the clearInterval method is called when the component is destroyed.
For your CarouselService, since it is also asynchronous, please see this post for a good solution.
Below is a complete example (using RC 6) using the previously mentioned services.
import { Component, OnInit, OnDestroy } from '#angular/core';
import { CommonModule } from '#angular/common';
import { TestBed } from '#angular/core/testing';
class IntervalService {
interval;
setInterval(time: number, callback: () => void) {
this.interval = setInterval(callback, time);
}
clearInterval() {
clearInterval(this.interval);
}
}
class MockIntervalService {
callback;
clearInterval = jasmine.createSpy('clearInterval');
setInterval(time: number, callback: () => void): any {
this.callback = callback;
return null;
}
tick() {
this.callback();
}
}
#Component({
template: '<span *ngIf="value">{{ value }}</span>',
})
class TestComponent implements OnInit, OnDestroy {
value;
constructor(private _intervalService: IntervalService) {}
ngOnInit() {
let counter = 0;
this._intervalService.setInterval(1000, () => {
this.value = ++counter;
});
}
ngOnDestroy() {
this._intervalService.clearInterval();
}
}
describe('component: TestComponent', () => {
let mockIntervalService: MockIntervalService;
beforeEach(() => {
mockIntervalService = new MockIntervalService();
TestBed.configureTestingModule({
imports: [ CommonModule ],
declarations: [ TestComponent ],
providers: [
{ provide: IntervalService, useValue: mockIntervalService }
]
});
});
it('should set the value on each tick', () => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
let el = fixture.debugElement.nativeElement;
expect(el.querySelector('span')).toBeNull();
mockIntervalService.tick();
fixture.detectChanges();
expect(el.innerHTML).toContain('1');
mockIntervalService.tick();
fixture.detectChanges();
expect(el.innerHTML).toContain('2');
});
it('should clear the interval when component is destroyed', () => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
fixture.destroy();
expect(mockIntervalService.clearInterval).toHaveBeenCalled();
});
});
I had the same problem: specifically, getting this errror when a third party service was calling setInterval() from a test:
Error: Cannot use setInterval from within an async zone test.
You can mock out the calls, but that is not always desirable, since you may actually want to test the interaction with another module.
I solved it in my case by just using Jasmine's (>=2.0) async support instead of Angulars's async():
it('Test MyAsyncService', (done) => {
var myService = new MyAsyncService()
myService.find().timeout(1000).toPromise() // find() returns Observable.
.then((m: any) => { console.warn(m); done(); })
.catch((e: any) => { console.warn('An error occured: ' + e); done(); })
console.warn("End of test.")
});
What about using the Observable? https://github.com/angular/angular/issues/6539
To test them you should use the .toPromise() method