How to use CachedResourceLoader as an equivalent mechanism to $templateCache in Angular2? - javascript

I know there are 2 similar questions about this but with no solution for any of them.
So I found this issue in Angular repo where they ask for the same, i.e an alternative for templateCache in Angular 2 but they close it saying you can use CachedResourceLoader.
So my question is how to use this CachedResourceLoader to replace the templateCache, I have been searching on google about this but couldn't find any related content so maybe I am not pointing to the right direction or I missed something.
The answer to this question could be a valid answer for the other 2 similar questions.
Code example for the functionality that templateCache provided in AngularJS:
Adding:
var myApp = angular.module('myApp', []);
myApp.run(function($templateCache) {
$templateCache.put('templateId.html', 'This is the content of the template');
});
Retrieval via $templateCache:
$templateCache.get('templateId.html')
Or retrieval:
myApp.component('myComponent', {
templateUrl: 'templateId.html'
});

CachedResourceLoader is existing yet undocumented Angular 2+ substitution for AngularJS $templateCache:
An implementation of ResourceLoader that uses a template cache to
avoid doing an actual ResourceLoader.
The template cache
needs to be built and loaded into window.$templateCache via a
separate mechanism.
It is supposed to work by providing ResourceLoader provider:
{provide: ResourceLoader, useClass: CachedResourceLoader}
Which was already defined in existing RESOURCE_CACHE_PROVIDER export.
And window.$templateCache is supposed to contain pairs of URLs and responses.
Since ResourceLoader should be specified before compilation, it should be provided not in application module but in compiler options.
Here is an example:
import {RESOURCE_CACHE_PROVIDER} from '#angular/platform-browser-dynamic';
import {COMPILER_OPTIONS} from '#angular/core';
window['$templateCache'] = { 'app.html': `...`};
platformBrowserDynamic({
provide: COMPILER_OPTIONS,
useValue: { providers: [RESOURCE_CACHE_PROVIDER] },
multi: true
}).bootstrapModule(AppModule)
As opposed to AngularJS $templateCache, CachedResourceLoader doesn't allow to make requests for missing templates. This is desirable behaviour most of the time. If it has to be changed, a custom implementation that extends default ResourceLoader implementation can be used instead.

Related

Using factory to create controller

I was wondering whether I can use a factory to initialize a controller and then add it to a module. Code could look something like this, but this is not working:
const controllerFactory = {
provide: DefinitionController,
useFactory: async (service: DefinitionService) => {
//initialization of controller
return new DefinitionController();
},
inject: [DefinitionService],
};
#Module({
controllers: [controllerFactory],
providers: [DefinitionService],
})
export class DefinitionModule {}
It looks like using factories for controllers is not supported, but I am not sure. There is an example of using factory for providers, but I cannot find anything for controller in documentation or on google.
It's not possible to define your controller with an async factory comparable to custom providers. You cannot add dynamic endpoints/routes unless using the native express/fastify instance:
At the moment there is no way to register a route dynamically except
by using the internal HTTP / Fastify / Express instance
There is an issue where a dynamic routing module is discussed but this will probably not be part of nest very soon:
At the moment both Kamil and I are really busy, so this issue may take
some time - except someone else takes on the task :)
But you can use the OnModuleInit lifecycle event to do static initialization:
#Injectable()
export class DefinitionController implements OnModuleInit {
onModuleInit() {
console.log(`Initialization...`);
}
It will be called once when your app starts and has access to the injected providers in your controller, e.g. your DefinitionService.

Using Angular to declare a new service instance per module?

I'm using angular. I already know that when an appmodule is importing modules which declares providers, the root injector gets them all and the service is visible to the app - globally. (I'm not talking about lazy loaded modules)
But is it possible that each module will have its own instance of the service?
I thought of maybe something like this :
#NgModule({
providers: [AService]
})
class A {
forRoot() {
return {
ngModule: A,
providers: [AService]
}
}
forChild() {
return {
ngModule: A,
providers: [AService]
}
}
}
But I don't know if it's the right way of doing it
Question
How can I accomplish service per module ?
STACKBLITZ : from my testing , they are using the same service instance
When we provide a service in a feature module that is eagerly loaded
by our app's root module, it is available for everyone to inject. - John Papa
So looks like there is no way to inject at feature-module level, as there are no module level injectors other than the one at root module.
But angular has nodes at each component level for the injector, so such a scenario will have to use coponent level-injectors I guess.
You can also have a parent component inject the service for different children sharing the same instance.
One way is to provide the services at component level. Not sure if that will work for you.
Also, check the multiple edit scenario in the docs
https://angular-iayenb.stackblitz.io
import { Component, OnInit } from '#angular/core';
import {CounterService} from "../../counter.service"
#Component({
selector: 'c2',
templateUrl: './c2.component.html',
styleUrls: ['./c2.component.css'],
providers:[CounterService]
})
export class C2Component implements OnInit {
constructor(private s:CounterService) { }
ngOnInit() {
}
}
Question
How can I accomplish service per module ?
Not with the default Injector. Default Injector keeps nodes at root level and component level, not at feature-module level. You will have to have a custom Injector if there is a real scenario.
Edit: the previous answer from me is not completely correct. The correct way to make this work is to use lazy loaded modules, provide the services there. The given service should not be provided with a static forRoot() method somewhere because then the lazy loaded module will access the root injector.
There is no actual reference to do so because that is not how angular is designed but if you want it that way you have to the opposite of
https://angular.io/guide/singleton-services#providing-a-singleton-service
and
https://angular.io/guide/singleton-services#forroot
Old not completely correct:
You simple have to declare the service you want to be single instance for each and every module within the providers meta data of each and every module. Then Angular will not reuse any instance of the service.
Giving scenario: You have two modules, ModuleA and ModuleB, both need the service but different instance, then you will declare them in the providers section of ModuleA and ModuleB.
Reference:
https://angular.io/guide/singleton-services

