Angular gRPC framework.
- two-way binding thanks to properties instead of Java-like setters / getters
- client services are wired to Angular's dependency injection
- rxjs first-class support
- typescript first-class support
- interceptors
- simple console logger
- web worker (experimental)
- easy to install, update and support thanks to npm packages
The example requires protoc, docker & docker-compose to be installed
Clone this repository and run
npm ci
npm run build
npm run examples
Now you can see following examples:
- basic grpc-web-client example at http://localhost:4201/
- worker-client example at http://localhost:4202/
The source code for the examples could be found at examples directory.
First ensure that you
- installed
protoc
: guide. - configured your backend / grpc-web proxy according to grpc-web documentation.
Then in your Angular project's root directory run
npm i -S @ngx-grpc/common @ngx-grpc/core @ngx-grpc/grpc-web-client google-protobuf grpc-web
npm i -D @ngx-grpc/protoc-gen-ng @types/google-protobuf
Where:
- @ngx-grpc/common contains common reusable types for other ngx-grpc packages
- @ngx-grpc/core contains angular specific implementation
- @ngx-grpc/grpc-web-client provides a client based on grpc-web
- @ngx-grpc/protoc-gen-ng generates the code based on your proto files
- google-protobuf is required to encode / decode the messages
- grpc-web implements the transport between the browser and grpc proxy
Add proto:generate
script to your package.json
scripts
section:
{
"scripts": {
"proto:generate": "protoc --plugin=protoc-gen-ng=./node_modules/.bin/protoc-gen-ng --ng_out=<OUTPUT_PATH> -I <PROTO_DIR_PATH> <PROTO_FILES>"
}
}
Where:
OUTPUT_PATH
- the directory your code will be generated at (please ensure the directory exists, otherwise you'll have aprotoc
error)PROTO_DIR_PATH
- the root path of your proto filesPROTO_FILES
- list of proto files to use
Example:
{
"scripts": {
"proto:generate": "protoc --plugin=protoc-gen-ng=./node_modules/.bin/protoc-gen-ng --ng_out=./src/proto -I ../proto $(find ../proto -iname \"*.proto\")"
}
}
Finally, run npm run proto:generate
every time you want to (re)generate the code
@NgModule({
providers: [
{ provide: GRPC_CLIENT_FACTORY, useClass: GrpcWebClientFactory },
],
})
export class AppModule {}
If you set GRPC_WEB_CLIENT_DEFAULT_SETTINGS
all the services will use the configuration you provide.
@NgModule({
providers: [
{ provide: GRPC_WEB_CLIENT_DEFAULT_SETTINGS, useValue: { host: 'http://localhost:8080' } as GrpcClientSettings },
],
})
export class AppModule {}
You can override the settings for each service (see below).
Every service has an injected configuration which could be found e.g. in the corresponding *.pbconf.ts
file.
E.g. for a service TestServiceClient
you need to provide the GRPC_TEST_SERVICE_CLIENT_SETTINGS
:
@NgModule({
providers: [
// the name of the token can be found in corresponding service constructor
// uses default grpcwebtext format
{ provide: GRPC_TEST_SERVICE_CLIENT_SETTINGS, useValue: { host: 'http://localhost:8080' } as GrpcClientSettings },
// or use value from environment.ts
// { provide: GRPC_TEST_SERVICE_CLIENT_SETTINGS, useValue: { host: environment.host } as GrpcClientSettings },
],
})
export class AppModule {}
To set grpcweb / binary proto format use
{ provide: GRPC_TEST_SERVICE_CLIENT_SETTINGS, useValue: { host: 'http://localhost:8080', format: 'binary' } as GrpcClientSettings },
From now on this particular service is set.
It's also handy to move configuration of all the services to a different module's providers
section and import this module into the AppModule
.
Each RPC has two corresponding methods, the first emits messages, the second - events. E.g. for rpc Echo(...)
there would be the following:
echo(...)
- returnsObservable
of messages and throws errors in case of non-zero status codes. This is the most common use-caseecho$eventStream(...)
- returnsObservable
ofGrpcEvent
s. Events could be of two kinds:GrpcDataEvent
containing the message inside andGrpcStatusEvent
containing gRPC status response. Apart from the returned data type there is important difference in the behaviour. There are no errors thrown in this stream (by design). All errors are considered to be normalGrpcStatusEvent
s. Furthermore, this method is the only one where it is anyhow possible to read the gRPC status code0
(OK
) metadata. This method is not that comfortable to use in every place, but it can do things that are not achievable with the method above.
There are two custom RxJS operators that could be used on the stream to make it easier:
throwStatusErrors
- searches for the non-zero status codes and throws them as errorstakeMessages
- searches for the messages
For usage example look at any of your generated .pbsc.ts
file. In fact, those two operators turn echo$eventStream()
into echo()
from example above.
Every message has toObject()
and toJSON()
methods which could be used to cast message to the normal JavaScript object.
To cast a JavaScript object to a message just pass it to the constructor: new Message(myObject)
.
As a side effect: just pass an instance of message to new Message()
constructor and it will be deeply cloned.
You can add global interceptors to all gRPC calls like Angular's built-in HttpClient
interceptors.
The important difference is that unlike HttpClient
interceptors GrpcInterceptor
s need to work with event streams; there are no errors thrown. Instead you should listen to the GrpcStatusEvent
and decide whether it is an error or not. Please keep this in mind.
As an example see GrpcConsoleLoggerInterceptor
in the core package.
You can enable loggin using GrpcConsoleLoggerInterceptor
(provided by @ngx-grpc/core).
To enable it provide it as interceptor and provide the parameter GRPC_CONSOLE_LOGGER_ENABLED
in your app.module.ts.
Example:
{ provide: GRPC_CONSOLE_LOGGER_ENABLED, useFactory: () => localStorage.getItem('GRPC_CONSOLE_LOGGER_ENABLED') === 'true' || !environment.prod },
{ provide: GRPC_INTERCEPTORS, useClass: GrpcConsoleLoggerInterceptor, multi: true },
Web worker allows to run gRPC clients, messages serialization and deserialization in a separate thread. It might give some performance benefits on large data sets; however the main reason of the worker is to avoid blocking the main thread. That means that rendering engine has more resources to work on rendering while the messages processing is done in parallel.
First, install additional packages:
npm i -S @ngx-grpc/worker @ngx-grpc/worker-client
Then configure the web worker. First you need to adapt the code generation command from above to pass the parameter worker=true:
:
{
"scripts": {
"proto:generate": "protoc --plugin=protoc-gen-ng=./node_modules/.bin/protoc-gen-ng --ng_out=worker=true:<OUTPUT_PATH> -I <PROTO_DIR_PATH> <PROTO_FILES>"
}
}
It will additionally generate *.pbwsc.ts
files containing the worker service client definitions.
Now, generate the worker (angular cli), e.g. with the name grpc
:
ng g webWorker grpc
or for Angular < 9
ng g worker grpc
You should see grpc.worker.ts
close to your app.module.ts
. Open this file and replace the contents with the following:
/// <reference lib="webworker" />
import { GrpcWorker } from '@ngx-grpc/worker';
import { GrpcWorkerEchoServiceClientDef } from '../proto/echo.pbwsc';
const worker = new GrpcWorker();
worker.register(
// register here all the service clients definitions
GrpcWorkerEchoServiceClientDef,
);
worker.start();
Finally, provide worker client factory instead of the grpc-web-client and provide your worker
@NgModule({
providers: [
// replace grpc-web client factory
// { provide: GRPC_CLIENT_FACTORY, useClass: GrpcWebClientFactory },
// with GrpcWorkerClientFactory
{ provide: GRPC_CLIENT_FACTORY, useClass: GrpcWorkerClientFactory },
// and wire your worker
{ provide: GRPC_WORKER, useFactory: () => new Worker('./grpc.worker', { type: 'module' }) },
],
})
export class AppModule {
}
That's it. All your requests are served by worker.
Proto 3 Any and Proto 2 Extensions
MIT