1

In my angular 12 app Im trying to return some api data before any of my other components load (including the app component). I want to place some returned items in local storage that will be able to reference img urls and other style attributes across the app globally.

I have implemented an APP_INITIALZER class and have called one of our endpoints to grab the mentioned style data. Since promises are being depreciated I am trying to return the items with an observable since they are now available to app initializers in v12.

Here is some of my code:

Method calling app loader in module.ts

export function serviceLoader(appLoader: AppLoaderService){
  return ()=> appLoader.init();
}

AppLoader Init Method:

init(){
return  this.styleService.getStyles()
       .subscribe(res=>{
          console.log('DATA: ', res);
          this.localStorageService.portal = {
                      color: res.portalPrimaryColour,
                      primaryImage: res.portalImageUrl,
                      logo: res.portalLogoUrl
          };
          console.log('portal: ', localStorage.getItem("portal"));       
        },
        err =>{
        this.localStorageService.portal = {
                        color: 'blue',
                        primaryImage: 'assets/images/img.png',
                        logo: 'assets/images/logo.png' 
            };         
        });
}

My issue is that I cant figure out an elegant way set local storage values AFTER any data OR an error has come from the backend. The app intializer will not wait for the .subscribe res or err functions to come back before returning to the app (I understand this is a limit of an Observable and have also tried .map).

I can ensure data comes back by merely not subscribing to the getStyles() method and doing some logic inside it like this:

this.styleService.getStyles()

getStyles Method:

  getStyles() : Observable<any>{
   return this.http.get<any>(this.apiService.baseUrl + 'portal_style', httpOptions)
          .pipe(tap((data) => 
          {
            console.log('DATA: ', data);
            this.localStorageService.portal = {
                        color: data.portalPrimaryColour,
                        primaryImage: data.portalImageUrl,
                        logo: data.portalLogoUrl, 
            };
            console.log('portal: ', localStorage.getItem("portal"));
          }
}

However, with that way of doing it I am having issues with adding error catching and custom logic to errors (such as adding hardcoded local storage values). Not to mention that the Observable must finish or the app will not bootstrap.

So I guess my question is - based off the above - does anyone know an elegant way of returning an observable to an app initializer and doing custom logic when its back (and do different logic and handle errors when they come back)?

Thanks!

3
  • 1
    Have you looked at catchError RxJs operator? Seems enough to me in combination with your tap operator. Commented Jul 31, 2021 at 19:11
  • I have tried using catchError but this way the observable is kept alive and the error is not returned quick enough. If there is a server error I need to do some custom log and assign some values to be returned to the app immediately before the initializer ends. Commented Aug 2, 2021 at 22:19
  • OK, it turns out that throwing the error on catchError stops the initializer from bootstrapping. That said, if the catchError was not present on the pipe it would still not bootstrap either, so i removed the ThrowError from the return statement and just added a string and it works. Commented Aug 3, 2021 at 11:30

2 Answers 2

1

As said in the documentation, APP_INITIALIZER can be a function which returns a Promise or an Observable. However you are not returning an observable nor a promise, you are returning the Subscription of a stream. That's why the Angular application bootstraps even when your request is not finished.

Sign up to request clarification or add additional context in comments.

Comments

0

Sounds like a race condition between the data from the API and you initializing your work based on that data. Consider getting your data first, then piping your styleService function to remove this race condition.

// DataService.ts
@Injectable()
export class DataService {
    data: Data'

    constructor(private http: HttpClient) {}
    
    getDataFromServer(): Observable<Data> {
        if (this.data) {
            return of(data);
        } else {
            return this.http.get('my/api/call').pipe(
                mergeMap(response => {
                    this.data = response;
                    return of(response);
                });
        }
    }
}


// StyleService.ts
@Injectable()
export class StyleService {
    constructor(private http: HttpClient) {}
    
    getStyles(): Observable<Data> {
        return this.http.get("my/style/call");
    }
}
    

// MyComponent.ts
@Component({
    selector: 'app-mycomponent',
    templateUrl: './myComponent.component.html',
    styleUrls: ['./myComonent.component.scss'],
})
export class myComonentComponent implements OnInit {
    private data: Data;
    
    constructor(private dataService: DataService, private styleService: StyleService) {}
    
    ngOnInit() {
         this.dataService.getData().pipe(
            mergeMap((data: Data) => {
                this.data = data;
                // save to localStorage if you want. You can also do it within the getData call but this also allows you to skip saving it in the localStorage
                return this.styleService.getStyles();
            })
           .subscribe(res => {
              console.log('DATA: ', res);
              // use this.data where needed
              // no changes were made below this line
              
              this.localStorageService.portal = {
                          color: res.portalPrimaryColour,
                          primaryImage: res.portalImageUrl,
                          logo: res.portalLogoUrl
              };
              
              console.log('portal: ', localStorage.getItem("portal"));       
            },
            err => {
                this.localStorageService.portal = {
                                color: 'blue',
                                primaryImage: 'assets/images/img.png',
                                logo: 'assets/images/logo.png' 
                    };         
            });
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.