Externally pass values to an Angular application

I'm thinking of separating our MVC-based website's front-end into few components and I was thinking about using Angular for that purpose (e.g. creating cart application that I can include to my view afterwards).
Problem is that I need to pass few variables into that application and I'm wondering how to do that safely and professionally. I was thinking about something like:
I'm going to ng build --prod --aot
I inject all my scripts to my view
I inject variables passed to view to my app
... and "code" representation for my thoughts:
Controller:
public function viewAction()
{
$this->view->addCss('angular/app/styles.css'); // adds styles for cart app
$this->view->addJS('angular/app/scripts.js'); // adds scripts for cart app
$this->view->setJSVariable('idCustomer', 555); // sets global var idCustomer
}
View:
<!-- bunch of HTML for view ... -->
<script>
// CartApp would be an angular app.
CartApp.use([
idCustomer
]);
</script>
So my question is... would it be possible (and would it be a good solution) to get the CartApp as an object and then make some function (like use in above example) that would set/pass the data? (let's say to some globally provided service/component or anything else). Or is there any other way to do this? Like taking the variables from inside the application from the window object? (as they're going to be bound to the window anyways). Thank you.
So I was going to suggest using input bindings... I've done that before in AngularJS but was surprised to find that using input bindings on the root component isn't supported yet. You can have fun reading this giant issue: https://github.com/angular/angular/issues/1858
The best post I saw there was Rob Wormald's which had a couple of suggestions:
to the initial question, if you really want to grab data from the
root, that's possible via
https://plnkr.co/edit/nOQuXE8hMkhakDNCNR9u?p=preview - note that it's not an input (because there's no angular context outside of it to do the input...) - its a simple string attribute
which you'd need to parse yourself.
ideally though, you'd do as
#robtown suggested and actually pass the data in javascript, rather
than passing it as a DOM string and retrieving / parsing it yourself
(which has never really been a supported case in angular, despite the
explicit-warned-against usage of ng-init in angular1 to accomplish
this) - see https://plnkr.co/edit/PoSd07IBvYm1EzeA2yJR?p=preview for a
simple, testable example of how to do this.
So the 2 good options I saw were:
Add normal HTML attributes to the root component:
<app-root appData='important stuff'></app-root>
and use ElementRef to fetch them:
#Component({
selector: 'app-root'
})
export class AppComponent {
constructor(el: ElementRef) {
console.log(el.nativeElement.getAttribute('appData'));
}
}
Would probably work best if you are just dealing with strings or config flags. If you are passing JSON, you will need to manually parse it.
Have the server render the data as JavaScript and import it in your app:
Have the server render something like this to a script tag or JS file that is loaded before Angular is bootstrapped:
window.APP_DATA = { ids: [1, 2, 3] }
Tell your NgModule about it using a provider:
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppComponent } from './app.component';
#NgModule({
providers: [{ provide: 'AppData', useValue: (<any> window).APP_DATA }],
bootstrap: [AppComponent]
})
export class AppModule { }
And then use it as a normal Angular service:
import {Component, Inject} from '#angular/core';
#Component({
selector: 'app-root'
})
export class AppComponent {
constructor(#Inject('AppData') appData) {
console.log(appData.ids);
}
}

