Angular
Table of Contents
Inbox
Angular
Installation, initialization
- To create Angular app, install Angular CLI and execute
ng new my-app --no-strict --standalone false --routing false
--no-strict
at a beginning will help learning by not using strict mode--standalone false
asstandalone
is some other way of working with Angular--routing false
as for the beginning routing is not needed
- In
angular.json
you can specify style sources- Ordering matters, so latter ones will override former ones
- Set it in
projects.first-app.architect.build.options.styles
property- e.g.
"styles": [ "node_modules/bootstrap/dist/css/bootstrap.min.css", "src/styles.css"]
- e.g.
- You can use Bootstrap for out-of-the-box useful styles
npm install --save bootstrap@3
- Then reach your CSS located in
node_modules/bootstrap/dist/css/bootstrap.css
- You can use minified file, which is smaller:
bootstrap.min.css
- You add the style to
angular.json
in a way mentioned above
- 🛠️
npm install
problems- When getting errors with
npm install
, a solution may be to runnpm install --legacy-peer-deps
instead ofnpm install
.
- When getting errors with
- Create Component from CLI
ng generate component my-component
- or in short:
ng g c my-component
- or in short:
- You can exclude creating tests with
--skip-tests
Selectors
- Selector
- It’s the way Angular selects an element
- Available Selectors
- Element Selector
- Used for Components
my-component
-><my-component></my-component>
- e.g.
@Component({ selector: 'app-recipes', (...) }}
- e.g.
- Attribute Selector
[my-component]
-> e.g.<div my-component></div>
- e.g.
@Directive({ selector: '[my-component]', (...) })
- e.g.
- Class Selector
.my-component
-> e.g.<div class="my-component"></div>
- ID Selectors, PseudoSelectors (like hover)
- Not supported
- Element Selector
- Data Binding
- It can be described as communication between:
- Business Logic Layer (TypeScript)
- Presentation Layer (HTML)
- It can be described as communication between:
- Data Binding possibilities
- Output Data (from TS to HTML)
- String Interpolation ``
- Property Binding - input:
[disabled]="userIsGuest"
, a:[url]="recipe.url"
- Input (React to Events)
- Event Binding
(event)="someExpression"
- Event Binding
- Output & Input (Two-Way) Data Binding
[(ngModel)]="data"
- Example:
<input type="text" [(ngModel)]="myVariable">
- Listen to everything that is inputted and save it in
myVariable
- At the same time listen to this variable and update
<input>
text accordingly
- Listen to everything that is inputted and save it in
- Output Data (from TS to HTML)
$event
- Syntax for data emitted by a given event
- Like keystroke in input
<input type="text" (input)="onKeystroke($event)">
Directives
- Directives
- Instructions in the DOM
- Structural Directives
- It means it changes the structure of the DOM
- It doesn’t hide/unhide, it really adds/removes elements
- It can be only one Structural Directive on an element
- Structural Directives start with a star
*
- Example:
*ngIf
,*ngFor
- Attribute Directives
- They allow to manipulate attributes
- They change only the element they sit on
- Example:
ngStyle
- Create
*ngIf
withelse
- Create
*ngIf
element<p *ngIf="myPredicate"; else myReference>Predicate is satisfied</p>
- Create a local reference in an “else” element
<ng-template #myReference><p>Predicate not satisfied</p></ng-template>
- Create
*ngFor
- Get index of current iteration
*ngFor="let item of items; let i = index"
- Get index of current iteration
Custom Directives
- Example
- Write directive.ts code
1 2 3 4 5 6 7 8 9
@Directive({ selector: '[appHighlight]' }) export class HighlightDirective implements OnInit { constructor(private elementRef: ElementRef, private renderer: Renderer2) { } ngOnInit() { this.renderer.setStyle(this.elementRef.nativeElement, 'background-color', 'yellow'); } }
- Add Directive to
@NgModule
declarations
:1 2 3 4 5 6
@NgModule({ declarations: [ AppComponent, HighlightDirective ] })
- Write directive.ts code
Generating Directives from CLI
ng generate directive improved-highlight
ng g d improved-highlight
Directives allow to listen to events:
1 2 3
@HostListener('mouseenter') mouseenter(event: Event) { this.renderer.setStyle(this.elementRef.nativeElement, 'background-color', 'blue') }
- Structural Directives behind the scenes
- Something like this
1 2 3
<div *ngIf="x > 0"> <p>x is greater than 0</p> </div>
- Gets translated into:
1 2 3 4 5
<ng-template [ngIf]="x > 0"> <div> <p>x is greater than 0</p> </div> </ng-template>
- Something like this
@HostBinding
- Use it to bind with a certain property, e.g.
@HostBinding('style.backgroundColor') backgroundColor = 'green'
@HostBinding('class.open') isOpen = true
- Use it to bind with a certain property, e.g.
- Example
[ngSwitch]
example:1 2 3 4 5
<div [ngSwitch]="myValue"> <p *ngSwitchCase="Yes">Yes, agreed.</p> <p *ngSwitchCase="No">No, disagreed.</p> <p *ngSwitchDefault>Can't say.</p> </div>
Binding
- Allow external components to “send” data
- HTML -> TS
@Input()
- Or binding through alias -
@Input('propertyName')
- Local reference
<input type="text" #myHTMLReference>
- Used to pass data HTML -> TS
- Used for passing data locally
- It passes the HTML Element itself (real reference)
- Can be placed on any HTML element
- Visible within the whole HTML Template
@ViewChild()
- Used to pass data HTML -> TS
- Used for local binding
- It passes
ElementRef
- Use
.nativeElement
to reference “real” element
- Use
- ❗ You shouldn’t change element using this way, only get values
- This is discouraged way of modifying DOM
- If you want to use it in
ngOnInit()
add{ static: true }
@ViewChild('localReference', { static: true })
- You can reference the first occurrence of a given type
@ViewChild(MyComponent)
<ng-content></ng-content>
- Directive used to include all HTML found within this tag
- By default all HTML within Component HTML tags is lost
- Directive used to include all HTML found within this tag
@ContentChild()
- Used to pass data HTML -> child Component’s TS
- It passes local reference from content into the child component
@ContentChild('contentReference')
- If you want to use it in
ngAfterContentInit()
add{ static: true }
@ContentChild('contentReference', { static: true })
- If you want to use it in
- E.g.:
main.component.html
1 2 3
<app-widget-component> <p #contentParagraph>Some Content to be passed to WidgetComponent</p> </app-widget-component>
widget.component.ts
1
@ContentChild('contentParagraph', { static: true }) paragraph: ElementRef
- Lifecycle Hooks
ngOnChanges()
- after on bound input property change (@Input
s)- It receives a
SimpleChanges
object
- It receives a
ngOnInit()
- Component is initialized (it is not not yet added to DOM)- Runs after constructor
ngDoCheck()
- runs whenever Angular change detection runs, e.g. button clickedngAfterContentInit()
- whenever<ng-content>
was projected into the ViewngAfterContentChecked()
- every time projected content was checkedngAfterViewInit()
- after Component’s View and child Views was initialized (rendered)ngAfterViewCheck()
- every time Component’s View and child Views was checkedngOnDestroy()
- right before Component will be destroyed
Services
- To make Service injected, add it into
providers
property of@Component
directive (to tell Angular how to handle this service), and add it intoconstructor
:1 2 3 4 5 6 7
@Component({ // ... providers: [MyService] }) export class MyComponent { constructor(private myService: MyService) {} }
- Hierarchical Injector
- It provides the same instance of a Service for a given Component and its all children.
- It’s important to note that therefore it’s not a pure
singleton
from Spring, but something like hierarchy singleton, therefore multiple instances of a given service/dependency may exist. - In short, it goes down, not up
- Scopes:
- The whole Application
- Add
@Injectable({providedIn: 'root'})
Decorator - Or before Angular 6:
- Add in
AppModule
- Add in
- Add
- All Components
- Placing instance in
AppComponent
- makes it available in all Components
- (but not for e.g. other Services)
- Placing instance in
- Component (and its children)
- Place it on Component
- ⚠️ this way it can override dependency provided on a higher level (like
AppModule
orAppComponent
)- 💡 So if you don’t want to duplicate dependency, don’t add it into
providers
array - place it only onconstructor
- 💡 So if you don’t want to duplicate dependency, don’t add it into
- The whole Application
- To make Service injected into another Service:
- it must be in the Application-wide scope
- you have to add
@Injectable()
decorator for the class- (
@Injectable()
marks class as one into which we want to inject something) - However (since some version) it’s recommended to add this annotation also on classes we want to be injected
- (
How it works
- Angular actually replaces Component tags like
<app-root></app-root>
with HTML defined in corresponding.html
files - Angular is for creating SPAs - Single Page Applications
- Even if we have Routing, etc. it’s still an SPA
- It is because there’s only one
index.html
- Angular CLI injects into
<script>
tag code that starts Angular - The first code that gets executed is
main.ts
file - By default, directories aren’t scanned, so you have to specify Components in
app.module.ts
in@NgModule
indeclarations
property - TL;DR is that Angular changes DOM (HTML) at runtime
- How Angular handles CSSes
- It adds a certain unique property to all HTML elements in the given component and applies styles only on elements with matching property, e.g.
<p _ngcontent-ejo-1>This text should be red</p>
p[_ngcontent-ejo-1] { font-color: red; }
- It adds a certain unique property to all HTML elements in the given component and applies styles only on elements with matching property, e.g.
- View Encapsulation
- There are 3 types:
None
- don’t encapsulate CSS, so it will be applies globally (se even in parent Components)Native
(ShadowDOM) - encapsulates CSS natively, but only in browsers which support itEmulated
- like real ShadowDOM, but it’s emulated, so all browsers will handle it properly
- You can set it in:
@Component({ encapsulation: ViewEncapsulation.None })
- There are 3 types:
Definitions
- Decorator
@Something
<- this is Decorator- Java Annotations are TypeScript Decorators
Various
Various
- Inline HTML/CSS for Component
- You can define HTML template in
@Component
Decorator inline- instead of
templateUrl
property, use justtemplate
- instead of
stylesUrl
, usestyles
- instead of
- You can define HTML template in
innerText
~ ``- Both these expressions are equivalent:
<p [innerText]="myValue"></p>
<p></p>
- Both these expressions are equivalent:
- Shortcut for defining class fields
- Put them into constructor like
constructor(public field: string, public anotherField: int)
- Put them into constructor like
Emmet
- Emmet
- It’s IDE plugin, which helps to work with HTML, CSS, JSX by expanding abbreviations
- e.g.
app-component
,Tab
becomes<app-component></app-component>
- Create
div
with givenclass
- creates
div
with givenclass
.container
-><div class="container"></div>
- creates
- Create nested HTML
.row>.col-xs-12
-><div class="row"><div class="col-xs-12"></div></div>
The rest
Bootstrap
- Create button group
<div class="btn-group"></div>
Testing
Running tests on WSL2 requires to handle Chrome executable.
Therefore working with them is more straightforward on host system.
- Run tests
- Build app in watch mode, launch Karma test runner
ng test
- Customize Karma test runner
- Create
karma.conf.js
, updateangular.json
ng generate config karma
- Create
- Run tests in CI/CD
ng test --no-watch --no-progress --browsers=ChromeHeadless
- Generate Coverage Report
ng test --no-watch --code-coverage
- Some ways of injecting dependencies
- Just use real production service
new SubjectUnderTest(new ProdService())
- Use fake service
new SubjectUnderTest(new FakeService())
- Use fake object
new SubjectUnderTest({ someFunction: () => 'fake value' } as ProdService)
- Use Spy
1 2 3 4
const serviceSpy = jasmine.createSpyObj("ProdService", [ 'someFunction' ]); const stubValue = "stub value"; serviceSpy.someFunction.and.returnValue(stubValue); new SubjectUnderTest(serviceSpy);
- Just use real production service
TestBed
TestBed
creates a dynamically-constructed Angular test module that emulates an Angular@NgModule
.TestBed.configureTestingModule()
method takes a metadata object that can have most of the properties of an@NgModule
.- When you want to test a Service, use
providers
metadata property and specify there other Services you want to mock.TestBed.configureTestingModule({ providers: [ MyService, { provide: OtherService, useValue: spy } ] });
- Or if using real class, instead of
useValue
, use:useClass: TestOtherService
- Inject it then using
const service = TestBed.inject(MyService)
Example:
1 2 3 4 5 6 7 8 9 10
let myService: MyService; let otherServiceSpy: jasmine.SpyObj<OtherService>; beforeEach(() => ( const spy = jasmine.createSpyObj('OtherService', ['someFunction']); TestBed.configureTestingModule({ providers: [ MyService, { provide: OtherService, useValue: spy } ]}); )); myService = TestBed.inject(MyService); otherServiceSpy = TestBed.inject(OtherService) as jasmine SpyObj<OtherService>;
Testing HTTP Services
- ❗ Always provide both
next
anderror
callbacks forObservable
s.
Otherwise you may encounter other tests failing in random places!- It is because without specifying
error
, you end up with an asynchronous uncaught observable error.
- It is because without specifying
Example of HTTP test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
let httpClientSpy: jasmine.SpyObj<HttpClient>; let myService: MyService; beforeEach(() => { httpClientSpy = jasmine.createObjSpy('HttpClient', ['get']); myService = new MyService(httpClientSpy); }); it('should return expected response', (done: DoneFn) => { const expectedValue = 'correct value'; httpClientSpy.get.and.returnValue(asyncData(expectedValue)); myService.someFunction().subscribe({ next: (returnedValue) => { expect(returnedValue).toEqual(expectedValue); done(); }, error: done.fail, }); expect(httpClientSpy.get.calls.count()).toBe(1); });
HttpClientTestingModule
- Used for complex interactions
- It’s covered in HttpGuide
Testing Components
- Remember to manually call lifecycle hook methods like
ngOnInit
(just as Angular would do)
DOM Testing
- Component is more than just a class. It interacts with DOM and other Components.
- Therefore tests covering only a class won’t tell if Component renders, interacts with user or interact with other (e.g. parent or child) Components properly.
- You need to examine DOM and simulate user interactions to be able to confirm Component works as intended.
- When function call is asynchronous, you can use
waitForAsync()
1 2 3
beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ BannerComponent ]}).compileComponents(); }));
- Basic Component test assertion
- Component can be created
expect(component).toBeDefined();
createComponent()
TestBed.createComponent(MyComponent)
creates an instance ofMyComponent
, adds it to the DOM and returns aComponentFixture
ComponentFixture
- It’s a harness for interacting with the created Component and its corresponding element
1 2 3
const componentFixture = TestBed.createComponent(MyComponent); const component = componentFixture.componentInstance; expect(component).toBeDefined();
- ❗ Don’t reconfigure
TestBed
after callingcreateComponent()
createComponent()
freezes the currentTestBed
definition, which makes it closed to further modifications
NativeElement
- It’s
any
type, as at a compile time it’s unknown what type it is or even if it’s anHTMLElement
- Tests may be run on a non-browser platform such as WebWorker or any other that doesn’t have DOM or where DOM-emulation doesn’t support full
HTMLElement
API
- Tests may be run on a non-browser platform such as WebWorker or any other that doesn’t have DOM or where DOM-emulation doesn’t support full
- In standard browser environment
nativeElement
will always be anHTMLElement
(or one of it’s derived classes) - You can use standard HTML API then, e.g.
querySelector()
1 2 3
const bannerElement: HTMLElement = componentFixture.nativeElement; const paragraph = bannerElement.querySelector('p')!; expect(p.textContext).toEqual('Hello World');
- Under the hood it’s actually
fixture.debugElement.nativeElement
- It’s
DebugElement
- An abstraction to work safely across all platforms
- Angular creates a
DebugElement
tree which wrapsNativeElement
s for the running platform DebugElement
contains methods and properties useful in testing
- Example of testing
Legend
❗ - highly important information
⚠️ - important information
💡 - tip, good practice
🛠️ - troubleshooting
💠✔️💭💬🗨️