It's a small set of notes for a two-days crash course on Angular. The example project can be edited on StackBlitz ⚡️.
Angular is build in TypeScript. Give it a quick look before proceeding.
- Navigate to angular.io, just to present the argument.
- Move to the "What is Angular?" and start discussing about the framework.
It's time to setup the development environment!
- Install Node.js and a code editor.
- Install Angular CLI globally.
npm install -g @angular/cli
- Create a brand new application with the CLI.
ng new my-app
- Run
ng serve
and open the browser onhttp:https://localhost:4200
: you'll see your first Angular application!
You can also follow this document.
Angular compiler is the hearth of the framework: it takes a NgModule type and produces a NgModuleFactory. The NgModule type contains all the application items (components, directives, pipes...). The factory instead is a JavaScript class the frameworks uses to stamp out components.
JIT stands for Just In Time: it's a compilation approach which consists in building the application during the bootstrap. You've to carry the compiler with you to run in this way: this will cost more time to the user (larger bundle to download with an extra compilation time once all files are ready).
AOT - Ahead Of Time - is on the opposite side of JIT: the compilation here is run during the application build stage. There's no need to carry the compiler as part of the bundle, but you need a clear separation between static and dynamic data in your application.
Angular offers both options (JIT and AOT), but
- AOT creates smaller bundles without dead code, offers type safety even on the template and reduces the bootstrap time of the application.
- AOT requires metadata to be known statically, so the build can be performed without executing the code.
NgModules are the principal unit of distribution of Angular applications, they define the compilation context of our components. Like ES6 modules, they've declarations, imports and exports. Components can be part of only one module.
The bootstrap property defines what needs to be instanciated in the early stage of the application. Typically you'll find the root component here, withing the main module of the application. For each of the components defined here, the frameworks creates a factory. Angular is able to detect which components are used within templates and creates a factory for them. In case a component is the target of a route, it's also able to create a factory since the route configuration is statically defined.
The providers property defines the Dependency Injection container items. What's defined here is merged with providers coming from the imported modules, so that all injectable items are available to the components part of the module.
NgModules are the main unit of distribution, so they can be lazy loaded. Think about a large application with many modules. To reduce bootstrap time, the Angular router is smart enough to decide whether it's time to load a module to take the user to the requested route. This means that it'll download the specific bundle only if the feature module is requested from the user.
In Angular, every UI element is a component. There'll always be a root component, that will contains all the others.
A component has two kind of properties: input and outputs ones. Data flows from the first to the second. Inputs are available through property binding, while outputs are served via event bindings. Only inputs can be accessed with property binding, so the internal state is in a safe area. When there's a change on the properties, Angular takes care of calling the setter to propagate the change. In case the component raises an event, the framework will be responsible of propagating that calling the function we defined in the event binding.
When there's the need to pass an argument to a component and seamless sync it's value in case of changes, two-way bindings is the way. Consider the example of binding a variable to an input field. Things like this can be provided even in custom components with [ngModel]
and (ngModelChange)
bindings (which can be collapsed to [(ngModel)]
).
Property binding works for objects or constants. Without brackets, the content is threat as constant. So, myProperty="Luca"
is like doing [myProperty]="'Luca'"
: in the second case, I'm passing an explicit string type, instead of a variable name.
It describes how the component is rendered on the page. The template can be defined inline (template) or in a separate file (templateUrl). Style is part of the template, but it's defined in a dedicated property of the @Component decorator: it can also be inline (styles) or in separate file (stylesUrl). Styles scope is usually encapsulated within the component, so it won't affect the others.
Templates can access public members of the component class. Interpolation is the possibility to render members value (or function result in case of methods) on the template. Something like this ``{{ myVariable }}` will render myVariable content, updated each time it changes. It's also possible to set content with property binding.
Angular components have a defined lifecycle: it's a series of steps which we can use to perform actions. See Lifecycle hooks .
Like NgModules, even components can define their providers.
A component can interact with its host DOM element: it can listen to events, update properties and eventually invoke methods. That's the case of directives: they're components without a template. Even if it seems they're interacting directly with the DOM, under the hood Angular is wrapping the DOM API with an higher level abstraction.
A component can also access its children. They're divided in two groups: content and view children. Content ones are those specified as part of the ng-content
block, while view children are the top-level DOM elements within the template.
A directive is a component without template. It can access the host element via host binding. Have a look at the TrimDirective within the example project.
They're used to alter the item to render, applying a step before to compute the value. Consider the case of an ISO represented date: you can't display to the user 1970-01-01T00:00:000.Z
, and calling an internal formatISODate
could be an option. But {{ myISODate | myDatePipe }}
is a greather option. There's an HelloPipe within the example project.
If you've a component which depends on a service (or even more than one), you don't have to create it yourself. You'll request for it in the constructor, and the framework will do that for you. That increases the decoupling and carries other great advantages. DI is not magic: dependencies needs to be specified within providers, so the container is filled from all needed items.
Services can be registered at module or component level. The recommendation is to register services at module level, relying on the component side only for specific UI services that needs to be scoped to a particular element.
Have a look at PublicIpService in the example project.
Form handling is a complex problem. Angular wraps the DOM form, introducing a UI-independent representation. There're three building blocks: FormControl, FormGroup and FormArray which can be provided from FormsModule or ReactiveFormsModule. With this splitting, the framework is offering us a better experience while working with this stuff.
It's an indivisible part of the form, usually corresponding to an input. It has value, status and error properties.
FormGroup is a collection of FormControls. It has the same properties as the FormControl, which depends on its children.
It's a collection of the same FormControl, with a variable length. All the considerations for value, status and errors done for the FormGroup are also valid here.
const myForm = new FormGroup({
firstName: new FormControl(''),
lastName: new FormConrol('', Validators.required)
});
That's the way forms are defined in Angular. Synchronous validators can be defined at creation, or even attached dynamically later on.
Form's value can be read with getValue
method. It can be set with setValue
. If there's the need to change only part of the model, then patchValue
is the way.
Controls can be disabled with disable
method. On the other hand, enable
restores it back.
Validators.required
is a synchronous validator: it's executed while typing and its result is immediately available, since no async operations are involved. Consider the case when the validation involves an HTTP call: in that scenario an async-validator is needed.
Validators can be composed. myNumber
is a control with initial value set to 1, which is required with a minimum value of 0.
...
myNumber: new FormControl(1, [Validators.required, Validators.min(0)]),
...
FormControls (but also FormGroup and FormArray) provides two observables: statusChanges
and valueChanges
. When subscribed, they notify us about status and value changes. In this way we can react and adapt our application behavior accordingly.
Angular provides a RouteModule
which helps in defining different scenarios for each route we want for the application. Usually, routes are meant for different functionalities of the application. See In-app navigation: routing to views and the example project.