Configure Angular 2 HTTP service

I wonder what is the right way to use http service. For example all my requests to server starts with /Api.
Should I write this.http.get('/Api/SomeRoute/:id') every time, or more elegant way exists to omit Api?
Or should I create some other managerService which will use http?
Something like an endpoint url is probably a good example for a general configuration file.
In my angular 2 apps i use the dependency injection for it. In this case i have something like an app.config.ts using OpaqueToken to make it injectable:
import { OpaqueToken } from '#angular/core';
export interface IAppConfig {
appTitle: string;
endpointUrl: string;
};
export const CONFIG: IAppConfig = {
appTitle: 'MyApp',
endpointUrl: 'http://api.myrealservice.com/'
};
export const LOCALCONFIG: IAppConfig = {
appTitle: 'MyApp (LOCAL)',
endpointUrl: 'http://localhost:8080/api/'
};
export let APP_CONFIG = new OpaqueToken('app.config');
This way you can have several configs (for example for local development or production etc..) and define this as a provider in your module like this:
providers: [
...,
{
provide: APP_CONFIG,
useValue: CONFIG
},
...
]
Then i just inject this config wherever i may need it, for example in a backend service to use the endpointUrl:
...
constructor(#Inject(APP_CONFIG) private _config:Config) {
console.log(this._config.endpointUrl);
}
In your module you can just change the kind of Config you want to provide (in this example CONFIG or LOCALCONFIG for example) and don't have to change it anywhere else anymore.
Hope this helps.
After your edit, you added a second question Or should I create some other managerService which will use http?:
Short answer: yes. You should seperate logic of your components, services, etc. as much as possible.
Let's say you have a an API which serves information about cats and dogs. What you want to have in your frontend would probably be a CatService, a DogService and a BackendService or ApiService, whatever you want to call it.
The CatService and DogService are the ones your view components will interact with, for example they will have methods like getCatById() or something like that. These services will most likely interact with anohter service, the BackendService which will inject Http and use general methods like post(), get() and so on.
So your BackendService is the one who has to know the specific urls, handle the responses or errors and report back to the calling services with the results, while the other ones will just be used as a pipeline and handle specific business logic.
You are right, it is better to put repeating values in a variable. You can put the base URL in a const:
const apiUrl = '/Api/';
and then if there is a change to the URL, you only change it in one place, and also your code looks cleaner. The usage is like this:
this.http.get(apiUrl + 'SomeRoute/:id')
Of course you can also create a function that does that for you, but that may be too much abstraction.

Angular 2 hierarchical Providers in ES5

I'm getting started using Angular 2 using ES5, and I've hit a brick wall when setting up my providers. In essence, I want to make a provider depend on HTTP. Here's how i have it set up:
var Provider = ng.core.Class({
constructor: [ng.http.Http, function(http) {
// some code here that uses HTTP
}]
});
var Component = ng.core.Component({
providers: [Provider, ng.http.HTTP_PROVIDERS]
}).Class({
constructor: [Provider, function(provider) {
// some code here that uses my Provider
}]
});
I keep getting the following error: No provider for t! (e -> t)
I've omitted the rest of the boilerplate code because this is where I'm stuck. Do I have a misunderstanding about how I'm injecting my dependencies? How do I set up hierarchical dependencies in Angular 2 in ES5?
I figured it out. In the actual code I was using, I ended up having something like this: providers: [[Provider, ng.http,HttpProviders]] which shouldn't have been a nested array

Categories