Web & Mobile Development Concepts: Node, Express, Angular, Dart
Node.js Hello World Example
Create a simple Node.js HTTP server:
- Install Node.js if you haven’t already.
- Create a file named
server.js
and add the following code:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World\n');
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(
Server running at <a href="http://localhost:${PORT}/">http://localhost:${PORT}/</a>
);
});
- Run the server from your terminal using the command:
node server.js
. - Open your web browser and navigate to
http://localhost:3000
to see the “Hello World” message.
Node.js Error Handling Techniques
Effective error handling is crucial for building reliable Node.js applications. Here are key techniques:
Try-Catch for Synchronous Code
Use standard
try...catch
blocks to handle errors in synchronous code execution.try { JSON.parse('invalid JSON'); } catch (err) { console.error('Error parsing JSON:', err.message); }
Error-First Callbacks
A common pattern in Node.js where callback functions receive an error object as the first argument.
const fs = require('fs');
fs.readFile('nonexistent-file.txt', 'utf8', (err, data) => { if (err) { return console.error('File Read Error:', err.message); } console.log('File content:', data); });
Promises and
.catch()
for Asynchronous CodeHandle errors in Promise-based asynchronous operations using the
.catch()
method.function fetchData() { // Simulating an async operation that might fail return new Promise((resolve, reject) => { setTimeout(() => reject(new Error('Failed to fetch data')), 1000); }); }
fetchData() .then(data => console.log('Data:', data)) .catch(err => console.error('Promise Error:', err.message));
Async/Await with Try-Catch
Combine
async/await
withtry...catch
for cleaner asynchronous error handling.async function fetchDataAsync() { try { // Assuming 'fetch' returns a Promise that might reject // let response = await fetch('invalid-url'); // let data = await response.json(); // console.log(data); throw new Error('Simulated fetch error'); // Example error } catch (err) { console.error('Async/Await Fetch Error:', err.message); } }
fetchDataAsync();
Global Error Handling
Catch errors that weren’t handled locally using process event listeners.
process.on('uncaughtException', (err) => { console.error('There was an uncaught error:', err.message); process.exit(1); // Mandatory exit after uncaught exception });
process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason.message); // Application specific logging, throwing an error, or other logic here });
Combining these techniques ensures robust error management in your Node.js applications.
Introduction to the Express.js Framework
Express.js is a fast, unopinionated, and minimalist web framework for Node.js. It simplifies server-side development by providing a robust set of features for web and mobile applications, particularly for building APIs.
Key Features:
- Lightweight & Fast: Minimal core with the flexibility to add features via middleware.
- Routing: Easily define application endpoints (URIs) and how they respond to client requests.
- Middleware: Access to request/response objects allows for executing functions sequentially.
- Template Engines: Supports popular template engines like Pug, EJS, and Handlebars for rendering dynamic HTML.
- Error Handling: Provides built-in mechanisms for handling errors gracefully.
- RESTful API Support: Ideal for building robust and scalable backend APIs.
Quick Start:
- Install Express: Use npm (Node Package Manager) to install Express in your project directory.
npm install express
- Create
server.js
: Set up a basic Express server.const express = require('express'); const app = express(); const PORT = 3000;
// Define a simple route for the homepage app.get('/', (req, res) => { res.send('Hello, World!'); });
// Start the server app.listen(PORT, () => { console.log(
Server running at <a href="http://localhost:${PORT}">http://localhost:${PORT}</a>
); }); - Run the Server: Execute the script using Node.js.
node server.js
- Visit
http://localhost:3000
in your browser to see the response"Hello, World!"
.
Why Use Express.js?
- Easy to learn and use, especially for those familiar with Node.js.
- Extensive middleware support enhances functionality.
- Efficient and flexible routing system.
- Integrates well with databases like MongoDB, MySQL, etc.
- A core component of popular stacks like MEAN (MongoDB, Express, Angular, Node) and MERN (MongoDB, Express, React, Node).
Understanding Routing in Express.js
Routing in Express.js refers to defining how an application responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP request method (GET
, POST
, etc.).
Basic Syntax:
app.METHOD(PATH, HANDLER);
app
is an instance of Express.METHOD
is an HTTP request method in lowercase (e.g.,get
,post
,put
,delete
).PATH
is a path (route) on the server.HANDLER
is the function executed when the route is matched. It typically takesreq
(request) andres
(response) objects as arguments.
Examples:
Basic Routes
Respond to GET requests on the root and /about paths.
// Responds with 'Welcome!' on the homepage
app.get('/', (req, res) => {
res.send('Welcome!');
});
// Responds with 'About Page' on the /about path
app.get('/about', (req, res) => {
res.send('About Page');
});
Dynamic Routes with Route Parameters
Capture values from the URL path.
// Example: /user/101 will respond with 'User ID: 101'
app.get('/user/:id', (req, res) => {
res.send(`User ID: ${req.params.id}`);
});
Handling Query Parameters
Access data passed in the URL query string.
// Example: /search?q=express will respond with 'Search Query: express'
app.get('/search', (req, res) => {
res.send(`Search Query: ${req.query.q}`);
});
Handling POST Requests
Process data submitted in the request body (often from forms). Requires middleware like express.json()
or express.urlencoded()
.
// Middleware to parse JSON request bodies
app.use(express.json());
app.post('/user', (req, res) => {
// Assuming the request body contains { "name": "John" }
res.send(
User ${req.body.name} added successfully!
);
});
Using Middleware in Routes
Execute middleware functions before the final route handler.
const logMiddleware = (req, res, next) => {
console.log('Request received at:', Date.now());
next(); // Pass control to the next handler
};
app.get('/profile', logMiddleware, (req, res) => {
res.send('User Profile Page');
});
Route Grouping with express.Router()
Organize routes into modular files.
// In routes/products.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.send('Products List');
});
router.get('/:id', (req, res) => {
res.send(
Product Details for ID: ${req.params.id}
);
});
module.exports = router;
// In server.js
const productRoutes = require('./routes/products');
app.use('/api/products', productRoutes); // All routes in productRoutes are prefixed with /api/products
Express.js Hello World Program
Here’s how to create a basic “Hello World” application using the Express.js framework:
1. Install Express
First, ensure you have Node.js and npm installed. Then, navigate to your project directory in the terminal and run:
npm install express
2. Create server.js
Create a file named server.js
and add the following code:
const express = require('express');
const app = express();
const PORT = 3000;
// Define a route handler for GET requests to the root URL ('/')
app.get('/', (req, res) => {
res.send('Hello World'); // Send 'Hello World' as the response
});
// Start the server and listen on the specified port
app.listen(PORT, () => {
console.log(
Server running at <a href="http://localhost:${PORT}">http://localhost:${PORT}</a>
);
});
3. Run the Server
Execute the script from your terminal:
node server.js
4. Open in Browser
Open your web browser and navigate to http://localhost:3000
. You should see the text "Hello World"
displayed on the page.
This simple example demonstrates Express’s ability to quickly set up an HTTP server and handle requests.
Introduction to MongoDB NoSQL Database
MongoDB is a popular, high-performance, open-source NoSQL database. It stores data in flexible, JSON-like documents using the BSON (Binary JSON) format. This makes it well-suited for modern web applications, big data, and real-time systems.
Key Features
- Schema-less & Flexible: Documents within a collection can have different fields and structures, allowing for easy evolution of data models.
- Scalable & Fast: Supports horizontal scaling through sharding and provides indexing capabilities for high-speed queries.
- High Availability: Uses replica sets (multiple copies of data) to ensure data redundancy and automatic failover.
- Rich Query Language: Offers powerful querying capabilities, including field queries, range queries, and regular expressions.
- Document-Oriented: Stores data naturally as documents, often mapping directly to objects in application code.
Basic Commands (Mongo Shell)
- Show all databases:
show databases;
orshow dbs;
- Switch to or create a database:
use myDatabase;
- Insert a document into a collection:
db.users.insertOne({ name: "John Doe", age: 30 });
- Find all documents in a collection:
db.users.find();
- Find specific documents:
db.users.find({ age: 30 });
- Update a document:
db.users.updateOne({ name: "John Doe" }, { $set: { age: 31 } });
- Delete a document:
db.users.deleteOne({ name: "John Doe" });
Why MongoDB?
- Excellent choice for applications dealing with large volumes of unstructured or semi-structured data.
- Ideal for real-time analytics, content management, IoT applications, and cloud-native apps.
- A core component of the MERN (MongoDB, Express, React, Node) and MEAN (MongoDB, Express, Angular, Node) stacks.
- Offers flexibility, scalability, and performance advantages over traditional relational databases in certain use cases.
Creating Databases and Collections in MongoDB
In MongoDB, a database acts as a container for collections, and a collection is a group of MongoDB documents (similar to a table in relational databases). Databases and collections are typically created implicitly.
1. Creating or Switching to a Database
You don’t need an explicit command to create a database. MongoDB creates a database automatically when you first store data in it or switch to a non-existent database using the use
command in the Mongo shell.
use myNewDatabase;
This command switches the current database context to myNewDatabase
. If it doesn’t exist, MongoDB reserves the name, but the database itself is only physically created when you insert the first document into a collection within it.
2. Creating a Collection
Similar to databases, collections are usually created automatically when you first insert a document into them.
Implicit Creation (on first insert):
// Assuming the current database is 'myNewDatabase'
db.users.insertOne({ name: "Alice", age: 25, city: "New York" });
This command inserts a document into the users
collection. If the users
collection didn’t exist previously within myNewDatabase
, MongoDB creates it automatically.
Explicit Creation (using createCollection
):
You can also create a collection explicitly, which allows specifying options like capped collections or validation rules.
db.createCollection("products");
// Example with options (creating a capped collection)
db.createCollection("logs", { capped: true, size: 100000, max: 1000 });
3. Verifying Database and Collection Creation
- List Databases: To see all databases that contain data, use:
Note: Databases without any collections/data might not appear in this list.show databases;
- List Collections: After switching to a specific database (
use myNewDatabase;
), list its collections:show collections;
- View Data: Confirm data insertion:
db.users.find();
Summary
- The
use <databaseName>
command selects a database or prepares it for creation upon first data insertion. - Collections are typically created automatically when the first document is inserted.
- Use
db.createCollection()
for explicit creation with options. - Verify creation using
show databases
andshow collections
.
MongoDB’s dynamic approach to schema and structure creation simplifies development and enhances flexibility.
Using the $or Operator in MongoDB Queries
In MongoDB, the $or
operator performs a logical OR operation on an array of two or more expressions. It selects documents in a collection that satisfy at least one of the specified conditions.
Syntax
The basic syntax for the $or
operator within a find()
query is:
db.collection.find({
$or: [
{ <condition1> },
{ <condition2> },
...
{ <conditionN> }
]
});
Example
Consider a students
collection with the following documents:
1. Insert Sample Data
db.students.insertMany([
{ name: "Alice", age: 22, course: "Math", grade: "A" },
{ name: "Bob", age: 24, course: "Physics", grade: "B" },
{ name: "Charlie", age: 21, course: "Chemistry", grade: "A" },
{ name: "David", age: 23, course: "Math", grade: "C" },
{ name: "Eve", age: 22, course: "Biology", grade: "B" }
]);
2. Query: Find Students Who Are Age 22 OR Are Taking the Math Course
To find documents where the student’s age is 22 or their course is “Math”, you would use the $or
operator like this:
db.students.find({
$or: [
{ age: 22 },
{ course: "Math" }
]
});
Result: This query will return the documents for Alice (age 22), David (course Math), and Eve (age 22).
Combining $or
with Other Conditions
You can combine the $or
operator with other query criteria. MongoDB implicitly joins conditions outside the $or
array with a logical AND.
Example: Find students who (are age 22 OR are taking Math) AND have a grade of “A”.
db.students.find({
$or: [
{ age: 22 },
{ course: "Math" }
],
grade: "A" // This condition is ANDed with the $or result
});
Result: This query will return only the document for Alice (age 22 and grade A).
The $or
operator is essential for constructing complex queries that involve multiple alternative conditions.
AngularJS One-Way Data Binding Explained
In AngularJS (Angular 1.x), one-way data binding establishes a connection where data flows in a single direction: from the model (JavaScript controller scope) to the view (HTML template). Changes made to the model automatically update the corresponding parts of the view, but changes initiated in the view (e.g., user input in a standard HTML element) do not automatically update the model.
Implementation
One-way binding can be achieved primarily in two ways:
-
Using the
ng-bind
Directive: This directive replaces the content of the HTML element with the value of the specified expression. It’s generally preferred over expressions ({{ }}
) to prevent flickering (briefly showing the raw expression) before AngularJS processes it.<p ng-bind="message"></p>
-
Using Interpolation Expressions (
{{ }}
): Double curly braces embed expressions directly into the HTML text content or attribute values.<p>{{ message }}</p>
Example
Consider a simple AngularJS application:
JavaScript (Controller):
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.message = "Hello from the AngularJS Model!";
// Function to simulate model change
$scope.updateMessage = function() {
$scope.message = "Model updated at " + new Date();
};
});
HTML (View):
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title>AngularJS One-Way Binding</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<script src="app.js"></script> <!-- Assuming controller code is in app.js -->
</head>
<body ng-controller="myCtrl">
<h2>One-Way Data Binding Demo</h2>
<!-- Using ng-bind -->
<p>Using ng-bind: <span ng-bind="message"></span></p>
<!-- Using interpolation -->
<p>Using interpolation: {{ message }}</p>
<button ng-click="updateMessage()">Update Model</button>
</body>
</html>
In this example, when the “Update Model” button is clicked, the $scope.message
in the controller changes, and both paragraphs in the HTML view automatically update to reflect the new value.
Key Points
- Direction: Data flows strictly from Model (
$scope
) to View (HTML). - View Changes: User interactions with standard HTML elements bound this way (e.g., typing in a
<p>
tag if it were editable) do not affect the model. - Use Cases: Ideal for displaying data that doesn’t need to be directly modified by the user through that specific element, such as displaying titles, labels, or computed values.
- Contrast with Two-Way Binding: This differs from two-way binding (using
ng-model
on input elements), where changes in either the model or the view automatically update the other.
Angular Project File Structure Details
When you create a new Angular project using the Angular CLI command ng new my-app
, it generates a standard workspace with a well-defined file and directory structure. This structure promotes consistency and maintainability.
Here’s a typical top-level structure:
my-app/ # Root project folder
├── .angular/ # Angular CLI cache and internal files
├── .vscode/ # VS Code editor settings (optional)
├── node_modules/ # All installed npm package dependencies
├── src/ # Main application source code folder
│ ├── app/ # Core application module, components, services, etc.
│ │ ├── app-routing.module.ts # Routing configuration (if generated)
│ │ ├── app.component.css # Styles specific to the root component
│ │ ├── app.component.html # HTML template for the root component
│ │ ├── app.component.spec.ts # Unit tests for the root component
│ │ ├── app.component.ts # TypeScript logic for the root component
│ │ └── app.module.ts # Root application module definition
│ ├── assets/ # Static assets (images, fonts, JSON files, etc.)
│ ├── environments/ # Environment-specific configuration files
│ │ ├── environment.prod.ts # Production environment settings
│ │ └── environment.ts # Development environment settings
│ ├── favicon.ico # Application icon for browser tabs
│ ├── index.html # The main HTML page served to the browser
│ ├── main.ts # The main entry point; bootstraps the AppModule
│ ├── polyfills.ts # Polyfills needed for older browser support
│ ├── styles.css # Global CSS styles for the application
│ └── test.ts # Main entry point for unit tests
├── .editorconfig # Code editor configuration
├── .gitignore # Specifies intentionally untracked files for Git
├── angular.json # Angular CLI configuration file for the workspace
├── package.json # npm package dependencies and project scripts
├── package-lock.json # Records exact versions of installed dependencies
├── README.md # Project documentation (Markdown format)
├── tsconfig.app.json # TypeScript configuration specific to the application
├── tsconfig.json # Base TypeScript configuration for the project
└── tsconfig.spec.json # TypeScript configuration for unit tests
Key Directories
src/
: Contains all the source code for your application.src/app/
: The heart of your application. It contains modules, components (typically organized into subfolders), services, routing configurations, and other application logic. The root component (AppComponent
) and root module (AppModule
) reside here.src/assets/
: A place to store static files like images, fonts, icons, or data files (e.g., JSON) that will be copied as-is during the build process.src/environments/
: Holds configuration files for different build environments (e.g., development, production). You can define variables like API endpoints here.node_modules/
: Stores all the third-party libraries and dependencies downloaded via npm or yarn.
Core Files
src/index.html
: The main HTML file that the browser loads. Angular injects the application into this file, usually within the<app-root></app-root>
tags.src/main.ts
: The primary entry point for the application. It compiles the application and bootstraps the mainAppModule
to run in the browser.src/styles.css
(or.scss
,.less
, etc.): Defines global styles applied across the entire application.angular.json
: The configuration file for the Angular CLI. It defines project settings, build options, testing configurations, and more for the workspace.package.json
: Standard npm configuration file listing project dependencies, scripts (likeng serve
,ng build
), and project metadata.
This structure provides a solid foundation for building scalable and maintainable Angular applications.
Angular’s MVC-Inspired Architecture
While modern Angular (version 2+) is primarily described as a component-based architecture, its structure shares similarities with the classic Model-View-Controller (MVC) pattern, albeit with some differences in terminology and implementation.
In MVC:
- Model: Manages the application data and business logic. It’s independent of the UI.
- View: Represents the UI (User Interface) and displays data from the Model.
- Controller: Acts as an intermediary, receiving user input, interacting with the Model, and updating the View.
Angular’s MVC Equivalent
Here’s how Angular concepts loosely map to MVC:
MVC Component | Angular Equivalent | Role in Angular |
---|---|---|
Model | Services & Data Models (Classes/Interfaces) | Services encapsulate business logic, data fetching (e.g., API calls), and state management. Data models (often defined using TypeScript classes or interfaces) structure the application data. |
View | HTML Templates (.component.html ) | Templates define the structure and layout of the UI. They use Angular directives (like *ngFor , *ngIf ) and data binding ({{ }} , [] , () ) to display data and respond to user events. |
Controller | Components (.component.ts ) | Component classes contain the presentation logic for their associated templates (Views). They manage the template’s data context, handle user events (like button clicks), and interact with Services (Models) to get or update data. |
Example
Consider a simple user display feature:
Model (Service & Interface)
// src/app/user.model.ts
export interface User {
id: number;
name: string;
email: string;
}
// src/app/user.service.ts
import { Injectable } from '@angular/core';
import { User } from './user.model';
@Injectable({ providedIn: 'root' })
export class UserService {
getUser(): User {
// In a real app, this would fetch data, e.g., via HttpClient
return { id: 1, name: 'Jane Doe', email: 'jane.doe@example.com' };
}
}
Controller (Component)
// src/app/user-profile/user-profile.component.ts
import { Component, OnInit } from '@angular/core';
import { User } from '../user.model';
import { UserService } from '../user.service';
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent implements OnInit {
user: User | null = null; // Property to hold user data for the template
constructor(private userService: UserService) {} // Inject the service
ngOnInit(): void {
this.user = this.userService.getUser(); // Get data from the service (Model)
}
}
View (Template)
<!-- src/app/user-profile/user-profile.component.html -->
<h2>User Profile</h2>
<div *ngIf="user"> <!-- Display only if user data exists -->
<p><strong>Name:</strong> {{ user.name }}</p>
<p><strong>Email:</strong> {{ user.email }}</p>
</div>
<div *ngIf="!user">
<p>Loading user data...</p>
</div>
In this structure, the UserService
acts as the Model, the HTML template is the View, and the UserProfileComponent
acts as the Controller/Presenter, connecting the two and managing the UI logic.
AngularJS Services and Dependency Injection
Services and Dependency Injection (DI) are fundamental concepts in AngularJS (Angular 1.x) for organizing code, sharing data, and promoting modularity and testability.
1. What are Services in AngularJS?
A service is a reusable, singleton object that encapsulates specific logic or data operations. They are commonly used for:
- Business Logic: Centralizing application logic instead of scattering it across controllers.
- Data Sharing: Providing a way for different controllers or components to access and modify shared data.
- API Calls: Encapsulating interactions with backend APIs (often using the built-in
$http
or$resource
services). - Utility Functions: Grouping reusable helper functions.
Key characteristics of AngularJS services:
- Singleton: Only one instance of a service is created per injector (usually per application).
- Lazily Instantiated: A service is only created when a component that depends on it is first instantiated.
- Reusable: Can be injected and used across multiple controllers, directives, or even other services.
- Separation of Concerns: Help keep controllers lean by moving non-view-related logic out.
- Dependency Injection: Services are made available to other components through DI.
2. Creating and Using a Service
You can define services using methods like .service()
, .factory()
, or .provider()
. Here’s an example using .service()
, which is suitable when your service is essentially a constructor function:
Define a Service (using .service()
)
var app = angular.module('myApp', []);
// Define a service named 'UserService'
app.service('UserService', function() {
var users = [
{ id: 1, name: 'John Doe', age: 30 },
{ id: 2, name: 'Jane Smith', age: 25 }
];
// Method provided by the service
this.getUsers = function() {
return users;
};
this.getUserById = function(id) {
return users.find(user => user.id === id);
};
});
Inject and Use the Service in a Controller
app.controller('UserController', function($scope, UserService) { // Inject 'UserService'
// Use the service's methods
$scope.allUsers = UserService.getUsers();
$scope.specificUser = UserService.getUserById(1);
});
Display Data in the View
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title>AngularJS Service Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="UserController">
<h2>All Users:</h2>
<ul>
<li ng-repeat="user in allUsers">{{ user.name }} ({{ user.age }})</li>
</ul>
<h2>Specific User (ID 1):</h2>
<p ng-if="specificUser">Name: {{ specificUser.name }}, Age: {{ specificUser.age }}</p>
</body>
</html>
3. Dependency Injection (DI)
Dependency Injection is a design pattern where components receive their dependencies (other objects they need to function) from an external source (the injector) rather than creating them internally. AngularJS has a built-in DI container.
How it works:
- You register your services, factories, etc., with AngularJS’s module system.
- When defining a component (like a controller, directive, or another service), you declare its dependencies by listing their registered names as arguments to the component’s factory function.
- AngularJS’s injector reads these argument names, finds the corresponding registered components, instantiates them if necessary (or retrieves the existing singleton instance), and passes them into your component’s function.
Example (Injecting built-in $scope
and $http
services):
app.controller('DataController', function($scope, $http) {
// $scope and $http are injected by AngularJS
$scope.data = null;
$scope.error = null;
$http.get('https://api.example.com/data') // Using the injected $http service
.then(function(response) {
$scope.data = response.data; // Using the injected $scope service
})
.catch(function(error) {
$scope.error = 'Failed to load data: ' + error.statusText;
});
});
Benefits of DI:
- Modularity: Components are loosely coupled.
- Testability: Dependencies can be easily mocked or stubbed during unit testing.
- Reusability: Services and other components can be reused across the application.
- Maintainability: Code becomes easier to manage and refactor.
AngularJS ng-repeat Directive Usage
The ng-repeat
directive in AngularJS (Angular 1.x) is a powerful tool used to iterate over a collection (an array or object properties) and instantiate a template once for each item in the collection.
1. Basic Syntax
The most common syntax iterates over an array:
<element ng-repeat="item in collection">
{{ item }} ...
</element>
collection
: An expression evaluating to an array or object on the scope.item
: A variable name assigned to the current item during each iteration.- The HTML
element
(and its contents) will be duplicated for each item in thecollection
.
2. Example Usage with an Array
Controller (app.js
):
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.fruits = ["Apple", "Banana", "Mango", "Orange"];
});
HTML (View):
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title>AngularJS ng-repeat Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="myCtrl">
<h2>List of Fruits:</h2>
<ul>
<li ng-repeat="fruit in fruits">{{ fruit }}</li>
</ul>
</body>
</html>
Output: This will render an unordered list with each fruit name as a list item:
- Apple
- Banana
- Mango
- Orange
3. Using ng-repeat
with an Array of Objects
Often, you’ll iterate over arrays containing objects.
Controller:
$scope.users = [
{ name: "John Doe", age: 30, city: "New York" },
{ name: "Alice Smith", age: 25, city: "London" },
{ name: "Bob Johnson", age: 35, city: "Paris" }
];
HTML (View – rendering a table):
<table border="1">
<thead>
<tr><th>Name</th><th>Age</th><th>City</th></tr>
</thead>
<tbody>
<tr ng-repeat="user in users">
<td>{{ user.name }}</td>
<td>{{ user.age }}</td>
<td>{{ user.city }}</td>
</tr>
</tbody>
</table>
4. Special Properties and Features
ng-repeat
provides special properties within its scope:
$index
: The zero-based index of the current item (0, 1, 2…).$first
: Boolean, true if the current item is the first one.$last
: Boolean, true if the current item is the last one.$middle
: Boolean, true if the current item is not the first or last.$even
: Boolean, true if$index
is even.$odd
: Boolean, true if$index
is odd.
Example using $index
:
<li ng-repeat="fruit in fruits">{{ $index + 1 }}. {{ fruit }}</li>
Performance with track by
:
When dealing with large collections or frequent updates, use track by
to help AngularJS identify items without relying on object identity, improving performance.
<tr ng-repeat="user in users track by user.id"> ... </tr>
Filtering and Ordering:
You can chain filters directly within the ng-repeat
expression.
<!-- Filter by name containing 'a', then order by age -->
<li ng-repeat="user in users | filter:{name:'a'} | orderBy:'age'">
{{ user.name }}
</li>
ng-repeat
is essential for dynamically rendering lists and tables based on data in your AngularJS application.
Updating Data in AngularJS Applications
In AngularJS (Angular 1.x), updating data typically involves modifying variables or objects attached to the $scope
in your controller. Due to AngularJS’s two-way data binding mechanism (primarily via the ng-model
directive) and its digest cycle, changes made to the $scope
are usually reflected automatically in the view (HTML), and vice-versa for elements using ng-model
.
Here are common ways to update data:
1. Updating Simple $scope
Variables
Modify primitive values (strings, numbers, booleans) directly on the scope, often triggered by user actions (like button clicks handled by ng-click
).
Controller:
app.controller('myCtrl', function($scope) {
$scope.message = "Initial Message";
$scope.updateMessage = function(newMessage) {
// Update the scope variable
$scope.message = newMessage || "Updated Message at " + new Date();
};
});
HTML View:
<div ng-controller="myCtrl">
<p>Current Message: {{ message }}</p>
<input type="text" ng-model="newMessageText" placeholder="Enter new message">
<button ng-click="updateMessage(newMessageText)">Update Message</button>
<button ng-click="updateMessage()">Update with Timestamp</button>
</div>
When the button is clicked, the updateMessage
function runs, changes $scope.message
, and AngularJS’s digest cycle updates the paragraph (<p>
) displaying {{ message }}
.
2. Updating Items in Arrays (used with ng-repeat
)
Modify objects within an array that is being iterated over by ng-repeat
. You can update properties of existing objects, add new objects, or remove objects.
Controller:
app.controller('ListCtrl', function($scope) {
$scope.users = [
{ id: 1, name: "Alice", age: 25 },
{ id: 2, name: "Bob", age: 28 }
];
// Update an existing user's age
$scope.increaseAge = function(user) {
user.age++;
};
// Add a new user
$scope.addUser = function(newName, newAge) {
if (newName && newAge) {
const newId = $scope.users.length > 0 ? Math.max(...$scope.users.map(u => u.id)) + 1 : 1;
$scope.users.push({ id: newId, name: newName, age: parseInt(newAge) });
// Clear input fields potentially bound via ng-model
$scope.newUserName = '';
$scope.newUserAge = '';
}
};
// Remove a user
$scope.removeUser = function(userToRemove) {
const index = $scope.users.findIndex(user => user.id === userToRemove.id);
if (index > -1) {
$scope.users.splice(index, 1);
}
};
});
HTML View:
<div ng-controller="ListCtrl">
<h3>User List</h3>
<ul>
<li ng-repeat="user in users track by user.id">
{{ user.name }} ({{ user.age }})
<button ng-click="increaseAge(user)">Increase Age</button>
<button ng-click="removeUser(user)">Remove</button>
</li>
</ul>
<h4>Add New User</h4>
<input type="text" ng-model="newUserName" placeholder="Name">
<input type="number" ng-model="newUserAge" placeholder="Age">
<button ng-click="addUser(newUserName, newUserAge)">Add User</button>
</div>
Modifying the $scope.users
array (pushing, splicing, or changing object properties) triggers ng-repeat
to update the rendered list.
3. Updating Data via API Calls (using $http
or Services)
Fetch data from a server, display it, and send updates back to the server.
Controller (using $http
):
app.controller('ApiCtrl', function($scope, $http) {
$scope.items = [];
$scope.status = 'Loading...';
// Fetch initial data
$http.get('/api/items').then(function(response) {
$scope.items = response.data;
$scope.status = 'Loaded';
}).catch(function(error) {
$scope.status = 'Error loading data: ' + error.statusText;
});
// Update an item on the server
$scope.updateItem = function(item) {
$scope.status = `Updating item ${item.id}...`;
$http.put('/api/items/' + item.id, item).then(function(response) {
// Optionally update the item in the local array with server response
// Or simply indicate success
$scope.status = `Item ${item.id} updated successfully.`;
}).catch(function(error) {
$scope.status = `Error updating item ${item.id}: ` + error.statusText;
// Optionally revert local changes if the update failed
});
};
});
HTML View (simplified):
<div ng-controller="ApiCtrl">
<p>Status: {{ status }}</p>
<ul>
<li ng-repeat="item in items">
<input type="text" ng-model="item.name"> <!-- Two-way binding -->
<button ng-click="updateItem(item)">Save Changes</button>
</li>
</ul>
</div>
In this case, ng-model
provides two-way binding for the input field. When the user types, item.name
on the scope updates immediately. Clicking “Save Changes” triggers the updateItem
function, which sends the modified item
object (including the updated name) to the server via an HTTP PUT request.
In all scenarios, the core principle is to modify the data bound to the $scope
. AngularJS’s digest cycle detects these changes and updates the relevant parts of the DOM.
Benefits of Using the Dart Language
Dart is a client-optimized programming language developed by Google, designed for building fast applications on multiple platforms. It’s the language behind the popular Flutter framework for mobile, web, and desktop UI development.
Key Benefits:
- Optimized for UI Development: Dart has features specifically designed for building user interfaces, such as its asynchronous support (
async/await
) and sound type system, making it ideal for Flutter. - High Performance: Dart can be compiled Just-In-Time (JIT) during development for fast cycles and Ahead-Of-Time (AOT) into native machine code for production, ensuring fast startup and smooth runtime performance on mobile and desktop.
- Cross-Platform Development: Through Flutter, Dart allows developers to write code once and deploy it on iOS, Android, web (by compiling to JavaScript), Windows, macOS, and Linux.
- Productive Development Cycle: Features like Hot Reload (enabled by JIT compilation) allow developers to see the results of code changes almost instantly without losing application state, significantly speeding up UI building and bug fixing.
- Flexible and Sound Type System: Dart supports both static typing (catching errors at compile time) and runtime checks, offering flexibility while ensuring code safety and maintainability. Null safety further reduces runtime errors.
- Efficient Memory Management: Dart features a fast, generational garbage collector optimized for allocating many short-lived objects, common in UI programming.
- Rich Standard Library: Provides core libraries for collections, asynchronous programming, isolates (for concurrency), and more, reducing reliance on external packages for basic tasks.
- Easy to Learn: Dart’s syntax is clean and familiar to developers coming from languages like Java, C#, or JavaScript, making the learning curve relatively gentle.
- Web and Backend Capabilities: Dart can compile to efficient JavaScript for web applications (beyond Flutter Web) and can also run on the server-side using the Dart VM, enabling full-stack development with a single language.
- Strong Ecosystem and Community: Backed by Google and fueled by the success of Flutter, Dart has a rapidly growing ecosystem of packages (available on pub.dev) and a supportive community.
These benefits make Dart a compelling choice, especially for teams looking to build high-quality, performant applications across multiple platforms efficiently.
Understanding Constructors in Dart
A constructor in Dart is a special method within a class used for creating and initializing objects (instances) of that class. It shares the same name as the class and is automatically called when you use the new
keyword (which is optional in Dart 2+) or simply the class name followed by parentheses to create an instance.
Example: Basic Constructor
class Person {
String name;
int age;
// This is the constructor
// It uses 'syntactic sugar' to directly initialize instance variables.
Person(this.name, this.age);
// Method to display person details
void display() {
print("Name: $name, Age: $age");
}
}
void main() {
// Create an instance of the Person class using the constructor
var person1 = Person("Alice", 30);
var person2 = Person("Bob", 25);
// Call the display method on the created objects
person1.display(); // Output: Name: Alice, Age: 30
person2.display(); // Output: Name: Bob, Age: 25
}
Types of Constructors in Dart:
- Default Constructor: If you don’t declare any constructors, Dart provides a default, no-argument constructor, provided the class has no final fields that need initialization.
- Generative Constructor (Parameterized): The most common type, like the
Person(this.name, this.age)
example above. It takes arguments and initializes the instance variables. - Named Constructors: Allows a class to have multiple constructors with distinct names, providing alternative ways to create objects. Defined using
ClassName.constructorName()
.class Point { double x, y; Point(this.x, this.y); // Main constructor
// Named constructor for origin Point.origin() { x = 0; y = 0; } } var p1 = Point(2, 3); var p2 = Point.origin();</code></pre> </li> <li><strong>Factory Constructors:</strong> Used when implementing a constructor that doesn't always create a new instance of its class. Useful for returning cached instances, instances of a subtype, or managing complex initialization logic (like parsing JSON). Marked with the <code>factory</code> keyword. <pre><code>class Logger { final String name; static final Map<String, Logger> _cache = {}; // Private named constructor Logger._internal(this.name); // Factory constructor factory Logger(String name) { return _cache.putIfAbsent(name, () => Logger._internal(name)); } } var logger1 = Logger('UI'); var logger2 = Logger('UI'); // Returns the cached instance</code></pre> </li> <li><strong>Constant Constructors:</strong> Used to create compile-time constant objects. The class must only have final fields, and the constructor must be marked with <code>const</code>. <pre><code>class ImmutablePoint { final double x, y; const ImmutablePoint(this.x, this.y); } var p = const ImmutablePoint(1, 1); // Compile-time constant</code></pre> </li>
Constructors are essential for setting up the initial state of objects when they are created.
Nesting Rows and Columns in Flutter
In Flutter, Row
and Column
are fundamental multi-child layout widgets used to arrange their children horizontally and vertically, respectively. Nesting these widgets—placing a Row
inside a Column
or vice versa—is a common technique for creating complex and structured user interfaces.
Row Inside Column
This pattern is useful when you want a primarily vertical layout, but need some elements arranged horizontally within that vertical flow.
Example: A vertical list where one item contains multiple icons side-by-side.
import 'package:flutter/material.dart';
class RowInColumnExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center, // Center vertically in the Column
crossAxisAlignment: CrossAxisAlignment.center, // Center horizontally
children: [
Text(
"Section Title",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
SizedBox(height: 10), // Spacing
Text("Some vertical content above the row."),
SizedBox(height: 20), // Spacing
// Row nested inside the Column
Row(
mainAxisAlignment: MainAxisAlignment.center, // Center horizontally in the Row
children: [
Icon(Icons.star, color: Colors.blue, size: 40),
SizedBox(width: 15), // Spacing between icons
Icon(Icons.favorite, color: Colors.red, size: 40),
SizedBox(width: 15), // Spacing between icons
Icon(Icons.thumb_up, color: Colors.green, size: 40),
],
),
SizedBox(height: 20), // Spacing
Text("Some vertical content below the row."),
],
);
}
}
Column Inside Row
This pattern is useful for horizontal layouts where some elements need to stack vertically.
Example: A horizontal navigation bar where each item has an icon stacked above text.
import 'package:flutter/material.dart';
class ColumnInRowExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, // Distribute space horizontally
crossAxisAlignment: CrossAxisAlignment.center, // Align items vertically in the center
children: [
// First Column nested inside the Row
Column(
mainAxisSize: MainAxisSize.min, // Take minimum vertical space
children: [
Icon(Icons.home, size: 40, color: Colors.blue),
SizedBox(height: 5), // Spacing between icon and text
Text("Home"),
],
),
// Second Column nested inside the Row
Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.search, size: 40, color: Colors.orange),
SizedBox(height: 5),
Text("Search"),
],
),
// Third Column nested inside the Row
Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.person, size: 40, color: Colors.green),
SizedBox(height: 5),
Text("Profile"),
],
),
],
);
}
}
Key Considerations:
- Flexibility: Nested Rows/Columns often need to be wrapped in
Expanded
orFlexible
widgets when placed inside another Row/Column to manage available space correctly, especially if the inner widgets need to grow or shrink. - Alignment: Use
mainAxisAlignment
andcrossAxisAlignment
properties carefully in both the outer and inner Rows/Columns to achieve the desired alignment. - Complexity: Deeply nested layouts can become hard to read and debug. Consider breaking down complex UIs into smaller, reusable custom widgets.
Nesting Rows and Columns is a powerful technique for building almost any 2D layout structure in Flutter.
Common Layout Widgets in Flutter
Flutter provides a rich set of layout widgets to arrange other widgets on the screen. They can be broadly categorized into single-child and multi-child layout widgets.
1. Single-Child Layout Widgets
These widgets have only one child widget, which they typically position, constrain, or decorate.
Container
: A versatile widget for styling. It combines painting, positioning, and sizing. You can set padding, margins, borders, background color/image, shape, and constraints.Container( padding: EdgeInsets.all(16.0), margin: EdgeInsets.all(10.0), decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(8.0), ), child: Text("Styled Text", style: TextStyle(color: Colors.white)), )
Center
: Centers its child widget within itself.Padding
: Adds empty space (padding) around its child widget.Align
: Aligns its child within itself based on specified alignment values (e.g.,Alignment.topRight
,Alignment.center
).SizedBox
: Creates a box with a specified size. If used without a child, it creates empty space. Useful for adding gaps between widgets in Rows/Columns.AspectRatio
: Attempts to size its child to a specific aspect ratio.ConstrainedBox
: Imposes additional constraints (min/max width/height) on its child.FittedBox
: Scales and positions its child within itself according to a specified fit (e.g.,BoxFit.contain
).
2. Multi-Child Layout Widgets
These widgets have multiple children, which they arrange according to specific rules.
Row
: Arranges its children horizontally. UsemainAxisAlignment
(for horizontal alignment) andcrossAxisAlignment
(for vertical alignment).Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[ Icon(Icons.star), Icon(Icons.favorite), Icon(Icons.thumb_up), ], )
Column
: Arranges its children vertically. UsemainAxisAlignment
(for vertical alignment) andcrossAxisAlignment
(for horizontal alignment).Stack
: Allows widgets to overlap. Children are layered on top of each other. UsePositioned
widgets within a Stack for precise placement.ListView
: Creates a scrollable list of widgets arranged linearly (usually vertically). Efficient for long lists.GridView
: Creates a scrollable 2D array of widgets. Useful for photo galleries or dashboards.Wrap
: Arranges children horizontally or vertically, wrapping to the next line/column when space runs out. Useful for tags or chips.Table
: Arranges children in a table grid with configurable column widths.Flow
: A more advanced widget for implementing custom layout algorithms, potentially optimizing repositioning and repainting.
Choosing the Right Layout Widget
The choice depends on the desired UI structure:
Widget | Common Use Case |
---|---|
Container | Adding styling (color, padding, border, margin) to a single widget. |
Center / Align | Positioning a single widget within a larger area. |
Padding / SizedBox | Adding space around or between widgets. |
Row / Column | Arranging multiple widgets linearly (horizontally/vertically). |
Stack | Overlapping widgets (e.g., text over an image, floating buttons). |
ListView / GridView | Displaying scrollable lists or grids of items. |
Wrap | Displaying items that should flow to the next line if they don’t fit. |
Expanded / Flexible | Controlling how children share space within a Row or Column . |
Often, complex layouts are built by combining multiple layout widgets.
The Flutter Widget Tree Structure
In Flutter, the user interface is built entirely from widgets. The Widget Tree is the hierarchical structure that represents how these widgets are composed and nested to create the final UI displayed on the screen.
Key Points:
- Hierarchical Structure: Widgets are arranged in a parent-child relationship. A widget can contain one child (like
Center
orContainer
) or multiple children (likeRow
,Column
, orStack
), forming a tree-like structure. - Root Widget: Every Flutter application has a root widget, typically
MaterialApp
orCupertinoApp
, which sets up the application’s theme, navigation, and other top-level configurations. The entire UI of your app lives within this root widget. - Composition over Inheritance: Flutter favors building complex UIs by composing simple widgets together rather than inheriting properties from large base classes. This makes the structure more flexible and understandable.
- Widget Types: The tree contains various types of widgets, primarily:
StatelessWidget
: Describes a part of the UI that depends only on its own configuration (passed in via the constructor) and the build context. It’s immutable once created.StatefulWidget
: Describes a part of the UI that can change dynamically over time (e.g., due to user interaction or data updates). It works with a separateState
object that holds the mutable state and rebuilds the widget’s subtree when the state changes (viasetState()
).- Other types like
InheritedWidget
(for passing data down the tree) andRenderObjectWidget
(which interacts directly with the rendering layer).
- Build Process: Flutter builds the UI by calling the
build()
method on widgets down the tree. Eachbuild()
method returns the widget(s) that should be placed below it in the hierarchy. - Element and Render Trees: Behind the scenes, Flutter maintains corresponding Element and RenderObject trees. The Widget Tree is a blueprint; the Element Tree manages the lifecycle and state, and the RenderObject Tree handles layout and painting. Flutter efficiently updates these trees when the Widget Tree changes.
Example: Simple Widget Tree
Consider this basic Flutter app:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp( // Root Widget
home: Scaffold( // Provides basic app structure
appBar: AppBar( // Top app bar
title: Text("Simple Widget Tree"), // Child of AppBar
),
body: Center( // Child of Scaffold's body
child: Column( // Child of Center
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ // Children of Column
Text("Hello, Flutter!"),
SizedBox(height: 20), // Spacer widget
ElevatedButton(
onPressed: () {},
child: Text("Click Me"),
),
],
),
),
),
),
);
}
Visualizing the Hierarchy:
MaterialApp
Scaffold
AppBar
Text
(“Simple Widget Tree”)
Center
(as body)Column
Text
(“Hello, Flutter!”)SizedBox
ElevatedButton
Text
(“Click Me”)
Understanding the Widget Tree is crucial for structuring your UI, managing state, and optimizing performance in Flutter.
Flutter State Management Approaches
State management in Flutter refers to how you handle data that affects the UI and how the UI updates when that data changes. State can be ephemeral (local to a single widget, like an animation state) or app state (shared across different parts of the widget tree, like user preferences or shopping cart contents).
Flutter offers several ways to manage state, ranging from simple built-in mechanisms to sophisticated external packages.
1. Local State Management (setState
)
For state that is contained entirely within a single widget (typically a StatefulWidget
), the simplest approach is using the setState()
method.
Example: A Simple Counter
import 'package:flutter/material.dart';
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0; // The state variable
void _incrementCounter() {
// Call setState to update the state and trigger a rebuild
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
Text(
'$_counter', // Display the state variable
style: Theme.of(context).textTheme.headlineMedium,
),
ElevatedButton(
onPressed: _incrementCounter, // Call the method that uses setState
child: Text('Increment'),
),
],
);
}
}
setState
is suitable only for local state within a widget and its direct descendants if passed down via constructors.
2. Approaches for App State (Shared State)
When state needs to be accessed or modified by widgets far apart in the widget tree, more advanced solutions are needed. Here are some popular approaches:
Approach | Description | Best For | Pros | Cons |
---|---|---|---|---|
setState | Built-in method within StatefulWidget . | Local/Ephemeral state within a single widget. | Simple, built-in, easy to understand for local cases. | Doesn’t scale for shared app state; can lead to prop drilling. |
InheritedWidget | A built-in Flutter widget class for passing data down the widget tree efficiently. | Providing ambient context (like themes or localization) or simple app state. | Efficient updates (only dependent widgets rebuild), built-in. | Boilerplate code required; managing state lifecycle can be manual. |
Provider | A popular package built on top of InheritedWidget , simplifying state management using ChangeNotifier , FutureProvider , StreamProvider , etc. (Recommended by Google). | Small to large applications; flexible for various state types. | Less boilerplate than raw InheritedWidget , flexible, widely used, good performance. | Requires understanding concepts like ChangeNotifier and context. |
Riverpod | A reactive caching and data-binding framework by the creator of Provider. Aims to improve upon Provider’s limitations. | Medium to large applications; preferred by many over Provider. | Compile-safe, independent of Flutter tree (no BuildContext needed for access), more testable, flexible provider types. | Slightly steeper learning curve than Provider initially. |
BLoC / Cubit | (Business Logic Component) A pattern using Streams/Events to separate business logic from the UI. Cubit is a simpler subset of BLoC. Often used with the flutter_bloc package. | Large, complex applications requiring clear separation of concerns and testability. | Excellent separation of logic, highly testable, predictable state flow. | Can involve significant boilerplate code (especially full BLoC); might be overkill for simple apps. |
GetX | An all-in-one micro-framework offering state management, dependency injection, and route management with minimal syntax. | Small to medium applications; teams preferring minimal boilerplate. | Very concise syntax, fast, combines multiple utilities. | Can feel”magica” (less explicit), less structured than BLoC/Riverpod, opinionated. |
Redux | Based on the JavaScript Redux pattern, enforcing a strict unidirectional data flow (Actions, Reducers, Store). Used with packages like flutter_redux . | Large enterprise applications needing highly predictable state and debugging capabilities. | Very predictable state changes, excellent debugging tools (time travel). | Significant boilerplate, can be complex for simple state. |
The best choice depends on the application’s complexity, team familiarity, and specific requirements for testability and structure.
Commenting Code in Dart and Flutter
Dart, the language used by Flutter, supports several types of comments to help document code, explain logic, or temporarily disable code sections.
1. Single-Line Comments (//
)
These comments start with //
and continue to the end of the line. They are typically used for short explanations or to comment out a single line of code.
// This is a single-line comment explaining the next line.
var name = 'Flutter'; // This comment is at the end of a line.
// print('This line is commented out and will not execute.');
2. Multi-Line Comments (/* ... */
)
These comments start with /*
and end with */
. They can span multiple lines and are often used for longer explanations or to comment out blocks of code.
/*
This is a multi-line comment.
It can span across several lines and is useful for
explaining complex logic or sections of code.
*/
void main() {
print('Hello, Dart!');
/*
var x = 10;
var y = 20;
print(x + y); // This block is commented out.
*/
}
Multi-line comments can be nested, although this is generally uncommon.
3. Documentation Comments (///
or /** ... */
)
These comments are specifically used to generate documentation for your code using tools like dart doc
. They are placed immediately before the declaration (class, method, variable, etc.) they describe.
///
(Recommended): Used for single or multiple consecutive lines of documentation. Markdown can be used within these comments./** ... */
: The multi-line version of documentation comments, also supporting Markdown.
Example using ///
:
/// Represents a point in a 2D Cartesian coordinate system.
class Point {
/// The horizontal coordinate.
final double x;
/// The vertical coordinate.
final double y;
/// Creates a Point with the given [x] and [y] coordinates.
Point(this.x, this.y);
/// Calculates the distance from the origin (0,0).
double distanceFromOrigin() {
return (x x + y y).sqrt(); // Assuming sqrt is available
}
}
Example using /** ... */
:
/**
* A utility class for string operations.
*/
class StringUtils {
/**
* Checks if a string is null or empty.
*
* Returns `true` if the string is null or has zero length.
*/
static bool isNullOrEmpty(String? s) {
return s == null || s.isEmpty;
}
}
Usage Guide:
- Use
//
for implementation comments, quick notes, or temporarily disabling code during debugging. - Use
/* ... */
for commenting out larger blocks of code or for longer implementation comments if preferred over multiple//
lines. - Use
///
(preferred) or/** ... */
for documenting public APIs (classes, methods, functions, variables) that other developers (or your future self) will consume. Write clear explanations of what the code does, its parameters, and what it returns.
Operators Available in the Dart Language
Dart provides a comprehensive set of operators, similar to those found in other C-style languages like Java, C#, and JavaScript.
1. Arithmetic Operators
Perform mathematical calculations.
+
: Addition-
: Subtraction-expr
: Unary minus (negation)*
: Multiplication/
: Division (always results in adouble
)~/
: Integer division (results in anint
, truncating the result)%
: Modulo (remainder of integer division)++var
: Pre-incrementvar++
: Post-increment--var
: Pre-decrementvar--
: Post-decrement
2. Relational (Equality and Comparison) Operators
Compare values.
==
: Equal!=
: Not equal>
: Greater than<
: Less than>=
: Greater than or equal to<=
: Less than or equal to
3. Type Test Operators
Check types at runtime.
as
: Typecast (throws an exception if the cast is invalid)is
: Type check (returnstrue
if the object has the specified type)is!
: Negative type check (returnstrue
if the object doesn’t have the specified type)
4. Assignment Operators
Assign values to variables.
=
: Basic assignment??=
: Assign value only if the variable is null- Compound assignment operators:
+=
,-=
,*=
,/=
,~/=
,%=
,&=
,|=
,^=
,<<=
,>>=
(e.g.,a += b
is shorthand fora = a + b
)
5. Logical Operators
Combine boolean expressions.
!expr
: Logical NOT (inverts boolean value)||
: Logical OR&&
: Logical AND
6. Bitwise and Shift Operators
Manipulate individual bits of integers.
&
: Bitwise AND|
: Bitwise OR^
: Bitwise XOR~expr
: Bitwise NOT (inverts bits)<<
: Left shift>>
: Right shift>>>
: Unsigned right shift (Dart 3 onwards)
7. Conditional Operators
Evaluate expressions based on conditions.
condition ? expr1 : expr2
: Ternary operator (ifcondition
is true, evaluates toexpr1
; otherwise, evaluates toexpr2
).expr1 ?? expr2
: Null-aware operator (ifexpr1
is non-null, evaluates to its value; otherwise, evaluates toexpr2
).
8. Cascade Notation (..
)
Allows making a sequence of operations on the same object.
var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
9. Null-aware Operators
Work with potentially null values safely.
?.
: Null-aware member access (accesses a property/method only if the object is non-null, otherwise returns null).??
: Null-aware operator (explained in Conditional Operators).??=
: Null-aware assignment (explained in Assignment Operators).!.
: Null assertion operator (casts away nullability, throws if the object is null at runtime). Use with caution.
10. Subscript Access Operator ([]
)
Used to access elements in lists, maps, or custom classes that implement it.
List<int> list = [1, 2, 3];
int first = list[0];
Map<String, int> map = {'a': 1};
int value = map['a'];